web修改
This commit is contained in:
@@ -9,99 +9,62 @@
|
||||
|
||||
<div class="knowledge-container">
|
||||
<el-card>
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="外部知识库" name="external">
|
||||
<p class="tab-desc">面向客户的知识库内容,包含设备操作指南、故障解决方案等</p>
|
||||
|
||||
<div class="kb-categories">
|
||||
<div v-for="cat in externalCategories" :key="cat.key" class="kb-category-card"
|
||||
:class="{ active: activeExternalCat === cat.key }" @click="activeExternalCat = cat.key">
|
||||
<el-icon :style="{ color: cat.color }"><component :is="cat.icon" /></el-icon>
|
||||
<span class="cat-name">{{ cat.name }}</span>
|
||||
<span class="cat-count">{{ cat.count }} 个文件</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kb-files-section">
|
||||
<div class="section-toolbar">
|
||||
<h3>{{ currentExternalCatName }}</h3>
|
||||
<el-input v-model="externalSearch" placeholder="搜索文件名" style="width: 240px;" :prefix-icon="Search" clearable />
|
||||
</div>
|
||||
|
||||
<el-table :data="filteredExternalFiles" style="width: 100%">
|
||||
<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 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>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="内部知识库" name="internal">
|
||||
<p class="tab-desc">内部技术资料与服务规范,仅供内部员工使用</p>
|
||||
|
||||
<div class="kb-categories">
|
||||
<div v-for="cat in internalCategories" :key="cat.key" class="kb-category-card"
|
||||
:class="{ active: activeInternalCat === cat.key }" @click="activeInternalCat = cat.key">
|
||||
<el-icon :style="{ color: cat.color }"><component :is="cat.icon" /></el-icon>
|
||||
<span class="cat-name">{{ cat.name }}</span>
|
||||
<span class="cat-count">{{ cat.count }} 个文件</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kb-files-section">
|
||||
<div class="section-toolbar">
|
||||
<h3>{{ currentInternalCatName }}</h3>
|
||||
<el-input v-model="internalSearch" placeholder="搜索文件名" style="width: 240px;" :prefix-icon="Search" clearable />
|
||||
</div>
|
||||
|
||||
<el-table :data="filteredInternalFiles" style="width: 100%">
|
||||
<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 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>
|
||||
</el-tab-pane>
|
||||
<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>
|
||||
|
||||
<p class="tab-desc">{{ currentTabDesc }}</p>
|
||||
|
||||
<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>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
@@ -109,125 +72,258 @@
|
||||
<el-dialog v-model="showUploadDialog" title="上传文档" width="500px">
|
||||
<el-form :model="uploadForm" label-width="80px">
|
||||
<el-form-item label="知识库" required>
|
||||
<el-select v-model="uploadForm.kbType" placeholder="请选择知识库">
|
||||
<el-option label="外部知识库" value="external" />
|
||||
<el-option label="内部知识库" value="internal" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类" required>
|
||||
<el-select v-model="uploadForm.category" placeholder="请选择分类">
|
||||
<el-option label="操作指南" value="guide" />
|
||||
<el-option label="故障排查" value="troubleshoot" />
|
||||
<el-option label="技术规范" value="spec" />
|
||||
<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"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="文件" required>
|
||||
<el-upload action="#" :auto-upload="false" drag>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<el-upload
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
:limit="1"
|
||||
drag
|
||||
>
|
||||
<el-icon class="el-icon--upload"><Upload /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
拖拽文件到此或 <em>点击上传</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持 PDF、Word、TXT 等文档格式</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showUploadDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="uploadFile">上传</el-button>
|
||||
<el-button type="primary" @click="handleUpload" :loading="uploading">上传</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import AdminLayout from '@/views/admin/AdminLayout.vue'
|
||||
import { Upload, Search, Document, View, Download, Delete } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
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' }
|
||||
]
|
||||
|
||||
const activeTab = ref('external')
|
||||
const activeExternalCat = ref('guide')
|
||||
const activeInternalCat = ref('spec')
|
||||
const externalSearch = ref('')
|
||||
const internalSearch = ref('')
|
||||
const searchKeyword = ref('')
|
||||
const showUploadDialog = ref(false)
|
||||
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[]>([])
|
||||
|
||||
const uploadForm = ref({
|
||||
kbType: '',
|
||||
category: '',
|
||||
file: null
|
||||
knowledgeId: '',
|
||||
file: null as File | null
|
||||
})
|
||||
|
||||
const externalCategories = ref([
|
||||
{ key: 'guide', name: '操作指南', count: 12, color: '#409eff', icon: 'Document' },
|
||||
{ key: 'troubleshoot', name: '故障排查', count: 8, color: '#67c23a', icon: 'Document' },
|
||||
{ key: 'spec', name: '技术规范', count: 5, color: '#e6a23c', icon: 'Document' }
|
||||
])
|
||||
// 当前 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)
|
||||
|
||||
const internalCategories = ref([
|
||||
{ key: 'spec', name: '技术规范', count: 15, color: '#409eff', icon: 'Document' },
|
||||
{ key: 'process', name: '流程文档', count: 10, color: '#67c23a', icon: 'Document' },
|
||||
{ key: 'training', name: '培训资料', count: 7, color: '#e6a23c', icon: 'Document' }
|
||||
])
|
||||
// 当前 Tab 下的知识库列表
|
||||
const currentKnowledges = computed(() =>
|
||||
knowledges.value.filter((kb: TbKnowledge) => kb.category === activeTab.value)
|
||||
)
|
||||
|
||||
const externalFiles = ref([
|
||||
{ name: 'TH-500GF操作手册.pdf', uploader: '张三', uploadTime: '2024-12-10 14:30', category: 'guide' },
|
||||
{ name: 'TH-300D故障排查指南.pdf', uploader: '李四', uploadTime: '2024-12-09 10:15', category: 'troubleshoot' },
|
||||
{ name: '设备安装规范.pdf', uploader: '王五', uploadTime: '2024-12-08 09:45', category: 'spec' }
|
||||
])
|
||||
|
||||
const internalFiles = ref([
|
||||
{ name: '内部技术规范v2.0.pdf', uploader: '赵六', uploadTime: '2024-12-11 16:20', category: 'spec' },
|
||||
{ name: '售后服务流程.pdf', uploader: '孙七', uploadTime: '2024-12-10 11:00', category: 'process' },
|
||||
{ name: '员工培训手册.pdf', uploader: '周八', uploadTime: '2024-12-09 15:30', category: 'training' }
|
||||
])
|
||||
|
||||
const currentExternalCatName = computed(() => {
|
||||
const cat = externalCategories.value.find(c => c.key === activeExternalCat.value)
|
||||
return cat?.name || '操作指南'
|
||||
// 当前选中的知识库名称
|
||||
const currentKnowledgeName = computed(() => {
|
||||
const kb = knowledges.value.find((k: TbKnowledge) => k.knowledgeId === activeKnowledgeId.value)
|
||||
return kb?.title || '请选择知识库'
|
||||
})
|
||||
|
||||
const currentInternalCatName = computed(() => {
|
||||
const cat = internalCategories.value.find(c => c.key === activeInternalCat.value)
|
||||
return cat?.name || '技术规范'
|
||||
// 搜索过滤后的文档列表
|
||||
const filteredDocuments = computed(() => {
|
||||
if (!searchKeyword.value) return documents.value
|
||||
return documents.value.filter(f =>
|
||||
f.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
const filteredExternalFiles = computed(() => {
|
||||
return externalFiles.value.filter(f => {
|
||||
const matchCat = f.category === activeExternalCat.value
|
||||
const matchSearch = !externalSearch.value || f.name.toLowerCase().includes(externalSearch.value.toLowerCase())
|
||||
return matchCat && matchSearch
|
||||
})
|
||||
// 获取知识库列表
|
||||
const fetchKnowledges = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await aiKnowledgeAPI.listKnowledges({ service: 'workcase' })
|
||||
if (result.success && Array.isArray(result.data)) {
|
||||
knowledges.value = result.data
|
||||
// 默认选中当前 Tab 下的第一个知识库
|
||||
selectFirstKnowledge()
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
||||
// Tab 切换
|
||||
const handleTabChange = () => {
|
||||
searchKeyword.value = ''
|
||||
selectFirstKnowledge()
|
||||
}
|
||||
|
||||
// 选择知识库
|
||||
const selectKnowledge = (knowledgeId: string) => {
|
||||
activeKnowledgeId.value = knowledgeId
|
||||
}
|
||||
|
||||
// 监听知识库选择变化
|
||||
watch(activeKnowledgeId, (newVal) => {
|
||||
if (newVal) fetchDocuments(newVal)
|
||||
})
|
||||
|
||||
const filteredInternalFiles = computed(() => {
|
||||
return internalFiles.value.filter(f => {
|
||||
const matchCat = f.category === activeInternalCat.value
|
||||
const matchSearch = !internalSearch.value || f.name.toLowerCase().includes(internalSearch.value.toLowerCase())
|
||||
return matchCat && matchSearch
|
||||
})
|
||||
})
|
||||
|
||||
const previewFile = (row: any) => {
|
||||
const previewFile = (row: DocumentItem) => {
|
||||
ElMessage.info(`预览文件: ${row.name}`)
|
||||
}
|
||||
|
||||
const downloadFile = (row: any) => {
|
||||
const downloadFile = (row: DocumentItem) => {
|
||||
ElMessage.success(`下载文件: ${row.name}`)
|
||||
}
|
||||
|
||||
const deleteFile = (row: any) => {
|
||||
ElMessage.warning(`删除文件: ${row.name}`)
|
||||
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('删除文件失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const uploadFile = () => {
|
||||
if (!uploadForm.value.kbType || !uploadForm.value.category) {
|
||||
ElMessage.error('请填写必填项')
|
||||
const handleFileChange = (uploadFile: UploadFile) => {
|
||||
uploadForm.value.file = uploadFile.raw || null
|
||||
}
|
||||
|
||||
const handleUpload = async () => {
|
||||
if (!uploadForm.value.knowledgeId) {
|
||||
ElMessage.error('请选择知识库')
|
||||
return
|
||||
}
|
||||
ElMessage.success('文件上传成功')
|
||||
showUploadDialog.value = false
|
||||
uploadForm.value = { kbType: '', category: '', file: null }
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchKnowledges()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
Reference in New Issue
Block a user