[AC-AISVC-02, AC-AISVC-16] 多个需求合并 #1
|
|
@ -18,10 +18,7 @@ export function uploadDocument(data: FormData) {
|
|||
return request({
|
||||
url: '/admin/kb/documents',
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,37 +4,206 @@
|
|||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>知识库列表</span>
|
||||
<el-button type="primary">上传文档</el-button>
|
||||
<el-button type="primary" @click="handleUploadClick">上传文档</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-table :data="tableData" style="width: 100%">
|
||||
<el-table v-loading="loading" :data="tableData" style="width: 100%">
|
||||
<el-table-column prop="name" label="文件名" />
|
||||
<el-table-column prop="status" label="状态">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === 'completed' ? 'success' : 'warning'">
|
||||
{{ scope.row.status }}
|
||||
<el-tag :type="getStatusType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jobId" label="任务ID" width="120" />
|
||||
<el-table-column prop="createTime" label="上传时间" />
|
||||
<el-table-column label="操作">
|
||||
<template #default>
|
||||
<el-button link type="primary">查看详情</el-button>
|
||||
<el-table-column label="操作" width="180">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handleViewJob(scope.row)">查看详情</el-button>
|
||||
<el-button link type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="jobDialogVisible" title="索引任务详情" width="500px">
|
||||
<el-descriptions :column="1" border v-if="currentJob">
|
||||
<el-descriptions-item label="任务ID">{{ currentJob.jobId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="getStatusType(currentJob.status)">
|
||||
{{ getStatusText(currentJob.status) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="进度">{{ currentJob.progress }}%</el-descriptions-item>
|
||||
<el-descriptions-item label="错误信息" v-if="currentJob.errorMsg">
|
||||
<el-alert type="error" :closable="false">{{ currentJob.errorMsg }}</el-alert>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<el-button @click="jobDialogVisible = false">关闭</el-button>
|
||||
<el-button v-if="currentJob?.status === 'pending' || currentJob?.status === 'processing'" type="primary" @click="refreshJobStatus">
|
||||
刷新状态
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<input ref="fileInput" type="file" style="display: none" @change="handleFileChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { uploadDocument, listDocuments, getIndexJob } from '@/api/kb'
|
||||
|
||||
const tableData = ref([
|
||||
{ name: '产品手册.pdf', status: 'completed', createTime: '2024-02-24 10:00:00' },
|
||||
{ name: '技术文档.docx', status: 'processing', createTime: '2024-02-24 11:30:00' }
|
||||
])
|
||||
interface DocumentItem {
|
||||
name: string
|
||||
status: string
|
||||
jobId: string
|
||||
createTime: string
|
||||
}
|
||||
|
||||
const tableData = ref<DocumentItem[]>([])
|
||||
const loading = ref(false)
|
||||
const jobDialogVisible = ref(false)
|
||||
const currentJob = ref<any>(null)
|
||||
const pollingJobs = ref<Set<string>>(new Set())
|
||||
let pollingInterval: number | null = null
|
||||
|
||||
const getStatusType = (status: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
completed: 'success',
|
||||
processing: 'warning',
|
||||
pending: 'info',
|
||||
failed: 'danger'
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
}
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
completed: '已完成',
|
||||
processing: '处理中',
|
||||
pending: '等待中',
|
||||
failed: '失败'
|
||||
}
|
||||
return textMap[status] || status
|
||||
}
|
||||
|
||||
const fetchDocuments = async () => {
|
||||
try {
|
||||
const res = await listDocuments({})
|
||||
tableData.value = res.data.map((doc: any) => ({
|
||||
name: doc.fileName,
|
||||
status: doc.status,
|
||||
jobId: doc.docId,
|
||||
createTime: new Date(doc.createdAt).toLocaleString('zh-CN')
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch documents:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchJobStatus = async (jobId: string) => {
|
||||
try {
|
||||
const res = await getIndexJob(jobId)
|
||||
return res
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch job status:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const handleViewJob = async (row: DocumentItem) => {
|
||||
if (!row.jobId) {
|
||||
ElMessage.warning('该文档没有任务ID')
|
||||
return
|
||||
}
|
||||
currentJob.value = await fetchJobStatus(row.jobId)
|
||||
jobDialogVisible.value = true
|
||||
}
|
||||
|
||||
const refreshJobStatus = async () => {
|
||||
if (currentJob.value?.jobId) {
|
||||
currentJob.value = await fetchJobStatus(currentJob.value.jobId)
|
||||
}
|
||||
}
|
||||
|
||||
const startPolling = (jobId: string) => {
|
||||
pollingJobs.value.add(jobId)
|
||||
if (!pollingInterval) {
|
||||
pollingInterval = window.setInterval(async () => {
|
||||
for (const jobId of pollingJobs.value) {
|
||||
const job = await fetchJobStatus(jobId)
|
||||
if (job) {
|
||||
if (job.status === 'completed') {
|
||||
pollingJobs.value.delete(jobId)
|
||||
ElMessage.success('文档索引任务已完成')
|
||||
fetchDocuments()
|
||||
} else if (job.status === 'failed') {
|
||||
pollingJobs.value.delete(jobId)
|
||||
ElMessage.error('文档索引任务失败')
|
||||
ElMessage.warning(`错误: ${job.errorMsg}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pollingJobs.value.size === 0 && pollingInterval) {
|
||||
clearInterval(pollingInterval)
|
||||
pollingInterval = null
|
||||
}
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchDocuments()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (pollingInterval) {
|
||||
clearInterval(pollingInterval)
|
||||
}
|
||||
})
|
||||
|
||||
const fileInput = ref<HTMLInputElement | null>(null)
|
||||
|
||||
const handleUploadClick = () => {
|
||||
fileInput.value?.click()
|
||||
}
|
||||
|
||||
const handleFileChange = async (event: Event) => {
|
||||
const target = event.target as HTMLInputElement
|
||||
const file = target.files?.[0]
|
||||
if (!file) return
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('kb_id', 'kb_default')
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await uploadDocument(formData)
|
||||
ElMessage.success(`文档上传成功!任务ID: ${res.jobId}`)
|
||||
console.log('Upload response:', res)
|
||||
|
||||
const newDoc: DocumentItem = {
|
||||
name: file.name,
|
||||
status: res.status || 'pending',
|
||||
jobId: res.jobId,
|
||||
createTime: new Date().toLocaleString('zh-CN')
|
||||
}
|
||||
tableData.value.unshift(newDoc)
|
||||
|
||||
startPolling(res.jobId)
|
||||
} catch (error) {
|
||||
ElMessage.error('文档上传失败')
|
||||
console.error('Upload error:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
target.value = ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
Loading…
Reference in New Issue