|
@@ -87,6 +87,7 @@
|
|
|
<el-empty v-if="documents.length === 0" description="暂无文档数据" />
|
|
<el-empty v-if="documents.length === 0" description="暂无文档数据" />
|
|
|
<el-table
|
|
<el-table
|
|
|
v-else
|
|
v-else
|
|
|
|
|
+ ref="multipleTableRef"
|
|
|
:data="documents"
|
|
:data="documents"
|
|
|
row-key="id"
|
|
row-key="id"
|
|
|
style="width: 100%"
|
|
style="width: 100%"
|
|
@@ -421,7 +422,7 @@
|
|
|
|
|
|
|
|
<!-- 上传文档对话框 -->
|
|
<!-- 上传文档对话框 -->
|
|
|
<el-dialog v-model="uploadDialogVisible" title="上传文档" width="500px">
|
|
<el-dialog v-model="uploadDialogVisible" title="上传文档" width="500px">
|
|
|
- <el-form :model="uploadForm" :rules="commonRules" ref="uploadFormRef" label-width="120px">
|
|
|
|
|
|
|
+ <el-form :model="uploadForm" :rules="commonRules" ref="uploadFormRef" label-width="120px" v-loading="uploadingFile" element-loading-text="正在上传文件到服务器...">
|
|
|
<el-form-item label="基本信息类型" prop="table_type">
|
|
<el-form-item label="基本信息类型" prop="table_type">
|
|
|
<el-select v-model="uploadForm.table_type" placeholder="请选择基本信息类型">
|
|
<el-select v-model="uploadForm.table_type" placeholder="请选择基本信息类型">
|
|
|
<el-option label="施工标准规范" value="standard" />
|
|
<el-option label="施工标准规范" value="standard" />
|
|
@@ -469,8 +470,8 @@
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-form>
|
|
</el-form>
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
- <el-button @click="uploadDialogVisible = false">取消</el-button>
|
|
|
|
|
- <el-button type="primary" @click="submitUpload" :loading="submitting">确定</el-button>
|
|
|
|
|
|
|
+ <el-button @click="uploadDialogVisible = false" :disabled="uploadingFile">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" @click="submitUpload" :loading="submitting" :disabled="uploadingFile">确定</el-button>
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
@@ -545,9 +546,9 @@
|
|
|
<el-col :span="12" v-if="editForm.table_type === 'standard'">
|
|
<el-col :span="12" v-if="editForm.table_type === 'standard'">
|
|
|
<el-form-item label="* 时效性">
|
|
<el-form-item label="* 时效性">
|
|
|
<el-select v-model="editForm.validity" placeholder="请选择时效性" style="width: 100%">
|
|
<el-select v-model="editForm.validity" placeholder="请选择时效性" style="width: 100%">
|
|
|
- <el-option label="现行" value="现行" />
|
|
|
|
|
- <el-option label="已废止" value="已废止" />
|
|
|
|
|
- <el-option label="被替代" value="被替代" />
|
|
|
|
|
|
|
+ <el-option label="现行" value="XH" />
|
|
|
|
|
+ <el-option label="废止" value="FZ" />
|
|
|
|
|
+ <el-option label="试行" value="SX" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
@@ -733,7 +734,7 @@
|
|
|
<el-descriptions-item label="批准部门">{{ currentDoc?.approving_department || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="批准部门">{{ currentDoc?.approving_department || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="文件类型">{{ currentDoc?.document_type || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="文件类型">{{ currentDoc?.document_type || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="专业领域">{{ currentDoc?.professional_field || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="专业领域">{{ currentDoc?.professional_field || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="时效性">{{ currentDoc?.validity || '-' }}</el-descriptions-item>
|
|
|
|
|
|
|
+ <el-descriptions-item label="时效性">{{ getValidityName(currentDoc?.validity) }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="参编单位">
|
|
<el-descriptions-item label="参编单位">
|
|
|
<div v-if="currentDoc?.participating_units">
|
|
<div v-if="currentDoc?.participating_units">
|
|
|
<div v-for="(unit, idx) in currentDoc.participating_units.split(';')" :key="idx">{{ unit }}</div>
|
|
<div v-for="(unit, idx) in currentDoc.participating_units.split(';')" :key="idx">{{ unit }}</div>
|
|
@@ -830,6 +831,7 @@ import { getKnowledgeBases } from '@/api/knowledge-base'
|
|
|
|
|
|
|
|
// 状态变量
|
|
// 状态变量
|
|
|
const loading = ref(false)
|
|
const loading = ref(false)
|
|
|
|
|
+const uploadingFile = ref(false)
|
|
|
const submitting = ref(false)
|
|
const submitting = ref(false)
|
|
|
const uploadRef = ref<any>(null)
|
|
const uploadRef = ref<any>(null)
|
|
|
const uploadDialogVisible = ref(false)
|
|
const uploadDialogVisible = ref(false)
|
|
@@ -920,6 +922,7 @@ const taskFormRef = ref()
|
|
|
const ingestFormRef = ref()
|
|
const ingestFormRef = ref()
|
|
|
const uploadFormRef = ref()
|
|
const uploadFormRef = ref()
|
|
|
const editFormRef = ref()
|
|
const editFormRef = ref()
|
|
|
|
|
+const multipleTableRef = ref()
|
|
|
|
|
|
|
|
const commonRules = {
|
|
const commonRules = {
|
|
|
kb_id: [
|
|
kb_id: [
|
|
@@ -1118,6 +1121,15 @@ const getFileIconClass = (row: DocumentItem) => {
|
|
|
return `icon-${getFileIcon(row)}`
|
|
return `icon-${getFileIcon(row)}`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const getValidityName = (val: string | null | undefined) => {
|
|
|
|
|
+ const names: Record<string, string> = {
|
|
|
|
|
+ XH: '现行',
|
|
|
|
|
+ FZ: '废止',
|
|
|
|
|
+ SX: '试行'
|
|
|
|
|
+ }
|
|
|
|
|
+ return names[val || ''] || val || '-'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const getSourceTypeName = (sourceType: string | null | undefined) => {
|
|
const getSourceTypeName = (sourceType: string | null | undefined) => {
|
|
|
const names: Record<string, string> = {
|
|
const names: Record<string, string> = {
|
|
|
standard: '施工标准规范',
|
|
standard: '施工标准规范',
|
|
@@ -1177,18 +1189,43 @@ const confirmIngest = async () => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ingesting.value = true
|
|
ingesting.value = true
|
|
|
-
|
|
|
|
|
try {
|
|
try {
|
|
|
- // 1. 临时请求选中 ID 的详情数据,确保校验时数据是最新的且完整的
|
|
|
|
|
- const detailPromises = selectedIds.value.map(id => documentApi.getDetail(id))
|
|
|
|
|
|
|
+ const res = await request.post<ApiResponse>('/api/v1/sample/documents/batch-enter', {
|
|
|
|
|
+ ids: selectedIds.value,
|
|
|
|
|
+ kb_method: ingestForm.kb_method,
|
|
|
|
|
+ chunk_size: ingestForm.chunk_size,
|
|
|
|
|
+ separator: ingestForm.separator,
|
|
|
|
|
+ table_type: searchQuery.table_type
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (res.code === 0) {
|
|
|
|
|
+ ElMessage.success(res.message || '已加入入库队列')
|
|
|
|
|
+ selectedIds.value = []
|
|
|
|
|
+ multipleTableRef.value?.clearSelection()
|
|
|
|
|
+ ingestDialogVisible.value = false
|
|
|
|
|
+ fetchDocuments()
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ if (error !== 'cancel') {
|
|
|
|
|
+ console.error('入库失败:', error)
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ ingesting.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 提取字段检查逻辑
|
|
|
|
|
+const checkDocumentsCompleteness = async (ids: string[]) => {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const detailPromises = ids.map(id => documentApi.getDetail(id))
|
|
|
const detailResults = await Promise.all(detailPromises)
|
|
const detailResults = await Promise.all(detailPromises)
|
|
|
- const selectedDocs = detailResults.map(res => res.data)
|
|
|
|
|
|
|
+ const docs = detailResults.map(res => res.data)
|
|
|
|
|
|
|
|
- // 2. 检查基本信息字段完善度
|
|
|
|
|
let isIncomplete = false
|
|
let isIncomplete = false
|
|
|
let missingFields: string[] = []
|
|
let missingFields: string[] = []
|
|
|
|
|
|
|
|
- for (const doc of selectedDocs) {
|
|
|
|
|
|
|
+ for (const doc of docs) {
|
|
|
const type = doc.source_type || searchQuery.table_type
|
|
const type = doc.source_type || searchQuery.table_type
|
|
|
|
|
|
|
|
if (type === 'standard') {
|
|
if (type === 'standard') {
|
|
@@ -1230,34 +1267,11 @@ const confirmIngest = async () => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- if (isIncomplete && selectedIds.value.length === 1) break // 如果是单个,直接跳出
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const startIngest = async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await request.post<ApiResponse>('/api/v1/sample/documents/batch-enter', {
|
|
|
|
|
- ids: selectedIds.value,
|
|
|
|
|
- kb_method: ingestForm.kb_method,
|
|
|
|
|
- chunk_size: ingestForm.chunk_size,
|
|
|
|
|
- separator: ingestForm.separator,
|
|
|
|
|
- table_type: searchQuery.table_type
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- if (res.code === 0) {
|
|
|
|
|
- ElMessage.success(res.message || '已加入入库队列')
|
|
|
|
|
- selectedIds.value = []
|
|
|
|
|
- ingestDialogVisible.value = false
|
|
|
|
|
- fetchDocuments()
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error: any) {
|
|
|
|
|
- if (error !== 'cancel') {
|
|
|
|
|
- console.error('入库失败:', error)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (isIncomplete && ids.length === 1) break
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (isIncomplete) {
|
|
if (isIncomplete) {
|
|
|
- ingesting.value = false // 弹窗前先结束 loading,方便用户操作
|
|
|
|
|
|
|
+ loading.value = false // 弹出确认框前先关闭 loading
|
|
|
try {
|
|
try {
|
|
|
await ElMessageBox.confirm(
|
|
await ElMessageBox.confirm(
|
|
|
'该文档基本信息其中某几个字段未补充完善,可能导致无法检索的问题,你是否继续?',
|
|
'该文档基本信息其中某几个字段未补充完善,可能导致无法检索的问题,你是否继续?',
|
|
@@ -1268,19 +1282,21 @@ const confirmIngest = async () => {
|
|
|
type: 'warning',
|
|
type: 'warning',
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
- ingesting.value = true // 继续后重新开启 loading
|
|
|
|
|
- await startIngest()
|
|
|
|
|
|
|
+ return true
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
- // 用户取消
|
|
|
|
|
|
|
+ return false
|
|
|
}
|
|
}
|
|
|
- } else {
|
|
|
|
|
- await startIngest()
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+ loading.value = false // 检查完成,关闭 loading
|
|
|
|
|
+ return true
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
- console.error('获取详情或入库失败:', error)
|
|
|
|
|
- ElMessage.error('入库准备失败,请重试')
|
|
|
|
|
|
|
+ loading.value = false // 出错时也关闭 loading
|
|
|
|
|
+ console.error('检查文档完善度失败:', error)
|
|
|
|
|
+ ElMessage.error('检查文档完善度失败,请重试')
|
|
|
|
|
+ return false
|
|
|
} finally {
|
|
} finally {
|
|
|
- ingesting.value = false
|
|
|
|
|
|
|
+ // 确保 loading 在所有路径下都被正确关闭
|
|
|
|
|
+ loading.value = false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1290,8 +1306,12 @@ const handleBatchEnter = async () => {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 不再使用 confirm,改为显示入库设置弹窗
|
|
|
|
|
- ingestForm.kb_method = 'length' // 重置为默认值
|
|
|
|
|
|
|
+ // 先检查字段完善度
|
|
|
|
|
+ const canContinue = await checkDocumentsCompleteness(selectedIds.value)
|
|
|
|
|
+ if (!canContinue) return
|
|
|
|
|
+
|
|
|
|
|
+ // 校验通过后,显示入库设置弹窗
|
|
|
|
|
+ ingestForm.kb_method = 'length'
|
|
|
ingestForm.chunk_size = 500
|
|
ingestForm.chunk_size = 500
|
|
|
ingestForm.separator = '。'
|
|
ingestForm.separator = '。'
|
|
|
|
|
|
|
@@ -1308,14 +1328,21 @@ const handleBatchAddToTask = async () => {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- taskForm.project_name = '' // 重置项目名称
|
|
|
|
|
- taskForm.selectedTags = [] // 重置标签
|
|
|
|
|
|
|
+ // 检查是否有未入库的数据
|
|
|
|
|
+ const unenteredDocs = documents.value.filter(doc => selectedIds.value.includes(doc.id) && !isEntered(doc.whether_to_enter))
|
|
|
|
|
+ if (unenteredDocs.length > 0) {
|
|
|
|
|
+ ElMessage.warning(`选中数据中包含 ${unenteredDocs.length} 份未入库文档,只有已入库的文档可以加入标注任务。`)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 显示任务弹窗(填入项目名称和标签),不再进行字段检查
|
|
|
|
|
+ taskForm.project_name = ''
|
|
|
|
|
+ taskForm.selectedTags = []
|
|
|
tempTagValue.value = null
|
|
tempTagValue.value = null
|
|
|
if (taskFormRef.value) {
|
|
if (taskFormRef.value) {
|
|
|
taskFormRef.value.clearValidate()
|
|
taskFormRef.value.clearValidate()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 获取标签树
|
|
|
|
|
if (tagTree.value.length === 0) {
|
|
if (tagTree.value.length === 0) {
|
|
|
await fetchTagTree()
|
|
await fetchTagTree()
|
|
|
}
|
|
}
|
|
@@ -1340,6 +1367,7 @@ const confirmAddTask = async () => {
|
|
|
if (res.code === 0) {
|
|
if (res.code === 0) {
|
|
|
ElMessage.success(res.message || '操作成功')
|
|
ElMessage.success(res.message || '操作成功')
|
|
|
selectedIds.value = []
|
|
selectedIds.value = []
|
|
|
|
|
+ multipleTableRef.value?.clearSelection()
|
|
|
taskDialogVisible.value = false
|
|
taskDialogVisible.value = false
|
|
|
fetchDocuments()
|
|
fetchDocuments()
|
|
|
} else {
|
|
} else {
|
|
@@ -1356,8 +1384,12 @@ const confirmAddTask = async () => {
|
|
|
const handleSingleEnter = async (doc: DocumentItem | null) => {
|
|
const handleSingleEnter = async (doc: DocumentItem | null) => {
|
|
|
if (!doc) return
|
|
if (!doc) return
|
|
|
|
|
|
|
|
|
|
+ // 先检查字段完善度
|
|
|
|
|
+ const canContinue = await checkDocumentsCompleteness([doc.id])
|
|
|
|
|
+ if (!canContinue) return
|
|
|
|
|
+
|
|
|
selectedIds.value = [doc.id]
|
|
selectedIds.value = [doc.id]
|
|
|
- ingestForm.kb_method = 'length' // 重置为默认值
|
|
|
|
|
|
|
+ ingestForm.kb_method = 'length'
|
|
|
ingestForm.chunk_size = 500
|
|
ingestForm.chunk_size = 500
|
|
|
ingestForm.separator = '。'
|
|
ingestForm.separator = '。'
|
|
|
|
|
|
|
@@ -1426,6 +1458,7 @@ const handleBatchDelete = async () => {
|
|
|
if (res.code === 0) {
|
|
if (res.code === 0) {
|
|
|
ElMessage.success(res.message || '批量删除成功')
|
|
ElMessage.success(res.message || '批量删除成功')
|
|
|
selectedIds.value = []
|
|
selectedIds.value = []
|
|
|
|
|
+ multipleTableRef.value?.clearSelection()
|
|
|
fetchDocuments()
|
|
fetchDocuments()
|
|
|
} else {
|
|
} else {
|
|
|
ElMessage.error(res.message || '批量删除失败')
|
|
ElMessage.error(res.message || '批量删除失败')
|
|
@@ -1471,6 +1504,7 @@ const handleBatchClear = async () => {
|
|
|
if (res.code === 0) {
|
|
if (res.code === 0) {
|
|
|
ElMessage.success(res.message || '数据清空成功')
|
|
ElMessage.success(res.message || '数据清空成功')
|
|
|
selectedIds.value = []
|
|
selectedIds.value = []
|
|
|
|
|
+ multipleTableRef.value?.clearSelection()
|
|
|
fetchDocuments()
|
|
fetchDocuments()
|
|
|
} else {
|
|
} else {
|
|
|
ElMessage.error(res.message || '数据清空失败')
|
|
ElMessage.error(res.message || '数据清空失败')
|
|
@@ -1614,6 +1648,7 @@ const handleExceed = () => {
|
|
|
const customUpload = async (options: any) => {
|
|
const customUpload = async (options: any) => {
|
|
|
const { file, onSuccess, onError } = options
|
|
const { file, onSuccess, onError } = options
|
|
|
try {
|
|
try {
|
|
|
|
|
+ uploadingFile.value = true
|
|
|
// 1. 获取预签名 URL
|
|
// 1. 获取预签名 URL
|
|
|
const res = await documentApi.getUploadUrl(file.name, file.type || 'application/octet-stream', uploadForm.table_type)
|
|
const res = await documentApi.getUploadUrl(file.name, file.type || 'application/octet-stream', uploadForm.table_type)
|
|
|
|
|
|
|
@@ -1643,6 +1678,8 @@ const customUpload = async (options: any) => {
|
|
|
console.error('文件上传失败:', error)
|
|
console.error('文件上传失败:', error)
|
|
|
ElMessage.error(error.message || '文件上传失败')
|
|
ElMessage.error(error.message || '文件上传失败')
|
|
|
onError(error)
|
|
onError(error)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ uploadingFile.value = false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|