知识库文件日志

This commit is contained in:
2025-12-31 16:30:42 +08:00
parent 1bb1dba4d6
commit 8cb8692b84
15 changed files with 636 additions and 76 deletions

View File

@@ -139,7 +139,8 @@ declare module 'shared/types' {
ChatMessageListParam,
SSEMessageData,
SSECallbacks,
SSETask
SSETask,
TbKnowledgeFileLog
} from '../../../shared/src/types/ai'
// 重新导出 menu

View File

@@ -11,49 +11,72 @@
<!-- 筛选区域 -->
<el-card class="filter-card">
<div class="ticket-filters">
<el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" style="width: 280px;" />
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
style="width: 280px;"
@change="handleSearch"
/>
<div class="filter-right">
<el-select v-model="operationFilter" placeholder="操作类型" clearable style="width: 140px;">
<el-select v-model="filter.action" placeholder="操作类型" clearable style="width: 140px;" @change="handleSearch">
<el-option label="上传" value="upload" />
<el-option label="下载" value="download" />
<el-option label="删除" value="delete" />
<el-option label="更新" value="update" />
</el-select>
<el-select v-model="kbTypeFilter" placeholder="知识库类型" clearable style="width: 140px;">
<el-option label="外部知识库" value="external" />
<el-option label="内部知识库" value="internal" />
<el-select v-model="filter.knowledgeId" placeholder="知识库" clearable style="width: 180px;" @change="handleSearch">
<el-option
v-for="kb in knowledgeList"
:key="kb.knowledgeId"
:label="kb.title"
:value="kb.knowledgeId"
/>
</el-select>
<el-input v-model="searchKeyword" placeholder="搜索文件名/操作人" style="width: 200px;" :prefix-icon="Search" clearable />
<el-input
v-model="filter.fileName"
placeholder="搜索文件名"
style="width: 200px;"
:prefix-icon="Search"
clearable
@clear="handleSearch"
@keyup.enter="handleSearch"
/>
</div>
</div>
</el-card>
<!-- 日志列表 -->
<el-card>
<el-table :data="filteredLogs" style="width: 100%">
<el-table-column prop="logId" label="日志ID" width="120">
<el-card v-loading="loading">
<el-table :data="logs" style="width: 100%">
<el-table-column prop="logId" label="日志ID" width="180">
<template #default="{ row }">
<span style="color: #409eff; font-weight: 500;">{{ row.logId }}</span>
</template>
</el-table-column>
<el-table-column prop="fileName" label="文件名" min-width="200" />
<el-table-column prop="operation" label="操作类型" width="100">
<el-table-column prop="fileName" label="文件名" min-width="200" show-overflow-tooltip />
<el-table-column prop="action" label="操作类型" width="100">
<template #default="{ row }">
<el-tag :type="getOperationType(row.operation)" size="small">
{{ row.operationName }}
<el-tag :type="getOperationType(row.action)" size="small">
{{ getOperationName(row.action) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="kbType" label="知识库" width="100">
<el-table-column prop="version" label="版本" width="80">
<template #default="{ row }">
<span>{{ row.kbType === 'external' ? '外部' : '内部' }}</span>
<span>v{{ row.version || 1 }}</span>
</template>
</el-table-column>
<el-table-column prop="operator" label="操作人" width="100" />
<el-table-column prop="operationTime" label="操作时间" width="160" />
<el-table-column prop="fileSize" label="文件大小" width="100" />
<el-table-column label="操作" width="120" fixed="right">
<el-table-column prop="creatorName" label="操作人" width="120" />
<el-table-column prop="createTime" label="操作时间" width="180">
<template #default="{ row }">
{{ formatTime(row.createTime) }}
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ row }">
<el-button type="primary" link size="small" @click="viewDetail(row)">详情</el-button>
</template>
@@ -61,7 +84,15 @@
</el-table>
<div class="table-pagination">
<el-pagination v-model:current-page="currentPage" :page-size="10" :total="logs.length" layout="total, prev, pager, next" />
<el-pagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50]"
:total="pagination.total"
layout="total, sizes, prev, pager, next"
@size-change="loadLogs"
@current-change="loadLogs"
/>
</div>
</el-card>
</div>
@@ -76,19 +107,22 @@
<span>{{ selectedLog.fileName }}</span>
</el-form-item>
<el-form-item label="操作类型">
<el-tag :type="getOperationType(selectedLog.operation)">{{ selectedLog.operationName }}</el-tag>
<el-tag :type="getOperationType(selectedLog.action)">{{ getOperationName(selectedLog.action) }}</el-tag>
</el-form-item>
<el-form-item label="知识库">
<span>{{ selectedLog.kbType === 'external' ? '外部知识库' : '内部知识库' }}</span>
<el-form-item label="文件版本">
<span>v{{ selectedLog.version || 1 }}</span>
</el-form-item>
<el-form-item label="操作人">
<span>{{ selectedLog.operator }}</span>
<span>{{ selectedLog.creatorName }}</span>
</el-form-item>
<el-form-item label="操作时间">
<span>{{ selectedLog.operationTime }}</span>
<span>{{ formatTime(selectedLog.createTime) }}</span>
</el-form-item>
<el-form-item label="文件大小">
<span>{{ selectedLog.fileSize }}</span>
<el-form-item label="知识库ID">
<span>{{ selectedLog.knowledgeId }}</span>
</el-form-item>
<el-form-item label="文件ID">
<span>{{ selectedLog.fileId }}</span>
</el-form-item>
</el-form>
</el-dialog>
@@ -96,63 +130,140 @@
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ref, reactive, onMounted } from 'vue'
import AdminLayout from '@/views/admin/AdminLayout.vue'
import { Download, Search } from 'lucide-vue-next'
import { ElMessage } from 'element-plus'
import { aiKnowledgeAPI } from 'shared/api/ai'
import type { TbKnowledgeFileLog, TbKnowledge } from 'shared/types'
const loading = ref(false)
const dateRange = ref<[Date, Date] | null>(null)
const operationFilter = ref('')
const kbTypeFilter = ref('')
const searchKeyword = ref('')
const currentPage = ref(1)
const showDetailDialog = ref(false)
const selectedLog = ref<any>(null)
const selectedLog = ref<TbKnowledgeFileLog | null>(null)
const logs = ref([
{ logId: 'LOG001', fileName: 'TH-500GF操作手册.pdf', operation: 'upload', operationName: '上传', kbType: 'external', operator: '张三', operationTime: '2024-12-10 14:30', fileSize: '2.5MB' },
{ logId: 'LOG002', fileName: 'TH-300D故障排查指南.pdf', operation: 'upload', operationName: '上传', kbType: 'external', operator: '李四', operationTime: '2024-12-09 10:15', fileSize: '1.8MB' },
{ logId: 'LOG003', fileName: '内部技术规范v2.0.pdf', operation: 'update', operationName: '更新', kbType: 'internal', operator: '赵六', operationTime: '2024-12-11 16:20', fileSize: '3.2MB' },
{ logId: 'LOG004', fileName: '售后服务流程.pdf', operation: 'download', operationName: '下载', kbType: 'internal', operator: '孙七', operationTime: '2024-12-10 11:00', fileSize: '1.1MB' },
{ logId: 'LOG005', fileName: '员工培训手册.pdf', operation: 'delete', operationName: '删除', kbType: 'internal', operator: '周八', operationTime: '2024-12-09 15:30', fileSize: '2.0MB' }
])
const filteredLogs = computed(() => {
let result = logs.value
if (operationFilter.value) {
result = result.filter(l => l.operation === operationFilter.value)
}
if (kbTypeFilter.value) {
result = result.filter(l => l.kbType === kbTypeFilter.value)
}
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase()
result = result.filter(l =>
l.fileName.toLowerCase().includes(keyword) ||
l.operator.toLowerCase().includes(keyword)
)
}
return result.slice((currentPage.value - 1) * 10, currentPage.value * 10)
// 筛选条件
const filter = reactive<TbKnowledgeFileLog>({
action: '',
knowledgeId: '',
fileName: '',
service: 'workcase'
})
const getOperationType = (operation: string) => {
const map: Record<string, string> = {
upload: 'success',
download: 'info',
delete: 'danger',
update: 'warning'
}
return map[operation] || 'info'
// 分页
const pagination = reactive({
page: 1,
pageSize: 10,
total: 0
})
// 日志列表
const logs = ref<TbKnowledgeFileLog[]>([])
// 知识库列表(用于筛选)
const knowledgeList = ref<TbKnowledge[]>([])
// 操作类型映射
const operationTypeMap: Record<string, string> = {
upload: 'success',
download: 'info',
delete: 'danger',
update: 'warning'
}
const viewDetail = (row: any) => {
const operationNameMap: Record<string, string> = {
upload: '上传',
download: '下载',
delete: '删除',
update: '更新'
}
const getOperationType = (action: string) => operationTypeMap[action] || 'info'
const getOperationName = (action: string) => operationNameMap[action] || action
// 格式化时间
const formatTime = (time?: string | number): string => {
if (!time) return '-'
const date = new Date(time)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
}
// 加载知识库列表
const loadKnowledgeList = async () => {
try {
const result = await aiKnowledgeAPI.listKnowledges({})
if (result.success && result.dataList) {
knowledgeList.value = result.dataList
}
} catch (error) {
console.error('加载知识库列表失败:', error)
}
}
// 加载日志列表
const loadLogs = async () => {
loading.value = true
try {
// 构建查询条件
const queryFilter: TbKnowledgeFileLog = { ...filter }
console.log(queryFilter)
// 处理日期范围
if (dateRange.value && dateRange.value[0] && dateRange.value[1]) {
queryFilter.createTimeStart = dateRange.value[0].toISOString()
queryFilter.createTimeEnd = dateRange.value[1].toISOString()
}
const result = await aiKnowledgeAPI.getFileLogPage({
filter: queryFilter,
pageParam: {
page: pagination.page,
pageSize: pagination.pageSize,
total: 0
}
})
if (result.success) {
logs.value = result.dataList || []
pagination.total = result.pageParam?.total || 0
} else {
ElMessage.error(result.message || '加载日志失败')
}
} catch (error) {
console.error('加载日志失败:', error)
ElMessage.error('加载日志失败')
} finally {
loading.value = false
}
}
// 搜索
const handleSearch = () => {
pagination.page = 1
loadLogs()
}
// 查看详情
const viewDetail = (row: TbKnowledgeFileLog) => {
selectedLog.value = row
showDetailDialog.value = true
}
// 导出日志
const exportLogs = () => {
ElMessage.success('日志导出功')
ElMessage.success('日志导出功能开发中')
}
onMounted(() => {
loadKnowledgeList()
loadLogs()
})
</script>
<style lang="scss" scoped>
@@ -163,4 +274,4 @@ const exportLogs = () => {
flex-direction: column;
gap: 16px;
}
</style>
</style>