feat: 增加文档替换功能,文档上传成功/失败 消息提示优化
This commit is contained in:
parent
676c76d1c2
commit
fb1237b1d3
|
|
@ -2,7 +2,7 @@
|
|||
<div class="doc-upload-container">
|
||||
<!-- 步骤条 -->
|
||||
<el-steps :active="currentStep" align-center class="upload-steps">
|
||||
<el-step title="选择文件" icon="Upload" />
|
||||
<el-step :title="props.replaceFileId ? '选择替换文件' : '选择文件'" icon="Upload" />
|
||||
<el-step title="解析模式" icon="Setting" />
|
||||
<el-step :title="getStep3Title()" :icon="getStep3Icon()" />
|
||||
</el-steps>
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
<!-- 第一步:文件选择 -->
|
||||
<div v-if="currentStep === 0" class="step-panel file-selection">
|
||||
<div class="panel-header">
|
||||
<h3>选择要上传的文件</h3>
|
||||
<h3>{{ props.replaceFileId ? '选择要替换的文件' : '选择要上传的文件' }}</h3>
|
||||
<p class="panel-description">支持多种文档格式,拖拽或点击选择文件</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -22,7 +22,8 @@
|
|||
drag
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
:file-list="uploadFiles"
|
||||
:file-list="[]"
|
||||
:show-file-list="false"
|
||||
:limit="10"
|
||||
accept=".txt,.md,.markdown,.mdx,.pdf,.html,.htm,.xlsx,.xls,.doc,.docx,.csv,.vtt,.properties"
|
||||
class="upload-area"
|
||||
|
|
@ -281,7 +282,7 @@
|
|||
<!-- 自适应模式 - 确认信息 -->
|
||||
<div v-else class="adaptive-confirm">
|
||||
<div class="panel-header">
|
||||
<h3>确认上传</h3>
|
||||
<h3>{{ props.replaceFileId ? '确认替换' : '确认上传' }}</h3>
|
||||
<p class="panel-description">使用自适应模式,系统将自动选择最佳解析策略</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -321,7 +322,7 @@
|
|||
:loading="uploading"
|
||||
:disabled="!canUpload()"
|
||||
>
|
||||
开始上传
|
||||
{{ props.replaceFileId ? '开始替换' : '开始上传' }}
|
||||
</el-button>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
</div>
|
||||
|
|
@ -343,19 +344,21 @@ import {
|
|||
Tools,
|
||||
Check
|
||||
} from '@element-plus/icons-vue'
|
||||
import { uploadDocument } from '@/api/dataset'
|
||||
import { uploadDocument, deleteDocument } from '@/api/dataset'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
datasetId: string
|
||||
visible: boolean
|
||||
parentId?: number
|
||||
replaceFileId?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
datasetId: '',
|
||||
visible: false,
|
||||
parentId: undefined
|
||||
parentId: undefined,
|
||||
replaceFileId: undefined
|
||||
})
|
||||
|
||||
// Emits
|
||||
|
|
@ -435,7 +438,7 @@ const getStep3Title = () => {
|
|||
switch (uploadForm.analysisStrategyType) {
|
||||
case 'custom': return '参数设置'
|
||||
case 'deep': return '处理进度'
|
||||
default: return '确认上传'
|
||||
default: return props.replaceFileId ? '确认替换' : '确认上传'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -473,6 +476,14 @@ const handleDragLeave = () => {
|
|||
|
||||
const removeFile = (index: number) => {
|
||||
uploadFiles.value.splice(index, 1)
|
||||
// 同步更新 el-upload 组件的内部文件列表
|
||||
if (uploadRef.value) {
|
||||
uploadRef.value.clearFiles()
|
||||
// 重新添加剩余文件到 el-upload 组件
|
||||
uploadFiles.value.forEach(file => {
|
||||
uploadRef.value.handleStart(file.raw)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const formatFileSize = (size?: number) => {
|
||||
|
|
@ -548,18 +559,42 @@ const handleUpload = async () => {
|
|||
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '正在上传文件...',
|
||||
text: props.replaceFileId ? '正在替换文件...' : '正在上传文件...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
|
||||
try {
|
||||
uploading.value = true
|
||||
|
||||
// 如果是文件替换,先删除旧文件
|
||||
if (props.replaceFileId) {
|
||||
// 关闭当前loading,显示删除进度
|
||||
loading.close()
|
||||
const deleteLoading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '正在删除旧文件...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
|
||||
await deleteDocument(parseInt(props.replaceFileId))
|
||||
deleteLoading.close()
|
||||
|
||||
// 重新显示上传进度
|
||||
const uploadLoading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '正在上传新文件...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
// 更新loading引用
|
||||
loading.close = uploadLoading.close
|
||||
}
|
||||
|
||||
// 构造请求数据,符合后端DocUploadReq结构
|
||||
const requestData: any = {
|
||||
datasetId: props.datasetId,
|
||||
analysisStrategyType: uploadForm.analysisStrategyType,
|
||||
parentId: props.parentId // 添加parentId参数,根目录时为undefined/null
|
||||
parentId: props.parentId, // 添加parentId参数,根目录时为undefined/null
|
||||
// 注意:删除 replaceFileId,因为我们已经手动删除了旧文件
|
||||
}
|
||||
|
||||
// 如果是自定义模式,添加解析策略
|
||||
|
|
@ -599,16 +634,18 @@ const handleUpload = async () => {
|
|||
}
|
||||
|
||||
ElNotification({
|
||||
title: '上传成功',
|
||||
message: `成功上传 ${uploadFiles.value.length} 个文件`,
|
||||
title: props.replaceFileId ? '替换成功' : '上传成功',
|
||||
message: props.replaceFileId
|
||||
? `成功替换 ${uploadFiles.value.length} 个文件`
|
||||
: `成功上传 ${uploadFiles.value.length} 个文件`,
|
||||
type: 'success'
|
||||
})
|
||||
|
||||
emit('success')
|
||||
handleCancel()
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error)
|
||||
ElMessage.error('上传失败,请重试')
|
||||
console.error(props.replaceFileId ? '替换失败:' : '上传失败:', error)
|
||||
//ElMessage.error(props.replaceFileId ? '替换失败,请重试' : '上传失败,请重试')
|
||||
} finally {
|
||||
uploading.value = false
|
||||
loading.close()
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@
|
|||
<span v-else class="folder-indicator">--</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('vabI18n.knowledge.document.table.actions')" width="320" fixed="right">
|
||||
<el-table-column :label="t('vabI18n.knowledge.document.table.actions')" width="380" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<!-- 文件操作 -->
|
||||
|
|
@ -166,6 +166,15 @@
|
|||
>
|
||||
{{t('vabI18n.knowledge.document.buttons.download')}}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
:icon="Upload"
|
||||
text
|
||||
class="action-btn"
|
||||
@click="handleFileReplaceFromRow(row)"
|
||||
>
|
||||
替换
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 文件夹操作 -->
|
||||
<template v-else>
|
||||
|
|
@ -235,6 +244,43 @@
|
|||
/>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 文件替换对话框 -->
|
||||
<el-dialog
|
||||
v-model="replaceDialogVisible"
|
||||
title="替换文件"
|
||||
width="900px"
|
||||
:close-on-click-modal="false"
|
||||
class="upload-dialog"
|
||||
>
|
||||
<div v-if="replaceTargetFile" class="replace-info">
|
||||
<div class="target-file-info">
|
||||
<h4>将要替换的文件:</h4>
|
||||
<div class="file-info-card">
|
||||
<img
|
||||
:src="getFileTypeIcon(replaceTargetFile.fileType)"
|
||||
alt="文件图标"
|
||||
class="file-icon"
|
||||
/>
|
||||
<div class="file-details">
|
||||
<div class="file-name">{{ replaceTargetFile.fileName }}</div>
|
||||
<div class="file-meta">
|
||||
<span>大小:{{ formatFileSize(replaceTargetFile.charCount) }}</span>
|
||||
<span>创建时间:{{ replaceTargetFile.createDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DocUpload
|
||||
:visible="replaceDialogVisible"
|
||||
:dataset-id="datasetId"
|
||||
:parent-id="currentParentId"
|
||||
:replace-file-id="replaceTargetFile?.id"
|
||||
@update:visible="replaceDialogVisible = $event"
|
||||
@success="handleReplaceSuccess"
|
||||
/>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 预览抽屉 -->
|
||||
<el-drawer
|
||||
v-model="previewDrawerVisible"
|
||||
|
|
@ -467,6 +513,8 @@ const datasetName = ref('');
|
|||
const previewLoading = ref(true)
|
||||
|
||||
const uploadDialogVisible = ref(false)
|
||||
const replaceDialogVisible = ref(false)
|
||||
const replaceTargetFile = ref<FileItem | null>(null)
|
||||
|
||||
// 重命名相关
|
||||
const renameDialogVisible = ref(false)
|
||||
|
|
@ -518,6 +566,8 @@ name: string;
|
|||
onUnmounted(() => {
|
||||
// 清理右键菜单事件监听器
|
||||
document.removeEventListener('click', hideContextMenu)
|
||||
// 停止自动刷新定时器
|
||||
stopAutoRefresh()
|
||||
})
|
||||
|
||||
const getFileTypeIcon = (fileType: string) => {
|
||||
|
|
@ -766,6 +816,12 @@ const triggerFileInput = () => {
|
|||
uploadDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 文件替换处理 - 从行操作触发
|
||||
const handleFileReplaceFromRow = (row: FileItem) => {
|
||||
replaceTargetFile.value = row
|
||||
replaceDialogVisible.value = true
|
||||
}
|
||||
|
||||
const indexMethod = (index: number) => {
|
||||
return (pagination.current - 1) * pagination.size + index + 1
|
||||
}
|
||||
|
|
@ -776,9 +832,22 @@ return (pagination.current - 1) * pagination.size + index + 1
|
|||
const handleUploadSuccess = async () => {
|
||||
uploadDialogVisible.value = false
|
||||
await fetchDocuments()
|
||||
// ElNotification({
|
||||
// title: t('vabI18n.knowledge.document.messages.uploadSuccess'),
|
||||
// message: t('vabI18n.knowledge.document.messages.uploadSuccessEnd'),
|
||||
// type: 'success'
|
||||
// })
|
||||
}
|
||||
|
||||
// 处理文件替换成功回调
|
||||
const handleReplaceSuccess = async () => {
|
||||
replaceDialogVisible.value = false
|
||||
replaceTargetFile.value = null
|
||||
selectedRows.value = [] // 清空选择
|
||||
await fetchDocuments()
|
||||
ElNotification({
|
||||
title: t('vabI18n.knowledge.document.messages.uploadSuccess'),
|
||||
message: t('vabI18n.knowledge.document.messages.uploadSuccessEnd'),
|
||||
title: '替换成功',
|
||||
message: '文件替换成功',
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
|
|
@ -896,6 +965,28 @@ const handlePreview = async (row: FileItem) => {
|
|||
// 在script setup部分添加loading状态
|
||||
const tableLoading = ref(false)
|
||||
|
||||
// 定时器相关
|
||||
const refreshTimer = ref<NodeJS.Timeout | null>(null)
|
||||
|
||||
// 启动定时器
|
||||
const startAutoRefresh = () => {
|
||||
stopAutoRefresh() // 先清除可能存在的定时器
|
||||
refreshTimer.value = setInterval(() => {
|
||||
// 只有在非加载状态下才自动刷新
|
||||
if (!tableLoading.value) {
|
||||
fetchDocuments()
|
||||
}
|
||||
}, 30000) // 5秒刷新一次
|
||||
}
|
||||
|
||||
// 停止定时器
|
||||
const stopAutoRefresh = () => {
|
||||
if (refreshTimer.value) {
|
||||
clearInterval(refreshTimer.value)
|
||||
refreshTimer.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// 修改fetchDocuments函数
|
||||
const fetchDocuments = async () => {
|
||||
tableLoading.value = true
|
||||
|
|
@ -988,7 +1079,7 @@ const typeMap: Record<string, 'success' | 'primary' | 'warning' | 'info' | 'dang
|
|||
'preprocessing': 'info',
|
||||
'indexing': 'warning',
|
||||
'waiting': 'info',
|
||||
'queued': 'primary',
|
||||
'queued': 'info', // 修改为info类型,显示较深灰色
|
||||
'failed': 'danger',
|
||||
'completed': 'success'
|
||||
}
|
||||
|
|
@ -1044,6 +1135,8 @@ datasetId.value = route.params.id as string
|
|||
datasetName.value = route.query.name as string
|
||||
// 这里可以调用API获取详细数据
|
||||
await fetchDocuments()
|
||||
// 启动自动刷新定时器
|
||||
startAutoRefresh()
|
||||
})
|
||||
|
||||
// 分页变化处理
|
||||
|
|
@ -1677,6 +1770,67 @@ border-top: 1px solid #ebeef5;
|
|||
|
||||
|
||||
|
||||
// 文件替换信息样式
|
||||
.replace-info {
|
||||
margin-bottom: 24px;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #e6a23c;
|
||||
|
||||
.target-file-info {
|
||||
h4 {
|
||||
margin: 0 0 16px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.file-info-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ebeef5;
|
||||
|
||||
.file-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex-shrink: 0;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.file-details {
|
||||
flex: 1;
|
||||
|
||||
.file-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
|
||||
span {
|
||||
&:not(:last-child)::after {
|
||||
content: '•';
|
||||
margin-left: 8px;
|
||||
color: #dcdfe6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 预览抽屉样式
|
||||
.preview-drawer {
|
||||
::v-deep .el-drawer__header {
|
||||
|
|
|
|||
|
|
@ -111,10 +111,16 @@ public class DatasetDocController {
|
|||
@PostMapping("/upload")
|
||||
public ResultUtils upload(@RequestPart("file") MultipartFile file,
|
||||
@RequestPart("request") DocUploadReq req) throws Exception{
|
||||
//1. 上传文件并解析到dify平台
|
||||
ResponseEntity<Map> documentByFile = difyDatasetApiService.createDocByFile(req, file,"");
|
||||
try {
|
||||
//1. 上传文件并解析到dify平台
|
||||
ResponseEntity<Map> documentByFile = difyDatasetApiService.createDocByFile(req, file,"");
|
||||
}catch (IllegalArgumentException e){
|
||||
return ResultUtils.error(e.getMessage());
|
||||
}catch (Exception e){
|
||||
return ResultUtils.error("上传文件失败");
|
||||
}
|
||||
|
||||
return ResultUtils.success(documentByFile);
|
||||
return ResultUtils.success(null);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue