feat(file): 在文件预览界面添加评论功能

- 实现文件评论的前端展示界面,包括评论列表、输入框和交互功能
- 添加文件评论的API接口,支持查询和新增评论操作
- 设计并实现后端服务层、控制器、数据访问层等相关组件
- 集成评论功能到现有的文件预览页面,支持展开/收起评论区
- 实现评论数据的实时加载和提交功能,优化用户体验
This commit is contained in:
jhd
2026-07-04 10:02:52 +08:00
parent 5029d09f09
commit 09466ad4a7
10 changed files with 443 additions and 0 deletions

View 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
})
}

View File

@@ -192,6 +192,36 @@
<span>{{ selectedFile.createBy }}</span>
<span>{{ parseTime(selectedFile.createTime) }}</span>
</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">
<ImagePreview v-if="fileTypeCategory === 'image'" :src="selectedFile.filePath" />
<PdfPreview v-else-if="fileTypeCategory === 'pdf'" :src="selectedFile.filePath" />
@@ -298,6 +328,7 @@
<script>
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 UserSelect from '@/components/KLPService/UserSelect/index'
import ImagePreview from '@/components/FilePreview/preview/image/index.vue'
@@ -397,6 +428,11 @@ export default {
infoVisible: false,
infoTitle: '',
infoFile: null,
// 评论
comments: [],
commentExpanded: false,
commentInput: '',
commentLoading: false,
// 拖拽调节宽度
leftPanelWidth: '40%',
isDragging: false,
@@ -422,6 +458,16 @@ export default {
created() {
this.getList()
},
watch: {
selectedFile(val) {
if (val) {
this.loadComments()
} else {
this.comments = []
this.commentExpanded = false
}
}
},
methods: {
/** 切换tab */
handleTabClick(tab) {
@@ -722,6 +768,25 @@ export default {
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) {
this.isDragging = true
@@ -894,6 +959,88 @@ export default {
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 {
flex: 1;
overflow: auto;