From 5ef1b8ec8d94ed1e4e7a5eb02ba19e43509e614d Mon Sep 17 00:00:00 2001 From: simonkoson <28867558@qq.com> Date: Tue, 26 May 2026 18:52:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=9F=A5=E8=AF=86=E5=BA=93=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=89=8D=E7=AB=AF=E9=A1=B5=E9=9D=A2=EF=BC=8C=E5=90=AB?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0/=E5=88=97=E8=A1=A8/=E7=AD=9B=E9=80=89/?= =?UTF-8?q?=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/KnowledgeBase/KnowledgeBase.jsx | 249 +++++++++++++++++- frontend/src/services/knowledgeService.js | 49 ++++ 2 files changed, 284 insertions(+), 14 deletions(-) create mode 100644 frontend/src/services/knowledgeService.js diff --git a/frontend/src/pages/KnowledgeBase/KnowledgeBase.jsx b/frontend/src/pages/KnowledgeBase/KnowledgeBase.jsx index 53c2552..4a30d2b 100644 --- a/frontend/src/pages/KnowledgeBase/KnowledgeBase.jsx +++ b/frontend/src/pages/KnowledgeBase/KnowledgeBase.jsx @@ -1,17 +1,238 @@ -import { Card } from 'antd' -import { BookOutlined } from '@ant-design/icons' +import { useState, useEffect } from 'react' +import { Card, Upload, Table, Button, Space, Select, Popconfirm, message, Tag } from 'antd' +import { InboxOutlined, DeleteOutlined, ReloadOutlined } from '@ant-design/icons' +import useAuthStore from '../../stores/authStore' +import knowledgeService from '../../services/knowledgeService' -function KnowledgeBase() { - return ( - -
- -

知识库

-

模块 C · Phase 3 实施

- 往期报题单 / 文稿 / 军报语义检索 -
-
- ) +const { Dragger } = Upload + +// source_type 枚举值(固定五类,不写死但供 Select 用) +const SOURCE_TYPE_OPTIONS = [ + { label: '全部类型', value: '' }, + { label: '杂志文章', value: 'military_report' }, + { label: '节目文稿', value: 'manuscript' }, + { label: '报题单', value: 'baoti' }, +] + +// source_type 中文标签 +const SOURCE_TYPE_LABEL = { + military_report: '杂志文章', + manuscript: '节目文稿', + baoti: '报题单', + manual: '其他', } -export default KnowledgeBase \ No newline at end of file +export default function KnowledgeBase() { + const { user } = useAuthStore() + const [items, setItems] = useState([]) + const [sources, setSources] = useState([]) + const [loading, setLoading] = useState(false) + const [uploading, setUploading] = useState(false) + const [sourceTypeFilter, setSourceTypeFilter] = useState('') + const [sourceDetailFilter, setSourceDetailFilter] = useState('') + + const fetchItems = async () => { + setLoading(true) + try { + const data = await knowledgeService.listItems(sourceTypeFilter || null) + setItems(data) + } catch { + message.error('加载知识库失败') + } finally { + setLoading(false) + } + } + + const fetchSources = async () => { + try { + const data = await knowledgeService.listSources() + setSources(data.map(s => ({ label: s.source, value: s.source }))) + } catch { + // ignore + } + } + + useEffect(() => { + fetchItems() + fetchSources() + }, [sourceTypeFilter]) + + // 上传 + const handleUpload = async (file) => { + if (!file.name.endsWith('.md')) { + message.warning('仅支持 .md 文件') + return false + } + setUploading(true) + try { + const result = await knowledgeService.uploadFiles([file]) + const { uploaded, errors } = result + if (uploaded.length > 0) { + message.success(`成功入库 ${uploaded.length} 篇`) + fetchItems() + fetchSources() + } + if (errors.length > 0) { + errors.forEach(e => message.error(`${e.file}: ${e.error}`)) + } + } catch { + message.error('上传失败') + } finally { + setUploading(false) + } + return false // 阻止默认上传行为 + } + + // 删除 + const handleDelete = async (id) => { + try { + await knowledgeService.deleteItem(id) + message.success('删除成功') + fetchItems() + fetchSources() + } catch { + message.error('删除失败') + } + } + + // 列表筛选后过滤 source_detail + const displayedItems = sourceDetailFilter + ? items.filter(i => i.source_detail === sourceDetailFilter) + : items + + const columns = [ + { + title: '标题', + dataIndex: 'title', + key: 'title', + render: (text, record) => ( + {text} + ), + }, + { + title: '作者', + dataIndex: 'author', + key: 'author', + render: (val) => val || null, // 无则不显示 + }, + { + title: '播出/发表时间', + dataIndex: 'publish_date', + key: 'publish_date', + render: (val) => val || null, // 无则不显示 + }, + { + title: '出处', + dataIndex: 'source_detail', + key: 'source_detail', + render: (val) => val || '-', + }, + { + title: '类型', + dataIndex: 'source_type', + key: 'source_type', + render: (val) => ( + + {SOURCE_TYPE_LABEL[val] || val} + + ), + }, + { + title: '入库时间', + dataIndex: 'created_at', + key: 'created_at', + render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-', + }, + { + title: '操作', + key: 'action', + render: (_, record) => ( + handleDelete(record.id)} + okText="确认删除" + cancelText="取消" + placement="left" + > + + + ), + }, + ] + + return ( +
+
+

知识库

+

上传 md 笔记 · 语义检索 · 报题参考

+
+ + {/* 上传区 */} + + +

+ +

+

+ {uploading ? '正在上传并生成向量…' : '点击或拖拽 .md 文件上传'} +

+

+ 支持批量上传,系统自动解析 yaml frontmatter 并生成语义向量 +

+
+
+ + {/* 筛选栏 */} + + + 筛选: + + + + + + {/* 列表 */} + + + + + ) +} \ No newline at end of file diff --git a/frontend/src/services/knowledgeService.js b/frontend/src/services/knowledgeService.js new file mode 100644 index 0000000..84cbd54 --- /dev/null +++ b/frontend/src/services/knowledgeService.js @@ -0,0 +1,49 @@ +/** + * 知识库 API 服务 + */ + +import http from './http' + +const knowledgeService = { + /** + * 上传 md 文件(单个或多个) + * @param {File[]} files - File 对象数组 + */ + async uploadFiles(files) { + const formData = new FormData() + files.forEach(f => formData.append('files', f)) + const resp = await http.post('/knowledge/upload', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }) + return resp.data + }, + + /** + * 获取知识库条目列表 + * @param {string|null} sourceType - 可选,按 source_type 筛选 + */ + async listItems(sourceType = null) { + const params = sourceType ? { source_type: sourceType } : {} + const resp = await http.get('/knowledge/items', { params }) + return resp.data + }, + + /** + * 删除知识库条目 + * @param {number} id + */ + async deleteItem(id) { + const resp = await http.delete(`/knowledge/items/${id}`) + return resp.data + }, + + /** + * 获取所有不重复的具体出处(供筛选下拉) + */ + async listSources() { + const resp = await http.get('/knowledge/sources') + return resp.data + }, +} + +export default knowledgeService \ No newline at end of file