TraceStudio-dev/docs/web1.0/FEATURE_TREE_CONTEXTMENU.md
2026-01-07 19:34:45 +08:00

310 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 功能更新:树状分类 + 连线右键菜单
## 更新内容
### 1. ✨ 节点面板多层级树状分类
#### 功能描述
- 支持最多 **3 级树状目录结构**(类似文件树)
- 超出3级的分类会作为 `subCategory` 标签显示在节点下方
- 支持递归展开/折叠,自动统计子节点数量
- 搜索时自动展开所有匹配的分类
#### 实现细节
**文件**[web/src/components/NodePalette.tsx](web/src/components/NodePalette.tsx)
**核心类型定义**
```typescript
interface TreeNode {
name: string
items: Array<{ type: string; meta: PluginMeta; subCategory?: string }> // 叶子节点
children: Map<string, TreeNode> // 子目录
expanded: boolean
}
const MAX_TREE_DEPTH = 3 // 最大树层级
```
**分类示例**
```python
# server/main.py
"CSVLoader": {
"category": "Loader/CSV/hello/world", # 4级分类
# 实际显示:
# Loader (目录)
# └─ CSV (目录)
# └─ hello (目录)
# └─ CSV 数据加载器 (节点)
# 📁 world (subCategory标签超出3级部分)
}
```
**功能特性**
- ✅ 递归构建树结构(`buildTree()`
- ✅ 递归渲染树节点(`renderTreeNode()`
- ✅ 自动计算节点总数(包含所有子节点)
- ✅ 搜索时保持树结构,高亮匹配节点
- ✅ 保留目录展开/折叠状态
---
### 2. 🖱️ 连线右键菜单
#### 功能描述
- 右键点击**连线**可显示操作菜单
- 支持删除单条连线或同一接口的所有连线
- 右键点击**节点**可断开该节点的所有连线
#### 实现细节
**文件**[web/src/components/Workspace.tsx](web/src/components/Workspace.tsx)
**菜单类型扩展**
```typescript
const [contextMenu, setContextMenu] = useState<{
x: number
y: number
type: 'pane' | 'node' | 'edge' // 新增 'edge' 类型
nodeId?: string
edgeId?: string // 新增边 ID
} | null>(null)
```
**事件处理器**
```typescript
// 连线右键菜单
const onEdgeContextMenu = useCallback((event: React.MouseEvent, edge: any) => {
event.preventDefault()
setContextMenu({
x: event.clientX,
y: event.clientY,
type: 'edge',
edgeId: edge.id
})
}, [])
```
**菜单操作**
| 右键对象 | 菜单项 | 功能描述 |
|---------|-------|---------|
| **连线** | ✂️ 删除此连线 | 删除当前选中的连线 |
| **连线** | 🗑️ 删除此接口所有连线 | 删除同一 target handle 的所有连线(用于清理多输入节点) |
| **节点** | ✂️ 断开所有连线 | 删除该节点的所有输入和输出连线 |
| **节点** | 📋 复制节点 | 复制节点到偏移位置 |
| **节点** | 🗑️ 删除节点 | 删除节点及其所有连线 |
| **画布** | 💾 保存工作流 | 保存到 localStorage |
| **画布** | 📥 导入工作流 | 从 JSON 文件导入 |
| **画布** | 📤 导出工作流 | 导出为 JSON 文件 |
| **画布** | 🗑️ 清空画布 | 清空所有节点和连线 |
---
## 使用示例
### 示例1多级树状分类
**后端配置** (`server/main.py`):
```python
"FilterRows": {
"function": "Transform",
"category": "Transform/Filter/Advanced", # 3级分类
}
"CSVLoader": {
"function": "Loader",
"category": "Loader/CSV/Local/File", # 4级分类
# 显示效果:
# Loader
# └─ CSV
# └─ Local
# └─ CSV 数据加载器
# 📁 File (subCategory标签)
}
```
**前端显示**
```
📥 Loader (2)
▸ CSV (1)
▸ Local (1)
📥 CSV 数据加载器
📁 File
▸ Trace (1)
📥 UTrace 文件加载器
⚙️ Transform (4)
▸ Filter (2)
▸ Advanced (1)
⚙️ 行过滤器
⚙️ 时间范围过滤
▸ Select (1)
⚙️ 列选择器
▸ Aggregate (1)
⚙️ 数据聚合
```
### 示例2连线管理
**场景1删除单条连线**
1. 右键点击连线
2. 选择 "✂️ 删除此连线"
3. 该连线被移除
**场景2清理多输入节点**
```
CSVLoader1 ──┐
├──> Aggregator (input-0)
CSVLoader2 ──┘
```
1. 右键点击任意一条连线
2. 选择 "🗑️ 删除此接口所有连线"
3. 两条连线都被移除(因为连接到同一 target handle
**场景3断开节点所有连线**
```
FilterRows ──> SelectColumns ──> Aggregator
```
1. 右键点击 SelectColumns 节点
2. 选择 "✂️ 断开所有连线"
3. SelectColumns 的输入和输出连线都被移除
---
## 技术要点
### 树状分类算法
**关键函数**
```typescript
function buildTree(): TreeNode {
const root: TreeNode = { name: 'root', items: [], children: new Map(), expanded: true }
for (const [nodeType, meta] of Object.entries(plugins)) {
const categoryPath = meta.category || 'Other'
const parts = categoryPath.split('/')
// 限制树的深度,超出部分作为 subCategory
const treeParts = parts.slice(0, MAX_TREE_DEPTH)
const subCategory = parts.length > MAX_TREE_DEPTH
? parts.slice(MAX_TREE_DEPTH).join('/')
: undefined
// 逐级构建树结构
let currentNode = root
for (let i = 0; i < treeParts.length; i++) {
const part = treeParts[i]
if (i === treeParts.length - 1) {
// 最后一级,添加到 items
currentNode.items.push({ type: nodeType, meta, subCategory })
} else {
// 中间层级,创建或获取子节点
if (!currentNode.children.has(part)) {
currentNode.children.set(part, {
name: part,
items: [],
children: new Map(),
expanded: expandedPaths.has(buildPath(treeParts.slice(0, i + 1)))
})
}
currentNode = currentNode.children.get(part)!
}
}
}
return root
}
```
### 连线右键菜单
**React Flow 事件绑定**
```tsx
<ReactFlow
onEdgeContextMenu={onEdgeContextMenu} // 新增
onNodeContextMenu={onNodeContextMenu}
onPaneContextMenu={onPaneContextMenu}
// ...其他配置
/>
```
**删除同一接口所有连线**
```typescript
case 'deleteAll': {
const edge = edges.find(e => e.id === contextMenu.edgeId)
if (edge) {
// 删除同一 target handle 的所有连线
setEdges((eds) => eds.filter((e) =>
!(e.target === edge.target && e.targetHandle === edge.targetHandle)
))
}
break
}
```
---
## 注意事项
1. **树层级限制**
- 当前 `MAX_TREE_DEPTH = 3`
- 超出部分会作为 `subCategory` 标签显示
- 可以在代码中修改此常量来调整限制
2. **搜索行为**
- 搜索时自动展开所有包含匹配节点的目录
- 空目录会被过滤掉
- 节点总数统计包含所有子节点
3. **连线删除**
- "删除此连线" 只删除当前选中的连线
- "删除此接口所有连线" 删除同一 target + targetHandle 的所有连线
- "断开所有连线" 删除节点的所有输入和输出连线
4. **性能优化**
- 树构建使用 Map 数据结构,查找效率高
- 递归渲染时会跳过空节点
- 状态更新使用 React Flow 的原生 hooks
---
## 测试建议
### 测试1树状分类
1. 修改 `server/main.py`,添加多级分类:
```python
"TestNode": {
"category": "A/B/C/D/E" # 5级超出限制
}
```
2. 启动前后端,观察节点面板显示
3. 应该看到:`A > B > C > TestNode` + 标签 `📁 D/E`
### 测试2连线右键菜单
1. 创建 2 个 CSVLoader + 1 个 Aggregator
2. 连接两个 Loader 到 Aggregator多输入
3. 右键点击任意连线
4. 选择 "删除此接口所有连线"
5. 应该看到两条连线都被删除
### 测试3节点断开连线
1. 创建 FilterRows -> SelectColumns -> Aggregator 链
2. 右键点击中间的 SelectColumns 节点
3. 选择 "断开所有连线"
4. 应该看到该节点的输入和输出连线都被移除
---
## 文件变更清单
| 文件 | 变更类型 | 描述 |
|------|---------|------|
| `web/src/components/NodePalette.tsx` | 重构 | 支持多层级树状分类 |
| `web/src/components/Workspace.tsx` | 增强 | 添加连线右键菜单 |
## 兼容性说明
- ✅ 向后兼容:旧的二级分类(如 `Loader/CSV`)仍然正常工作
- ✅ 渐进增强:如果 category 只有一级,也能正常显示
- ✅ 数据安全:所有操作都有确认提示(清空画布等危险操作)