fix: 回退Grid布局恢复flex并列,保留二级分组映射,空大类隐藏
This commit is contained in:
@@ -6,15 +6,20 @@
|
||||
* - 节点高亮 + 联动回调
|
||||
* - 「全部展开 / 全部收起」操作按钮
|
||||
*
|
||||
* 数据分组逻辑完全委托给 useKnowledgeGrouping(数据分组层),
|
||||
* 数据分组逻辑完全委托给后端 get_grouped_items(数据分组层),
|
||||
* 本组件只管交互,不感知分组的业务含义。
|
||||
*
|
||||
* 二级节点 key 格式(由 useKnowledgeGrouping 控制):
|
||||
* - 大类节点: "manuscript"
|
||||
* - 二级节点(新格式): "manuscript|author|左鑫"
|
||||
* type|二级字段名|字段值
|
||||
* 二级节点 key 格式(由后端控制):
|
||||
* - 大类节点: "manuscript"
|
||||
* - 二级节点(新格式): "manuscript|author|左鑫"
|
||||
* type|二级字段名|字段值
|
||||
* - 二级节点(出处,杂志文章): "military_report|source_detail|航空知识..."
|
||||
*
|
||||
* 将来切换分组维度时,只改 useKnowledgeGrouping,本组件只调整 key 解析逻辑(解包字段数)。
|
||||
* 回调给 KnowledgeBase:统一返回 { type, detail }
|
||||
* - 大类节点 → { type: "manuscript", detail: null }
|
||||
* - 作者二级节点 → { type: "manuscript", detail: "左鑫" }
|
||||
* - 出处二级节点 → { type: "military_report", detail: "航空知识..." }
|
||||
* (detail=author名 或 source_detail原文,与 displayedItems 过滤逻辑对齐)
|
||||
*/
|
||||
|
||||
import { useState, useCallback } from 'react'
|
||||
@@ -26,7 +31,7 @@ const { TreeNode } = Tree
|
||||
|
||||
export default function KnowledgeTree({
|
||||
treeData, // 来自 getGroupedItems() API 的原始树数据(含全部节点)
|
||||
onNodeSelect, // 选中节点时回调,参数: { key, type, secondaryField, secondaryValue } | null(全部)
|
||||
onNodeSelect, // 选中节点时回调,参数: { key, type, detail } | null(全部)
|
||||
selectedKey, // 当前选中的 key(用于高亮)
|
||||
}) {
|
||||
const [expandedKeys, setExpandedKeys] = useState(() => {
|
||||
@@ -66,9 +71,10 @@ export default function KnowledgeTree({
|
||||
const key = selectedKeys[0]
|
||||
|
||||
// 解析 key 格式:
|
||||
// "all" → 全部根节点
|
||||
// "manuscript" → 大类节点
|
||||
// "manuscript|author|左鑫" → 二级节点(type|二级字段名|字段值)
|
||||
// "all" → 全部根节点
|
||||
// "manuscript" → 大类节点
|
||||
// "manuscript|author|左鑫" → 二级节点(新格式:type|fieldName|fieldValue)
|
||||
// "military_report|source_detail|..." → 二级节点(出处)
|
||||
if (key === 'all') {
|
||||
onNodeSelect(null)
|
||||
return
|
||||
@@ -77,15 +83,11 @@ export default function KnowledgeTree({
|
||||
const parts = key.split('|')
|
||||
if (parts.length === 1) {
|
||||
// 大类节点
|
||||
onNodeSelect({ key, type: parts[0], secondaryField: null, secondaryValue: null })
|
||||
onNodeSelect({ key, type: parts[0], detail: null })
|
||||
} else {
|
||||
// 二级节点:新格式 type|fieldName|fieldValue
|
||||
onNodeSelect({
|
||||
key,
|
||||
type: parts[0],
|
||||
secondaryField: parts[1],
|
||||
secondaryValue: parts[2],
|
||||
})
|
||||
// 二级节点:parts[0]=type, parts[1]=字段名(废弃不用), parts[2]=字段值
|
||||
// 统一以 parts[2] 作为 detail(作者名 或 出处名),与 displayedItems 过滤逻辑对齐
|
||||
onNodeSelect({ key, type: parts[0], detail: parts[2] })
|
||||
}
|
||||
}, [onNodeSelect])
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Card, Upload, Table, Button, Space, Select, Popconfirm, message, Tag, Row, Col } from 'antd'
|
||||
import { InboxOutlined, DeleteOutlined, ReloadOutlined, UploadOutlined } from '@ant-design/icons'
|
||||
import { Card, Upload, Table, Button, Space, Select, Popconfirm, message, Tag } from 'antd'
|
||||
import { DeleteOutlined, ReloadOutlined, UploadOutlined } from '@ant-design/icons'
|
||||
import useAuthStore from '../../stores/authStore'
|
||||
import knowledgeService from '../../services/knowledgeService'
|
||||
import KnowledgeTree from '../../components/KnowledgeTree/KnowledgeTree'
|
||||
@@ -32,7 +32,7 @@ export default function KnowledgeBase() {
|
||||
const [sources, setSources] = useState([]) // 出处下拉选项
|
||||
const [sourceTypeFilter, setSourceTypeFilter] = useState('')
|
||||
const [sourceDetailFilter, setSourceDetailFilter] = useState('')
|
||||
const [selectedTreeNode, setSelectedTreeNode] = useState(null) // { key, type, secondaryField, secondaryValue }
|
||||
const [selectedTreeNode, setSelectedTreeNode] = useState(null) // { type, detail }
|
||||
|
||||
const fetchItems = async () => {
|
||||
setLoading(true)
|
||||
@@ -110,52 +110,30 @@ export default function KnowledgeBase() {
|
||||
}
|
||||
|
||||
// 树节点选中 → 联动过滤
|
||||
// 回调参数:{ key, type, secondaryField, secondaryValue } | null
|
||||
const handleNodeSelect = (node) => {
|
||||
setSelectedTreeNode(node)
|
||||
}
|
||||
|
||||
// 根据树节点过滤列表
|
||||
// 二级节点 key 格式:type|fieldName|fieldValue
|
||||
// secondaryField === 'source_detail' → 用 item.source_detail 过滤
|
||||
// secondaryField === 'author' → 用 item.author 过滤
|
||||
const displayedItems = (() => {
|
||||
let result = items
|
||||
|
||||
// 树节点过滤优先(覆盖原有 Select 筛选)
|
||||
if (selectedTreeNode) {
|
||||
const { type, secondaryField, secondaryValue } = selectedTreeNode
|
||||
const { type, detail } = selectedTreeNode
|
||||
result = result.filter(i => {
|
||||
if (i.source_type !== type) return false
|
||||
if (secondaryField !== null && secondaryValue !== null) {
|
||||
if (secondaryField === 'source_detail') {
|
||||
if (i.source_detail !== secondaryValue) return false
|
||||
} else {
|
||||
// 其他二级字段(如 author)直接用字段名匹配
|
||||
const itemFieldVal = (i[secondaryField] || '').trim() || null
|
||||
if (itemFieldVal !== secondaryValue) return false
|
||||
}
|
||||
}
|
||||
if (detail !== null && i.source_detail !== detail) return false
|
||||
return true
|
||||
})
|
||||
} else if (sourceDetailFilter) {
|
||||
// 保留原有的 source_detail 筛选逻辑(来自右侧 Select 下拉)
|
||||
// 保留原有的 source_detail 筛选逻辑
|
||||
result = result.filter(i => i.source_detail === sourceDetailFilter)
|
||||
}
|
||||
|
||||
return result
|
||||
})()
|
||||
|
||||
// 构建 selectedKey 供 KnowledgeTree 高亮
|
||||
// 格式:type|field|value(三段,与后端 get_grouped_items 输出的 key 一致)
|
||||
const buildSelectedKey = () => {
|
||||
if (!selectedTreeNode) return 'all'
|
||||
const { type, secondaryField, secondaryValue } = selectedTreeNode
|
||||
if (secondaryField === null || secondaryValue === null) {
|
||||
return type
|
||||
}
|
||||
return `${type}|${secondaryField}|${secondaryValue}`
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '标题',
|
||||
@@ -220,14 +198,14 @@ export default function KnowledgeBase() {
|
||||
]
|
||||
|
||||
return (
|
||||
<div style={{ padding: '12px 16px' }}>
|
||||
<div style={{ maxWidth: 1200, padding: '12px 16px' }}>
|
||||
{/* 标题栏 */}
|
||||
<div style={{ marginBottom: 10 }}>
|
||||
<h2 style={{ margin: 0, fontSize: 20, fontWeight: 600, color: '#3b4a3b' }}>知识库</h2>
|
||||
<p style={{ margin: '4px 0 0', fontSize: 13, color: '#888' }}>上传 md 笔记 · 语义检索 · 报题参考</p>
|
||||
</div>
|
||||
|
||||
{/* 上传区(收小为一行) */}
|
||||
{/* 上传区(收小为一行按钮) */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
|
||||
<Dragger
|
||||
accept=".md"
|
||||
@@ -243,27 +221,24 @@ export default function KnowledgeBase() {
|
||||
<span style={{ fontSize: 12, color: '#aaa' }}>支持批量上传,系统自动解析 yaml frontmatter 并生成语义向量</span>
|
||||
</div>
|
||||
|
||||
{/* 双列对齐布局:左侧树 | 右侧筛选+列表 */}
|
||||
{/* 左列固定 280px,右列撑满,ant-row gutter=12 保持两列间距 */}
|
||||
<Row gutter={12}>
|
||||
{/* 左列:树(固定宽度,min-width 防止收缩) */}
|
||||
<Col span={5} style={{ minWidth: 280 }}>
|
||||
<Card
|
||||
style={{ borderRadius: 14 }}
|
||||
bodyStyle={{ padding: 0 }}
|
||||
>
|
||||
<KnowledgeTree
|
||||
treeData={treeData}
|
||||
onNodeSelect={handleNodeSelect}
|
||||
selectedKey={buildSelectedKey()}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
{/* 主体:左侧树 + 右侧列表(flex 左右并列,宽度稳定) */}
|
||||
<div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
|
||||
{/* 左侧树 */}
|
||||
<Card
|
||||
style={{ borderRadius: 14, width: 280, flexShrink: 0 }}
|
||||
bodyStyle={{ padding: 0, height: '100%' }}
|
||||
>
|
||||
<KnowledgeTree
|
||||
treeData={treeData}
|
||||
onNodeSelect={handleNodeSelect}
|
||||
selectedKey={selectedTreeNode ? `${selectedTreeNode.type}${selectedTreeNode.detail ? '|' + selectedTreeNode.detail : ''}` : 'all'}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 右列:筛选栏 + 列表(两栏独立等高,顶部对齐) */}
|
||||
<Col span={19} style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
{/* 筛选栏(顶部对齐,与左列卡片顶线平齐) */}
|
||||
<Card style={{ borderRadius: 14 }}>
|
||||
{/* 右侧列表 + 筛选栏 */}
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
{/* 筛选栏 */}
|
||||
<Card style={{ borderRadius: 14, marginBottom: 12 }}>
|
||||
<Space size="middle" wrap>
|
||||
<span style={{ fontSize: 14, color: '#666' }}>筛选:</span>
|
||||
<Select
|
||||
@@ -303,8 +278,8 @@ export default function KnowledgeBase() {
|
||||
locale={{ emptyText: '暂无知识库条目,上传 md 文件开始入库' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user