feat: 增加文档替换功能,文档上传成功/失败 消息提示优化
This commit is contained in:
parent
676c76d1c2
commit
fb1237b1d3
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="doc-upload-container">
|
<div class="doc-upload-container">
|
||||||
<!-- 步骤条 -->
|
<!-- 步骤条 -->
|
||||||
<el-steps :active="currentStep" align-center class="upload-steps">
|
<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="解析模式" icon="Setting" />
|
||||||
<el-step :title="getStep3Title()" :icon="getStep3Icon()" />
|
<el-step :title="getStep3Title()" :icon="getStep3Icon()" />
|
||||||
</el-steps>
|
</el-steps>
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
<!-- 第一步:文件选择 -->
|
<!-- 第一步:文件选择 -->
|
||||||
<div v-if="currentStep === 0" class="step-panel file-selection">
|
<div v-if="currentStep === 0" class="step-panel file-selection">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<h3>选择要上传的文件</h3>
|
<h3>{{ props.replaceFileId ? '选择要替换的文件' : '选择要上传的文件' }}</h3>
|
||||||
<p class="panel-description">支持多种文档格式,拖拽或点击选择文件</p>
|
<p class="panel-description">支持多种文档格式,拖拽或点击选择文件</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -22,7 +22,8 @@
|
||||||
drag
|
drag
|
||||||
:auto-upload="false"
|
:auto-upload="false"
|
||||||
:on-change="handleFileChange"
|
:on-change="handleFileChange"
|
||||||
:file-list="uploadFiles"
|
:file-list="[]"
|
||||||
|
:show-file-list="false"
|
||||||
:limit="10"
|
:limit="10"
|
||||||
accept=".txt,.md,.markdown,.mdx,.pdf,.html,.htm,.xlsx,.xls,.doc,.docx,.csv,.vtt,.properties"
|
accept=".txt,.md,.markdown,.mdx,.pdf,.html,.htm,.xlsx,.xls,.doc,.docx,.csv,.vtt,.properties"
|
||||||
class="upload-area"
|
class="upload-area"
|
||||||
|
|
@ -281,7 +282,7 @@
|
||||||
<!-- 自适应模式 - 确认信息 -->
|
<!-- 自适应模式 - 确认信息 -->
|
||||||
<div v-else class="adaptive-confirm">
|
<div v-else class="adaptive-confirm">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<h3>确认上传</h3>
|
<h3>{{ props.replaceFileId ? '确认替换' : '确认上传' }}</h3>
|
||||||
<p class="panel-description">使用自适应模式,系统将自动选择最佳解析策略</p>
|
<p class="panel-description">使用自适应模式,系统将自动选择最佳解析策略</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -321,7 +322,7 @@
|
||||||
:loading="uploading"
|
:loading="uploading"
|
||||||
:disabled="!canUpload()"
|
:disabled="!canUpload()"
|
||||||
>
|
>
|
||||||
开始上传
|
{{ props.replaceFileId ? '开始替换' : '开始上传' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button @click="handleCancel">取消</el-button>
|
<el-button @click="handleCancel">取消</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -343,19 +344,21 @@ import {
|
||||||
Tools,
|
Tools,
|
||||||
Check
|
Check
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import { uploadDocument } from '@/api/dataset'
|
import { uploadDocument, deleteDocument } from '@/api/dataset'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
datasetId: string
|
datasetId: string
|
||||||
visible: boolean
|
visible: boolean
|
||||||
parentId?: number
|
parentId?: number
|
||||||
|
replaceFileId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
datasetId: '',
|
datasetId: '',
|
||||||
visible: false,
|
visible: false,
|
||||||
parentId: undefined
|
parentId: undefined,
|
||||||
|
replaceFileId: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
// Emits
|
// Emits
|
||||||
|
|
@ -435,7 +438,7 @@ const getStep3Title = () => {
|
||||||
switch (uploadForm.analysisStrategyType) {
|
switch (uploadForm.analysisStrategyType) {
|
||||||
case 'custom': return '参数设置'
|
case 'custom': return '参数设置'
|
||||||
case 'deep': return '处理进度'
|
case 'deep': return '处理进度'
|
||||||
default: return '确认上传'
|
default: return props.replaceFileId ? '确认替换' : '确认上传'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -473,6 +476,14 @@ const handleDragLeave = () => {
|
||||||
|
|
||||||
const removeFile = (index: number) => {
|
const removeFile = (index: number) => {
|
||||||
uploadFiles.value.splice(index, 1)
|
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) => {
|
const formatFileSize = (size?: number) => {
|
||||||
|
|
@ -548,18 +559,42 @@ const handleUpload = async () => {
|
||||||
|
|
||||||
const loading = ElLoading.service({
|
const loading = ElLoading.service({
|
||||||
lock: true,
|
lock: true,
|
||||||
text: '正在上传文件...',
|
text: props.replaceFileId ? '正在替换文件...' : '正在上传文件...',
|
||||||
background: 'rgba(0, 0, 0, 0.7)'
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
uploading.value = true
|
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结构
|
// 构造请求数据,符合后端DocUploadReq结构
|
||||||
const requestData: any = {
|
const requestData: any = {
|
||||||
datasetId: props.datasetId,
|
datasetId: props.datasetId,
|
||||||
analysisStrategyType: uploadForm.analysisStrategyType,
|
analysisStrategyType: uploadForm.analysisStrategyType,
|
||||||
parentId: props.parentId // 添加parentId参数,根目录时为undefined/null
|
parentId: props.parentId, // 添加parentId参数,根目录时为undefined/null
|
||||||
|
// 注意:删除 replaceFileId,因为我们已经手动删除了旧文件
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是自定义模式,添加解析策略
|
// 如果是自定义模式,添加解析策略
|
||||||
|
|
@ -599,16 +634,18 @@ const handleUpload = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ElNotification({
|
ElNotification({
|
||||||
title: '上传成功',
|
title: props.replaceFileId ? '替换成功' : '上传成功',
|
||||||
message: `成功上传 ${uploadFiles.value.length} 个文件`,
|
message: props.replaceFileId
|
||||||
|
? `成功替换 ${uploadFiles.value.length} 个文件`
|
||||||
|
: `成功上传 ${uploadFiles.value.length} 个文件`,
|
||||||
type: 'success'
|
type: 'success'
|
||||||
})
|
})
|
||||||
|
|
||||||
emit('success')
|
emit('success')
|
||||||
handleCancel()
|
handleCancel()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('上传失败:', error)
|
console.error(props.replaceFileId ? '替换失败:' : '上传失败:', error)
|
||||||
ElMessage.error('上传失败,请重试')
|
//ElMessage.error(props.replaceFileId ? '替换失败,请重试' : '上传失败,请重试')
|
||||||
} finally {
|
} finally {
|
||||||
uploading.value = false
|
uploading.value = false
|
||||||
loading.close()
|
loading.close()
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@
|
||||||
<span v-else class="folder-indicator">--</span>
|
<span v-else class="folder-indicator">--</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</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 }">
|
<template #default="{ row }">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<!-- 文件操作 -->
|
<!-- 文件操作 -->
|
||||||
|
|
@ -166,6 +166,15 @@
|
||||||
>
|
>
|
||||||
{{t('vabI18n.knowledge.document.buttons.download')}}
|
{{t('vabI18n.knowledge.document.buttons.download')}}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
:icon="Upload"
|
||||||
|
text
|
||||||
|
class="action-btn"
|
||||||
|
@click="handleFileReplaceFromRow(row)"
|
||||||
|
>
|
||||||
|
替换
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<!-- 文件夹操作 -->
|
<!-- 文件夹操作 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
|
@ -235,6 +244,43 @@
|
||||||
/>
|
/>
|
||||||
</el-dialog>
|
</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
|
<el-drawer
|
||||||
v-model="previewDrawerVisible"
|
v-model="previewDrawerVisible"
|
||||||
|
|
@ -467,6 +513,8 @@ const datasetName = ref('');
|
||||||
const previewLoading = ref(true)
|
const previewLoading = ref(true)
|
||||||
|
|
||||||
const uploadDialogVisible = ref(false)
|
const uploadDialogVisible = ref(false)
|
||||||
|
const replaceDialogVisible = ref(false)
|
||||||
|
const replaceTargetFile = ref<FileItem | null>(null)
|
||||||
|
|
||||||
// 重命名相关
|
// 重命名相关
|
||||||
const renameDialogVisible = ref(false)
|
const renameDialogVisible = ref(false)
|
||||||
|
|
@ -518,6 +566,8 @@ name: string;
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
// 清理右键菜单事件监听器
|
// 清理右键菜单事件监听器
|
||||||
document.removeEventListener('click', hideContextMenu)
|
document.removeEventListener('click', hideContextMenu)
|
||||||
|
// 停止自动刷新定时器
|
||||||
|
stopAutoRefresh()
|
||||||
})
|
})
|
||||||
|
|
||||||
const getFileTypeIcon = (fileType: string) => {
|
const getFileTypeIcon = (fileType: string) => {
|
||||||
|
|
@ -766,6 +816,12 @@ const triggerFileInput = () => {
|
||||||
uploadDialogVisible.value = true
|
uploadDialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 文件替换处理 - 从行操作触发
|
||||||
|
const handleFileReplaceFromRow = (row: FileItem) => {
|
||||||
|
replaceTargetFile.value = row
|
||||||
|
replaceDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
const indexMethod = (index: number) => {
|
const indexMethod = (index: number) => {
|
||||||
return (pagination.current - 1) * pagination.size + index + 1
|
return (pagination.current - 1) * pagination.size + index + 1
|
||||||
}
|
}
|
||||||
|
|
@ -776,9 +832,22 @@ return (pagination.current - 1) * pagination.size + index + 1
|
||||||
const handleUploadSuccess = async () => {
|
const handleUploadSuccess = async () => {
|
||||||
uploadDialogVisible.value = false
|
uploadDialogVisible.value = false
|
||||||
await fetchDocuments()
|
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({
|
ElNotification({
|
||||||
title: t('vabI18n.knowledge.document.messages.uploadSuccess'),
|
title: '替换成功',
|
||||||
message: t('vabI18n.knowledge.document.messages.uploadSuccessEnd'),
|
message: '文件替换成功',
|
||||||
type: 'success'
|
type: 'success'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -896,6 +965,28 @@ const handlePreview = async (row: FileItem) => {
|
||||||
// 在script setup部分添加loading状态
|
// 在script setup部分添加loading状态
|
||||||
const tableLoading = ref(false)
|
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函数
|
// 修改fetchDocuments函数
|
||||||
const fetchDocuments = async () => {
|
const fetchDocuments = async () => {
|
||||||
tableLoading.value = true
|
tableLoading.value = true
|
||||||
|
|
@ -988,7 +1079,7 @@ const typeMap: Record<string, 'success' | 'primary' | 'warning' | 'info' | 'dang
|
||||||
'preprocessing': 'info',
|
'preprocessing': 'info',
|
||||||
'indexing': 'warning',
|
'indexing': 'warning',
|
||||||
'waiting': 'info',
|
'waiting': 'info',
|
||||||
'queued': 'primary',
|
'queued': 'info', // 修改为info类型,显示较深灰色
|
||||||
'failed': 'danger',
|
'failed': 'danger',
|
||||||
'completed': 'success'
|
'completed': 'success'
|
||||||
}
|
}
|
||||||
|
|
@ -1044,6 +1135,8 @@ datasetId.value = route.params.id as string
|
||||||
datasetName.value = route.query.name as string
|
datasetName.value = route.query.name as string
|
||||||
// 这里可以调用API获取详细数据
|
// 这里可以调用API获取详细数据
|
||||||
await fetchDocuments()
|
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 {
|
.preview-drawer {
|
||||||
::v-deep .el-drawer__header {
|
::v-deep .el-drawer__header {
|
||||||
|
|
|
||||||
|
|
@ -111,10 +111,16 @@ public class DatasetDocController {
|
||||||
@PostMapping("/upload")
|
@PostMapping("/upload")
|
||||||
public ResultUtils upload(@RequestPart("file") MultipartFile file,
|
public ResultUtils upload(@RequestPart("file") MultipartFile file,
|
||||||
@RequestPart("request") DocUploadReq req) throws Exception{
|
@RequestPart("request") DocUploadReq req) throws Exception{
|
||||||
|
try {
|
||||||
//1. 上传文件并解析到dify平台
|
//1. 上传文件并解析到dify平台
|
||||||
ResponseEntity<Map> documentByFile = difyDatasetApiService.createDocByFile(req, file,"");
|
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