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