Files
urbanLifeline/urbanLifelineWeb/packages/workcase/src/views/admin/knowledge/KnowLedgeView.vue

346 lines
13 KiB
Vue
Raw Normal View History

2025-12-13 15:56:12 +08:00
<template>
2025-12-15 10:47:01 +08:00
<AdminLayout title="知识库管理" info="管理外部和内部知识库文档">
<template #action>
<el-button type="primary" @click="showUploadDialog = true">
<el-icon><Upload /></el-icon>
上传文档
</el-button>
2025-12-13 18:44:28 +08:00
</template>
2025-12-15 10:47:01 +08:00
<div class="knowledge-container">
<el-card>
2025-12-19 17:34:30 +08:00
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<el-tab-pane v-for="tab in tabConfig" :key="tab.name" :label="tab.label" :name="tab.name" />
</el-tabs>
2025-12-15 10:47:01 +08:00
2025-12-19 17:34:30 +08:00
<p class="tab-desc">{{ currentTabDesc }}</p>
2025-12-15 10:47:01 +08:00
2025-12-19 17:34:30 +08:00
<div class="kb-categories">
<div v-for="kb in currentKnowledges" :key="kb.knowledgeId" class="kb-category-card"
:class="{ active: activeKnowledgeId === kb.knowledgeId }" @click="selectKnowledge(kb.knowledgeId || '')">
<el-icon :style="{ color: currentTabColor }"><Document /></el-icon>
<span class="cat-name">{{ kb.title }}</span>
<span class="cat-count">{{ kb.documentCount || 0 }} 个文件</span>
</div>
<el-empty v-if="currentKnowledges.length === 0" :description="'暂无' + currentTabLabel" :image-size="60" />
</div>
<div class="kb-files-section">
<div class="section-toolbar">
<h3>{{ currentKnowledgeName }}</h3>
<el-input v-model="searchKeyword" placeholder="搜索文件名" style="width: 240px;" :prefix-icon="Search" clearable />
</div>
<el-table :data="filteredDocuments" style="width: 100%" v-loading="loading">
<el-table-column prop="name" label="文件名" min-width="280">
<template #default="{ row }">
<div class="file-name-cell">
<el-icon class="file-icon"><Document /></el-icon>
<span>{{ row.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="uploader" label="上传人员" width="120" />
<el-table-column prop="uploadTime" label="上传时间" width="180" />
<el-table-column prop="wordCount" label="字数" width="100" />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.enabled ? 'success' : 'info'" size="small">
{{ row.enabled ? '已启用' : '已禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180" align="center">
<template #default="{ row }">
<el-button type="primary" link size="small" @click="previewFile(row)">
<el-icon><View /></el-icon>预览
</el-button>
<el-button type="success" link size="small" @click="downloadFile(row)">
<el-icon><Download /></el-icon>下载
</el-button>
<el-button type="danger" link size="small" @click="deleteFile(row)">
<el-icon><Delete /></el-icon>删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
2025-12-15 10:47:01 +08:00
</el-card>
</div>
2025-12-13 18:44:28 +08:00
2025-12-15 10:47:01 +08:00
<!-- 上传文档弹窗 -->
<el-dialog v-model="showUploadDialog" title="上传文档" width="500px">
<el-form :model="uploadForm" label-width="80px">
<el-form-item label="知识库" required>
2025-12-19 17:34:30 +08:00
<el-select v-model="uploadForm.knowledgeId" placeholder="请选择知识库" style="width: 100%">
<el-option
v-for="kb in knowledges"
:key="kb.knowledgeId"
:label="kb.title"
:value="kb.knowledgeId"
/>
2025-12-15 10:47:01 +08:00
</el-select>
</el-form-item>
<el-form-item label="文件" required>
2025-12-19 17:34:30 +08:00
<el-upload
action="#"
:auto-upload="false"
:on-change="handleFileChange"
:limit="1"
drag
>
<el-icon class="el-icon--upload"><Upload /></el-icon>
2025-12-15 10:47:01 +08:00
<div class="el-upload__text">
拖拽文件到此或 <em>点击上传</em>
</div>
2025-12-19 17:34:30 +08:00
<template #tip>
<div class="el-upload__tip">支持 PDFWordTXT 等文档格式</div>
</template>
2025-12-15 10:47:01 +08:00
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showUploadDialog = false">取消</el-button>
2025-12-19 17:34:30 +08:00
<el-button type="primary" @click="handleUpload" :loading="uploading">上传</el-button>
2025-12-15 10:47:01 +08:00
</template>
</el-dialog>
2025-12-13 18:44:28 +08:00
</AdminLayout>
2025-12-13 15:56:12 +08:00
</template>
2025-12-15 10:47:01 +08:00
2025-12-13 15:56:12 +08:00
<script setup lang="ts">
2025-12-19 17:34:30 +08:00
import { ref, computed, onMounted, watch } from 'vue'
2025-12-15 10:47:01 +08:00
import AdminLayout from '@/views/admin/AdminLayout.vue'
import { Upload, Search, Document, View, Download, Delete } from '@element-plus/icons-vue'
2025-12-19 17:34:30 +08:00
import { ElMessage, ElMessageBox, type UploadFile } from 'element-plus'
import { aiKnowledgeAPI } from 'shared/api/ai'
import type { TbKnowledge } from 'shared/types'
// Tab 配置
const tabConfig = [
{ name: 'external', label: '外部知识库', desc: '面向客户的知识库内容,包含设备操作指南、故障解决方案等', color: '#409eff' },
{ name: 'internal', label: '内部知识库', desc: '内部技术资料与服务规范,仅供内部员工使用', color: '#67c23a' }
]
2025-12-15 10:47:01 +08:00
const activeTab = ref('external')
2025-12-19 17:34:30 +08:00
const searchKeyword = ref('')
2025-12-15 10:47:01 +08:00
const showUploadDialog = ref(false)
2025-12-19 17:34:30 +08:00
const loading = ref(false)
const uploading = ref(false)
// 知识库列表
const knowledges = ref<TbKnowledge[]>([])
const activeKnowledgeId = ref('')
// 文档列表
interface DocumentItem {
id: string
name: string
uploader: string
uploadTime: string
position: number
dataSourceType: string
wordCount: number
hitCount: number
indexingStatus: string
enabled: boolean
}
const documents = ref<DocumentItem[]>([])
2025-12-15 10:47:01 +08:00
const uploadForm = ref({
2025-12-19 17:34:30 +08:00
knowledgeId: '',
file: null as File | null
2025-12-15 10:47:01 +08:00
})
2025-12-13 18:44:28 +08:00
2025-12-19 17:34:30 +08:00
// 当前 Tab 配置
const currentTabConfig = computed(() => tabConfig.find(t => t.name === activeTab.value) || tabConfig[0])
const currentTabDesc = computed(() => currentTabConfig.value.desc)
const currentTabLabel = computed(() => currentTabConfig.value.label)
const currentTabColor = computed(() => currentTabConfig.value.color)
2025-12-15 10:47:01 +08:00
2025-12-20 12:03:26 +08:00
// 当前 Tab 下的知识库列表(直接使用查询结果,不再前端过滤)
const currentKnowledges = computed(() => knowledges.value)
2025-12-19 17:34:30 +08:00
// 当前选中的知识库名称
const currentKnowledgeName = computed(() => {
const kb = knowledges.value.find((k: TbKnowledge) => k.knowledgeId === activeKnowledgeId.value)
return kb?.title || '请选择知识库'
2025-12-15 10:47:01 +08:00
})
2025-12-19 17:34:30 +08:00
// 搜索过滤后的文档列表
const filteredDocuments = computed(() => {
if (!searchKeyword.value) return documents.value
return documents.value.filter(f =>
f.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
)
2025-12-15 10:47:01 +08:00
})
2025-12-20 12:03:26 +08:00
// 获取知识库列表(根据当前 Tab 的 category 查询)
2025-12-19 17:34:30 +08:00
const fetchKnowledges = async () => {
loading.value = true
try {
2025-12-20 12:03:26 +08:00
const result = await aiKnowledgeAPI.listKnowledges({
service: 'workcase',
category: activeTab.value
})
console.log('知识库列表响应:', result)
// API 返回的是 dataList 字段
knowledges.value = result.dataList || []
selectFirstKnowledge()
2025-12-19 17:34:30 +08:00
} catch (error) {
console.error('获取知识库列表失败:', error)
ElMessage.error('获取知识库列表失败')
} finally {
loading.value = false
}
}
// 选中当前 Tab 下的第一个知识库
const selectFirstKnowledge = () => {
const firstKb = currentKnowledges.value[0]
if (firstKb?.knowledgeId) {
activeKnowledgeId.value = firstKb.knowledgeId
} else {
activeKnowledgeId.value = ''
documents.value = []
}
}
// 获取文档列表
const fetchDocuments = async (knowledgeId: string) => {
if (!knowledgeId) {
documents.value = []
return
}
loading.value = true
try {
const result = await aiKnowledgeAPI.getDocumentList(knowledgeId, 1, 100)
if (result.success && result.data) {
documents.value = (result.data.data || []).map((doc: any) => ({
id: doc.id,
name: doc.name,
uploader: doc.created_by || '-',
uploadTime: doc.created_at ? new Date(doc.created_at * 1000).toLocaleString() : '-',
position: doc.position,
dataSourceType: doc.data_source_type,
wordCount: doc.word_count || 0,
hitCount: doc.hit_count || 0,
indexingStatus: doc.indexing_status,
enabled: doc.enabled
}))
}
} catch (error) {
console.error('获取文档列表失败:', error)
ElMessage.error('获取文档列表失败')
} finally {
loading.value = false
}
}
2025-12-20 12:03:26 +08:00
// Tab 切换时重新查询对应类别的知识库
2025-12-19 17:34:30 +08:00
const handleTabChange = () => {
searchKeyword.value = ''
2025-12-20 12:03:26 +08:00
knowledges.value = []
activeKnowledgeId.value = ''
documents.value = []
fetchKnowledges()
2025-12-19 17:34:30 +08:00
}
// 选择知识库
const selectKnowledge = (knowledgeId: string) => {
activeKnowledgeId.value = knowledgeId
}
// 监听知识库选择变化
watch(activeKnowledgeId, (newVal) => {
if (newVal) fetchDocuments(newVal)
2025-12-15 10:47:01 +08:00
})
2025-12-19 17:34:30 +08:00
const previewFile = (row: DocumentItem) => {
2025-12-15 10:47:01 +08:00
ElMessage.info(`预览文件: ${row.name}`)
}
2025-12-19 17:34:30 +08:00
const downloadFile = (row: DocumentItem) => {
2025-12-15 10:47:01 +08:00
ElMessage.success(`下载文件: ${row.name}`)
}
2025-12-19 17:34:30 +08:00
const deleteFile = async (row: DocumentItem) => {
try {
await ElMessageBox.confirm(`确定要删除文件 "${row.name}" 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
const result = await aiKnowledgeAPI.deleteFile(row.id)
if (result.success) {
ElMessage.success('删除成功')
fetchDocuments(activeKnowledgeId.value)
} else {
ElMessage.error(result.message || '删除失败')
}
} catch (error) {
if (error !== 'cancel') {
console.error('删除文件失败:', error)
ElMessage.error('删除文件失败')
}
}
2025-12-15 10:47:01 +08:00
}
2025-12-19 17:34:30 +08:00
const handleFileChange = (uploadFile: UploadFile) => {
uploadForm.value.file = uploadFile.raw || null
}
const handleUpload = async () => {
if (!uploadForm.value.knowledgeId) {
ElMessage.error('请选择知识库')
2025-12-15 10:47:01 +08:00
return
}
2025-12-19 17:34:30 +08:00
if (!uploadForm.value.file) {
ElMessage.error('请选择要上传的文件')
return
}
const targetKnowledgeId = uploadForm.value.knowledgeId
uploading.value = true
try {
const result = await aiKnowledgeAPI.uploadToKnowledge(
uploadForm.value.file,
targetKnowledgeId
)
if (result.success) {
ElMessage.success('文件上传成功')
showUploadDialog.value = false
uploadForm.value = { knowledgeId: '', file: null }
fetchKnowledges()
if (targetKnowledgeId === activeKnowledgeId.value) {
fetchDocuments(activeKnowledgeId.value)
}
} else {
ElMessage.error(result.message || '上传失败')
}
} catch (error) {
console.error('上传文件失败:', error)
ElMessage.error('上传文件失败')
} finally {
uploading.value = false
}
2025-12-15 10:47:01 +08:00
}
2025-12-19 17:34:30 +08:00
onMounted(() => {
fetchKnowledges()
})
2025-12-13 15:56:12 +08:00
</script>
2025-12-15 10:47:01 +08:00
2025-12-13 15:56:12 +08:00
<style lang="scss" scoped>
2025-12-15 10:47:01 +08:00
@import url("./KnowLedgeView.scss");
.knowledge-container {
display: flex;
flex-direction: column;
gap: 16px;
}
.tab-desc {
color: #909399;
font-size: 13px;
margin-bottom: 12px;
}
2025-12-13 15:56:12 +08:00
</style>