feat(file): 在文件预览界面添加评论功能
- 实现文件评论的前端展示界面,包括评论列表、输入框和交互功能 - 添加文件评论的API接口,支持查询和新增评论操作 - 设计并实现后端服务层、控制器、数据访问层等相关组件 - 集成评论功能到现有的文件预览页面,支持展开/收起评论区 - 实现评论数据的实时加载和提交功能,优化用户体验
This commit is contained in:
@@ -0,0 +1,50 @@
|
|||||||
|
package com.klp.web.controller.system;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import javax.validation.constraints.*;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import com.klp.common.annotation.RepeatSubmit;
|
||||||
|
import com.klp.common.annotation.Log;
|
||||||
|
import com.klp.common.core.controller.BaseController;
|
||||||
|
import com.klp.common.core.domain.R;
|
||||||
|
import com.klp.common.core.validate.AddGroup;
|
||||||
|
import com.klp.common.enums.BusinessType;
|
||||||
|
import com.klp.system.domain.vo.SysFileCommentVo;
|
||||||
|
import com.klp.system.domain.bo.SysFileCommentBo;
|
||||||
|
import com.klp.system.service.ISysFileCommentService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件评论
|
||||||
|
*
|
||||||
|
* @author klp
|
||||||
|
* @date 2026-07-04
|
||||||
|
*/
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system/file/comment")
|
||||||
|
public class SysFileCommentController extends BaseController {
|
||||||
|
|
||||||
|
private final ISysFileCommentService iSysFileCommentService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询文件评论列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/list/{fileId}")
|
||||||
|
public R<List<SysFileCommentVo>> list(@NotNull(message = "文件ID不能为空") @PathVariable Long fileId) {
|
||||||
|
return R.ok(iSysFileCommentService.queryListByFileId(fileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增文件评论
|
||||||
|
*/
|
||||||
|
@Log(title = "文件评论", businessType = BusinessType.INSERT)
|
||||||
|
@RepeatSubmit()
|
||||||
|
@PostMapping()
|
||||||
|
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysFileCommentBo bo) {
|
||||||
|
return toAjax(iSysFileCommentService.insertByBo(bo));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.klp.system.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import com.klp.common.core.domain.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件评论对象 sys_file_comment
|
||||||
|
*
|
||||||
|
* @author klp
|
||||||
|
* @date 2026-07-04
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("sys_file_comment")
|
||||||
|
public class SysFileComment extends BaseEntity {
|
||||||
|
|
||||||
|
private static final long serialVersionUID=1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "comment_id")
|
||||||
|
private Long commentId;
|
||||||
|
/**
|
||||||
|
* 关联文件ID
|
||||||
|
*/
|
||||||
|
private Long fileId;
|
||||||
|
/**
|
||||||
|
* 评论内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
/**
|
||||||
|
* 所属部门
|
||||||
|
*/
|
||||||
|
private String dept;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.klp.system.domain.bo;
|
||||||
|
|
||||||
|
import com.klp.common.core.domain.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import javax.validation.constraints.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件评论业务对象 sys_file_comment
|
||||||
|
*
|
||||||
|
* @author klp
|
||||||
|
* @date 2026-07-04
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class SysFileCommentBo extends BaseEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
private Long commentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联文件ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "文件ID不能为空")
|
||||||
|
private Long fileId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论内容
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "评论内容不能为空")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属部门
|
||||||
|
*/
|
||||||
|
private String dept;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.klp.system.domain.vo;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||||
|
import com.klp.common.core.domain.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件评论视图对象 sys_file_comment
|
||||||
|
*
|
||||||
|
* @author klp
|
||||||
|
* @date 2026-07-04
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ExcelIgnoreUnannotated
|
||||||
|
public class SysFileCommentVo extends BaseEntity {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
private Long commentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联文件ID
|
||||||
|
*/
|
||||||
|
private Long fileId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属部门
|
||||||
|
*/
|
||||||
|
private String dept;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.klp.system.mapper;
|
||||||
|
|
||||||
|
import com.klp.system.domain.SysFileComment;
|
||||||
|
import com.klp.system.domain.vo.SysFileCommentVo;
|
||||||
|
import com.klp.common.core.mapper.BaseMapperPlus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件评论Mapper接口
|
||||||
|
*
|
||||||
|
* @author klp
|
||||||
|
* @date 2026-07-04
|
||||||
|
*/
|
||||||
|
public interface SysFileCommentMapper extends BaseMapperPlus<SysFileCommentMapper, SysFileComment, SysFileCommentVo> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.klp.system.service;
|
||||||
|
|
||||||
|
import com.klp.system.domain.vo.SysFileCommentVo;
|
||||||
|
import com.klp.system.domain.bo.SysFileCommentBo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件评论Service接口
|
||||||
|
*
|
||||||
|
* @author klp
|
||||||
|
* @date 2026-07-04
|
||||||
|
*/
|
||||||
|
public interface ISysFileCommentService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询文件评论列表
|
||||||
|
*/
|
||||||
|
List<SysFileCommentVo> queryListByFileId(Long fileId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增文件评论
|
||||||
|
*/
|
||||||
|
Boolean insertByBo(SysFileCommentBo bo);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.klp.system.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import com.klp.common.helper.LoginHelper;
|
||||||
|
import com.klp.system.domain.bo.SysFileCommentBo;
|
||||||
|
import com.klp.system.domain.vo.SysFileCommentVo;
|
||||||
|
import com.klp.system.domain.SysFileComment;
|
||||||
|
import com.klp.system.mapper.SysFileCommentMapper;
|
||||||
|
import com.klp.system.service.ISysFileCommentService;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件评论Service业务层处理
|
||||||
|
*
|
||||||
|
* @author klp
|
||||||
|
* @date 2026-07-04
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Service
|
||||||
|
public class SysFileCommentServiceImpl implements ISysFileCommentService {
|
||||||
|
|
||||||
|
private final SysFileCommentMapper baseMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询文件评论列表(按时间升序)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<SysFileCommentVo> queryListByFileId(Long fileId) {
|
||||||
|
LambdaQueryWrapper<SysFileComment> lqw = Wrappers.lambdaQuery();
|
||||||
|
lqw.eq(SysFileComment::getFileId, fileId);
|
||||||
|
lqw.orderByAsc(SysFileComment::getCreateTime);
|
||||||
|
return baseMapper.selectVoList(lqw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增文件评论
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean insertByBo(SysFileCommentBo bo) {
|
||||||
|
SysFileComment add = BeanUtil.toBean(bo, SysFileComment.class);
|
||||||
|
// 自动填充部门和评论人
|
||||||
|
add.setDept(LoginHelper.getLoginUser().getDeptName());
|
||||||
|
return baseMapper.insert(add) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.klp.system.mapper.SysFileCommentMapper">
|
||||||
|
|
||||||
|
<resultMap type="com.klp.system.domain.SysFileComment" id="SysFileCommentResult">
|
||||||
|
<result property="commentId" column="comment_id"/>
|
||||||
|
<result property="fileId" column="file_id"/>
|
||||||
|
<result property="content" column="content"/>
|
||||||
|
<result property="dept" column="dept"/>
|
||||||
|
<result property="createBy" column="create_by"/>
|
||||||
|
<result property="createTime" column="create_time"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
18
klp-ui/src/api/system/fileComment.js
Normal file
18
klp-ui/src/api/system/fileComment.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 查询文件评论列表
|
||||||
|
export function listFileComment(fileId) {
|
||||||
|
return request({
|
||||||
|
url: '/system/file/comment/list/' + fileId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增文件评论
|
||||||
|
export function addFileComment(data) {
|
||||||
|
return request({
|
||||||
|
url: '/system/file/comment',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -192,6 +192,36 @@
|
|||||||
<span>{{ selectedFile.createBy }}</span>
|
<span>{{ selectedFile.createBy }}</span>
|
||||||
<span>{{ parseTime(selectedFile.createTime) }}</span>
|
<span>{{ parseTime(selectedFile.createTime) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="preview-comment">
|
||||||
|
<div class="comment-bar" @click="commentExpanded = !commentExpanded">
|
||||||
|
<span><i class="el-icon-chat-dot-round"></i> 评论 ({{ comments.length }})</span>
|
||||||
|
<span class="comment-toggle">{{ commentExpanded ? '收起' : '展开' }} <i :class="commentExpanded ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i></span>
|
||||||
|
</div>
|
||||||
|
<div v-show="commentExpanded" class="comment-body">
|
||||||
|
<div v-if="commentLoading" class="comment-hint"><i class="el-icon-loading"></i> 加载中...</div>
|
||||||
|
<div v-else-if="comments.length === 0" class="comment-hint comment-empty">暂无评论</div>
|
||||||
|
<div v-else class="comment-list">
|
||||||
|
<div v-for="item in comments" :key="item.commentId" class="comment-item">
|
||||||
|
<div class="comment-meta">
|
||||||
|
<span class="comment-dept">{{ item.dept }}</span>
|
||||||
|
<span class="comment-user">{{ item.createBy }}</span>
|
||||||
|
<span class="comment-time">{{ parseTime(item.createTime) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="comment-text">{{ item.content }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="comment-input-area">
|
||||||
|
<el-input
|
||||||
|
v-model="commentInput"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
placeholder="输入评论..."
|
||||||
|
resize="none"
|
||||||
|
/>
|
||||||
|
<el-button type="primary" size="mini" @click="handleAddComment" :disabled="!commentInput.trim()" style="margin-top: 6px;">发送</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="preview-content">
|
<div class="preview-content">
|
||||||
<ImagePreview v-if="fileTypeCategory === 'image'" :src="selectedFile.filePath" />
|
<ImagePreview v-if="fileTypeCategory === 'image'" :src="selectedFile.filePath" />
|
||||||
<PdfPreview v-else-if="fileTypeCategory === 'pdf'" :src="selectedFile.filePath" />
|
<PdfPreview v-else-if="fileTypeCategory === 'pdf'" :src="selectedFile.filePath" />
|
||||||
@@ -298,6 +328,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { listFile, getFile, addFile, updateFile, delFile, exportFile, listVisibleUser, addVisibleUser, delVisibleUser, listVisibleUserByFileId, listRelatedToMe, incrementView } from '@/api/system/file'
|
import { listFile, getFile, addFile, updateFile, delFile, exportFile, listVisibleUser, addVisibleUser, delVisibleUser, listVisibleUserByFileId, listRelatedToMe, incrementView } from '@/api/system/file'
|
||||||
|
import { listFileComment, addFileComment } from '@/api/system/fileComment'
|
||||||
import { getToken } from '@/utils/auth'
|
import { getToken } from '@/utils/auth'
|
||||||
import UserSelect from '@/components/KLPService/UserSelect/index'
|
import UserSelect from '@/components/KLPService/UserSelect/index'
|
||||||
import ImagePreview from '@/components/FilePreview/preview/image/index.vue'
|
import ImagePreview from '@/components/FilePreview/preview/image/index.vue'
|
||||||
@@ -397,6 +428,11 @@ export default {
|
|||||||
infoVisible: false,
|
infoVisible: false,
|
||||||
infoTitle: '',
|
infoTitle: '',
|
||||||
infoFile: null,
|
infoFile: null,
|
||||||
|
// 评论
|
||||||
|
comments: [],
|
||||||
|
commentExpanded: false,
|
||||||
|
commentInput: '',
|
||||||
|
commentLoading: false,
|
||||||
// 拖拽调节宽度
|
// 拖拽调节宽度
|
||||||
leftPanelWidth: '40%',
|
leftPanelWidth: '40%',
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
@@ -422,6 +458,16 @@ export default {
|
|||||||
created() {
|
created() {
|
||||||
this.getList()
|
this.getList()
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
selectedFile(val) {
|
||||||
|
if (val) {
|
||||||
|
this.loadComments()
|
||||||
|
} else {
|
||||||
|
this.comments = []
|
||||||
|
this.commentExpanded = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** 切换tab */
|
/** 切换tab */
|
||||||
handleTabClick(tab) {
|
handleTabClick(tab) {
|
||||||
@@ -722,6 +768,25 @@ export default {
|
|||||||
window.open(row.filePath, '_blank')
|
window.open(row.filePath, '_blank')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/** 加载评论列表 */
|
||||||
|
loadComments() {
|
||||||
|
this.commentLoading = true
|
||||||
|
listFileComment(this.selectedFile.fileId).then(res => {
|
||||||
|
this.comments = res.data || []
|
||||||
|
this.commentLoading = false
|
||||||
|
}).catch(() => {
|
||||||
|
this.commentLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/** 发送评论 */
|
||||||
|
handleAddComment() {
|
||||||
|
const content = this.commentInput.trim()
|
||||||
|
if (!content) return
|
||||||
|
addFileComment({ fileId: this.selectedFile.fileId, content }).then(() => {
|
||||||
|
this.commentInput = ''
|
||||||
|
this.loadComments()
|
||||||
|
})
|
||||||
|
},
|
||||||
/** 开始拖拽 */
|
/** 开始拖拽 */
|
||||||
startResize(e) {
|
startResize(e) {
|
||||||
this.isDragging = true
|
this.isDragging = true
|
||||||
@@ -894,6 +959,88 @@ export default {
|
|||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preview-comment {
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-bar {
|
||||||
|
padding: 8px 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-bar:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-toggle {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-body {
|
||||||
|
max-height: 260px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-hint {
|
||||||
|
padding: 16px;
|
||||||
|
text-align: center;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-list {
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-item {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-bottom: 1px solid #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-dept {
|
||||||
|
color: #409eff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-user {
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-time {
|
||||||
|
color: #c0c4cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #303133;
|
||||||
|
line-height: 1.5;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-input-area {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-top: 1px solid #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
.preview-content {
|
.preview-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|||||||
Reference in New Issue
Block a user