feat: 知识库树形视图(按来源分组),左侧树导航+右侧列表联动
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
.kt-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.kt-header {
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.kt-tree-wrapper {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
.kt-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 120px;
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 节点文字 */
|
||||
.kt-node-title {
|
||||
font-size: 13px;
|
||||
color: #3b4a3b;
|
||||
}
|
||||
|
||||
/* 全部根节点高亮 */
|
||||
.kt-node-root {
|
||||
font-weight: 600;
|
||||
color: #3b4a3b;
|
||||
}
|
||||
|
||||
/* Tree 选中态 */
|
||||
.kt-container .ant-tree-node-selected .kt-node-title {
|
||||
color: #6b8e6b;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* 知识库树形导航组件(交互层)
|
||||
*
|
||||
* 职责:
|
||||
* - 树的展开/收起/记忆状态
|
||||
* - 节点高亮 + 联动回调
|
||||
* - 「全部展开 / 全部收起」操作按钮
|
||||
*
|
||||
* 数据分组逻辑完全委托给 useKnowledgeGrouping(数据分组层),
|
||||
* 本组件只管交互,不感知分组的业务含义。
|
||||
* 将来切换分组维度时,只改 useKnowledgeGrouping,本组件一行不动。
|
||||
*/
|
||||
|
||||
import { useState, useCallback } from 'react'
|
||||
import { Tree, Button, Space } from 'antd'
|
||||
import { CaretDownFilled, NodeIndexOutlined } from '@ant-design/icons'
|
||||
import './KnowledgeTree.css'
|
||||
|
||||
const { TreeNode } = Tree
|
||||
|
||||
export default function KnowledgeTree({
|
||||
treeData, // 来自 getGroupedItems() API 的原始树数据(含全部节点)
|
||||
onNodeSelect, // 选中节点时回调,参数: { key, type, detail } | null(全部)
|
||||
selectedKey, // 当前选中的 key(用于高亮)
|
||||
}) {
|
||||
const [expandedKeys, setExpandedKeys] = useState(() => {
|
||||
// 默认全部展开
|
||||
if (!treeData || treeData.length === 0) return []
|
||||
const root = treeData[0]
|
||||
return [root.key, ...(root.children || []).map(c => c.key)]
|
||||
})
|
||||
|
||||
// 全部展开
|
||||
const handleExpandAll = useCallback(() => {
|
||||
if (!treeData || treeData.length === 0) return
|
||||
const root = treeData[0]
|
||||
const allKeys = [root.key]
|
||||
for (const child of root.children || []) {
|
||||
allKeys.push(child.key)
|
||||
if (child.children) {
|
||||
for (const grandchild of child.children) {
|
||||
allKeys.push(grandchild.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
setExpandedKeys(allKeys)
|
||||
}, [treeData])
|
||||
|
||||
// 全部收起
|
||||
const handleCollapseAll = useCallback(() => {
|
||||
setExpandedKeys([])
|
||||
}, [])
|
||||
|
||||
// 节点选择
|
||||
const handleSelect = useCallback((selectedKeys, info) => {
|
||||
if (selectedKeys.length === 0) {
|
||||
onNodeSelect(null)
|
||||
return
|
||||
}
|
||||
const key = selectedKeys[0]
|
||||
|
||||
// 解析 key 格式:
|
||||
// "all" → 全部根节点
|
||||
// "manuscript" → 大类节点
|
||||
// "manuscript|具体出处" → 二级节点
|
||||
if (key === 'all') {
|
||||
onNodeSelect(null)
|
||||
return
|
||||
}
|
||||
|
||||
const parts = key.split('|')
|
||||
const type = parts[0]
|
||||
const detail = parts[1] || null
|
||||
onNodeSelect({ key, type, detail })
|
||||
}, [onNodeSelect])
|
||||
|
||||
// 渲染节点
|
||||
const renderNode = (node, isRoot = false) => {
|
||||
return (
|
||||
<TreeNode
|
||||
key={node.key}
|
||||
title={
|
||||
<span className={`kt-node-title ${isRoot ? 'kt-node-root' : ''}`}>
|
||||
{node.label}
|
||||
</span>
|
||||
}
|
||||
icon={isRoot ? <NodeIndexOutlined /> : undefined}
|
||||
>
|
||||
{(node.children || []).map(child => renderNode(child, false))}
|
||||
</TreeNode>
|
||||
)
|
||||
}
|
||||
|
||||
if (!treeData || treeData.length === 0) {
|
||||
return (
|
||||
<div className="kt-empty">
|
||||
暂无知识库条目
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="kt-container">
|
||||
<div className="kt-header">
|
||||
<Space size="small">
|
||||
<Button size="small" onClick={handleExpandAll}>
|
||||
全部展开
|
||||
</Button>
|
||||
<Button size="small" onClick={handleCollapseAll}>
|
||||
全部收起
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
<div className="kt-tree-wrapper">
|
||||
<Tree
|
||||
showIcon
|
||||
showLine={{ showLeafIcon: false }}
|
||||
expandedKeys={expandedKeys}
|
||||
selectedKeys={selectedKey ? [selectedKey] : []}
|
||||
onExpand={(keys) => setExpandedKeys(keys)}
|
||||
onSelect={handleSelect}
|
||||
blockNode
|
||||
switcherIcon={<CaretDownFilled />}
|
||||
>
|
||||
{treeData.map(node => renderNode(node, true))}
|
||||
</Tree>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user