442 lines
11 KiB
Markdown
442 lines
11 KiB
Markdown
|
|
# Phase 2 极致体验优化 - 完成报告
|
|||
|
|
|
|||
|
|
## 🎯 优化概览
|
|||
|
|
|
|||
|
|
本次更新实现了 TraceStudio 第二阶段的最终抛光,从基础交互到极致体验的全面提升。
|
|||
|
|
|
|||
|
|
## ✅ 完成的核心功能
|
|||
|
|
|
|||
|
|
### 1. Inspector 深度修复 ✅
|
|||
|
|
|
|||
|
|
**双向绑定机制**:
|
|||
|
|
- ✨ 参数修改实时同步到 Zustand Store
|
|||
|
|
- 💾 修改后切换节点不会丢失数据
|
|||
|
|
- 🔄 通过 `updateNodeData(nodeId, { key: value })` 即时更新
|
|||
|
|
|
|||
|
|
**只读属性分离**:
|
|||
|
|
```
|
|||
|
|
┌─ 系统信息(只读)────┐
|
|||
|
|
│ 节点 ID: n_xxx │ ← 灰色背景,禁用输入
|
|||
|
|
│ 节点类型: universal │
|
|||
|
|
│ 分类: Loader │
|
|||
|
|
├─ 参数配置(可编辑)──┤
|
|||
|
|
│ file_path: [输入框] │ ← 白色背景,可编辑
|
|||
|
|
│ delimiter: [输入框] │
|
|||
|
|
│ (CSV 文件路径) │ ← 描述文字
|
|||
|
|
└────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**智能类型处理**:
|
|||
|
|
- `integer` 类型 → `<input type="number">`
|
|||
|
|
- `string` 类型 → `<input type="text">`
|
|||
|
|
- 支持 `default` 值和 `description` 提示
|
|||
|
|
|
|||
|
|
### 2. 自定义右键菜单 ✅
|
|||
|
|
|
|||
|
|
**画布右键(空白处)**:
|
|||
|
|
```
|
|||
|
|
┌────────────────┐
|
|||
|
|
│ 💾 保存工作流 │ → localStorage
|
|||
|
|
│ 📥 导入工作流 │ → 文件选择器
|
|||
|
|
│ 📤 导出工作流 │ → JSON 下载
|
|||
|
|
│ ───────────── │
|
|||
|
|
│ 🗑️ 清空画布 │ → 确认对话框
|
|||
|
|
└────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**节点右键**:
|
|||
|
|
```
|
|||
|
|
┌────────────────┐
|
|||
|
|
│ 📋 复制节点 │ → 偏移 50px
|
|||
|
|
│ ───────────── │
|
|||
|
|
│ 🗑️ 删除节点 │ → 同时删除连线
|
|||
|
|
└────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**视觉设计**:
|
|||
|
|
- 毛玻璃背景 (`backdrop-filter: blur(12px)`)
|
|||
|
|
- 蓝色边框 (`rgba(59,130,246,0.2)`)
|
|||
|
|
- Hover 高亮(蓝色/红色)
|
|||
|
|
- 危险操作红色标记
|
|||
|
|
|
|||
|
|
### 3. 贝塞尔连线优化 ✅
|
|||
|
|
|
|||
|
|
**连线样式升级**:
|
|||
|
|
- 类型:`smoothstep` → `default` (贝塞尔曲线)
|
|||
|
|
- 粗细:2px → 3px(静态)/ 4px(运行时)
|
|||
|
|
- 透明度:0.7 → 0.8(静态)/ 1.0(运行时)
|
|||
|
|
|
|||
|
|
**单输入顶替逻辑**:
|
|||
|
|
```typescript
|
|||
|
|
onConnect: (connection) => {
|
|||
|
|
// 删除目标 Handle 的旧连线
|
|||
|
|
const filtered = edges.filter(e =>
|
|||
|
|
!(e.target === connection.target &&
|
|||
|
|
e.targetHandle === connection.targetHandle)
|
|||
|
|
)
|
|||
|
|
// 添加新连线
|
|||
|
|
addEdge({ ...connection, style: {...} }, filtered)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**效果**:
|
|||
|
|
- 每个输入口只保留一条连线
|
|||
|
|
- 新连线自动替换旧连线
|
|||
|
|
- 符合 DAG 拓扑约束
|
|||
|
|
|
|||
|
|
### 4. 节点运行态视觉反馈 ✅
|
|||
|
|
|
|||
|
|
**状态指示灯系统**:
|
|||
|
|
| 状态 | 颜色 | 动画 | 说明 |
|
|||
|
|
|------|------|------|------|
|
|||
|
|
| idle | 灰色 | 无 | 未执行 |
|
|||
|
|
| running | 黄色 | 闪烁脉冲 | 执行中 |
|
|||
|
|
| success | 绿色 | 无 | 执行成功 |
|
|||
|
|
| error | 红色 | 闪烁脉冲 | 执行失败 |
|
|||
|
|
|
|||
|
|
**CSS 动画**:
|
|||
|
|
```css
|
|||
|
|
@keyframes pulse {
|
|||
|
|
0%, 100% { opacity: 1; transform: scale(1); }
|
|||
|
|
50% { opacity: 0.7; transform: scale(1.2); }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**执行耗时显示**:
|
|||
|
|
- 位置:节点底部右侧
|
|||
|
|
- 格式:`12ms`(单色编码)
|
|||
|
|
- 颜色规则:
|
|||
|
|
- `< 50ms` → 绿色(快速)
|
|||
|
|
- `50-100ms` → 橙色(适中)
|
|||
|
|
- `> 100ms` → 红色(慢)
|
|||
|
|
|
|||
|
|
**数据结构**:
|
|||
|
|
```typescript
|
|||
|
|
data: {
|
|||
|
|
status: 'idle' | 'running' | 'success' | 'error'
|
|||
|
|
executionTime: 42 // 毫秒
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📊 功能对比表
|
|||
|
|
|
|||
|
|
| 功能 | Phase 2 初版 | Phase 2 Final |
|
|||
|
|
|------|-------------|--------------|
|
|||
|
|
| 参数持久化 | ❌ 切换丢失 | ✅ 实时同步 |
|
|||
|
|
| 右键菜单 | ⚠️ 简单确认 | ✅ 完整菜单 |
|
|||
|
|
| 连线类型 | smoothstep | 贝塞尔曲线 |
|
|||
|
|
| 连线粗细 | 2px | 3px → 4px |
|
|||
|
|
| 单输入约束 | ❌ 无 | ✅ 自动顶替 |
|
|||
|
|
| 状态反馈 | ⚠️ 基础 | ✅ 4 态 + 动画 |
|
|||
|
|
| 执行耗时 | ❌ 无 | ✅ 彩色显示 |
|
|||
|
|
|
|||
|
|
## 🎨 交互流程优化
|
|||
|
|
|
|||
|
|
### 工作流保存/加载
|
|||
|
|
|
|||
|
|
**保存流程**:
|
|||
|
|
1. 画布空白处右键 → 选择"保存工作流"
|
|||
|
|
2. 数据存入 `localStorage['tracestudio.workflow']`
|
|||
|
|
3. 弹出提示"✅ 工作流已保存到本地存储"
|
|||
|
|
|
|||
|
|
**导出流程**:
|
|||
|
|
1. 画布空白处右键 → 选择"导出工作流"
|
|||
|
|
2. 生成 JSON 文件:`workflow_{timestamp}.json`
|
|||
|
|
3. 自动触发浏览器下载
|
|||
|
|
|
|||
|
|
**导入流程**:
|
|||
|
|
1. 画布空白处右键 → 选择"导入工作流"
|
|||
|
|
2. 打开文件选择器(.json)
|
|||
|
|
3. 解析并验证 JSON 结构
|
|||
|
|
4. 成功 → 替换当前画布,失败 → 提示错误
|
|||
|
|
|
|||
|
|
### 节点复制流程
|
|||
|
|
|
|||
|
|
1. 右键目标节点 → 选择"复制节点"
|
|||
|
|
2. 创建新节点:
|
|||
|
|
- ID:`n_{timestamp}_{random}`
|
|||
|
|
- 位置:原位置 + (50, 50)
|
|||
|
|
- 数据:完全克隆
|
|||
|
|
3. 添加到画布
|
|||
|
|
|
|||
|
|
### 参数编辑流程
|
|||
|
|
|
|||
|
|
1. 点击节点 → Inspector 显示详情
|
|||
|
|
2. 修改参数值 → `onChange` 触发
|
|||
|
|
3. 调用 `updateNodeData(nodeId, { param: value })`
|
|||
|
|
4. Zustand Store 更新
|
|||
|
|
5. 节点体实时刷新预览
|
|||
|
|
|
|||
|
|
## 🔧 技术实现细节
|
|||
|
|
|
|||
|
|
### Inspector 参数渲染逻辑
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 遍历 param_schema
|
|||
|
|
{Object.entries(node.data.meta.param_schema).map(([key, schema]) => {
|
|||
|
|
const currentValue = node.data[key] ?? schema.default
|
|||
|
|
|
|||
|
|
return schema.type === 'integer' ? (
|
|||
|
|
<input type="number"
|
|||
|
|
value={currentValue}
|
|||
|
|
onChange={(e) => updateNodeData(node.id, {
|
|||
|
|
[key]: parseInt(e.target.value) || 0
|
|||
|
|
})} />
|
|||
|
|
) : (
|
|||
|
|
<input type="text"
|
|||
|
|
value={currentValue}
|
|||
|
|
onChange={(e) => updateNodeData(node.id, {
|
|||
|
|
[key]: e.target.value
|
|||
|
|
})} />
|
|||
|
|
)
|
|||
|
|
})}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 右键菜单状态管理
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const [contextMenu, setContextMenu] = useState<{
|
|||
|
|
x: number
|
|||
|
|
y: number
|
|||
|
|
type: 'pane' | 'node'
|
|||
|
|
nodeId?: string
|
|||
|
|
} | null>(null)
|
|||
|
|
|
|||
|
|
// 画布右键
|
|||
|
|
onPaneContextMenu={(e) => {
|
|||
|
|
e.preventDefault()
|
|||
|
|
setContextMenu({ x: e.clientX, y: e.clientY, type: 'pane' })
|
|||
|
|
}}
|
|||
|
|
|
|||
|
|
// 节点右键
|
|||
|
|
onNodeContextMenu={(e, node) => {
|
|||
|
|
e.preventDefault()
|
|||
|
|
setContextMenu({
|
|||
|
|
x: e.clientX,
|
|||
|
|
y: e.clientY,
|
|||
|
|
type: 'node',
|
|||
|
|
nodeId: node.id
|
|||
|
|
})
|
|||
|
|
}}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 单输入顶替算法
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
setEdges((eds) => {
|
|||
|
|
// 过滤:删除目标 Handle 的旧连线
|
|||
|
|
const filtered = eds.filter((e) => {
|
|||
|
|
if (e.target === connection.target &&
|
|||
|
|
e.targetHandle === connection.targetHandle) {
|
|||
|
|
return false // 删除旧连线
|
|||
|
|
}
|
|||
|
|
return true
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 添加新连线
|
|||
|
|
return addEdge({
|
|||
|
|
...connection,
|
|||
|
|
style: { stroke: edgeColor, strokeWidth: 3 }
|
|||
|
|
}, filtered)
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 运行态动画触发
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// UniversalNode 中
|
|||
|
|
const statusConfig = {
|
|||
|
|
running: { color: '#f59e0b', animated: true },
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
<div style={{
|
|||
|
|
background: currentStatus.color,
|
|||
|
|
boxShadow: currentStatus.animated
|
|||
|
|
? `0 0 12px ${currentStatus.color}`
|
|||
|
|
: 'none',
|
|||
|
|
animation: currentStatus.animated
|
|||
|
|
? 'pulse 1.5s ease-in-out infinite'
|
|||
|
|
: 'none'
|
|||
|
|
}} />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🧪 测试清单
|
|||
|
|
|
|||
|
|
### Inspector 测试
|
|||
|
|
|
|||
|
|
- [x] 修改参数后切换节点,再切回来值仍保留
|
|||
|
|
- [x] 系统属性(ID/Type/Category)无法编辑
|
|||
|
|
- [x] integer 类型使用数字输入框
|
|||
|
|
- [x] string 类型使用文本输入框
|
|||
|
|
- [x] placeholder 显示 default 值
|
|||
|
|
|
|||
|
|
### 右键菜单测试
|
|||
|
|
|
|||
|
|
- [x] 画布空白处右键显示 4 个选项
|
|||
|
|
- [x] 节点上右键显示 2 个选项
|
|||
|
|
- [x] "保存工作流"存入 localStorage
|
|||
|
|
- [x] "导出工作流"下载 JSON 文件
|
|||
|
|
- [x] "导入工作流"加载 JSON 文件
|
|||
|
|
- [x] "清空画布"删除所有节点和连线
|
|||
|
|
- [x] "复制节点"创建偏移副本
|
|||
|
|
- [x] "删除节点"同时删除关联连线
|
|||
|
|
|
|||
|
|
### 连线测试
|
|||
|
|
|
|||
|
|
- [x] 新连线使用贝塞尔曲线
|
|||
|
|
- [x] 静态连线粗细 3px
|
|||
|
|
- [x] 运行时连线粗细 4px
|
|||
|
|
- [x] 单输入口只保留最新连线
|
|||
|
|
- [x] 旧连线被新连线顶替
|
|||
|
|
|
|||
|
|
### 运行态测试
|
|||
|
|
|
|||
|
|
- [x] idle 状态显示灰色指示灯
|
|||
|
|
- [x] running 状态显示黄色闪烁
|
|||
|
|
- [x] success 状态显示绿色常亮
|
|||
|
|
- [x] error 状态显示红色闪烁
|
|||
|
|
- [x] 执行耗时显示在节点底部
|
|||
|
|
- [x] 耗时颜色根据阈值变化
|
|||
|
|
|
|||
|
|
## 📁 文件变更清单
|
|||
|
|
|
|||
|
|
**修改文件**:
|
|||
|
|
1. `web/src/components/Inspector.tsx` - 双向绑定 + 只读分离
|
|||
|
|
2. `web/src/components/Workspace.tsx` - 右键菜单 + 连线优化
|
|||
|
|
3. `web/src/components/nodes/UniversalNode.tsx` - 运行态反馈
|
|||
|
|
|
|||
|
|
**核心改动统计**:
|
|||
|
|
- Inspector: +150 lines(参数系统重构)
|
|||
|
|
- Workspace: +120 lines(右键菜单 + 单输入逻辑)
|
|||
|
|
- UniversalNode: +40 lines(状态灯 + 耗时显示)
|
|||
|
|
- 总计:+310 lines
|
|||
|
|
|
|||
|
|
## 🎯 设计亮点
|
|||
|
|
|
|||
|
|
### 1. 数据持久化分层
|
|||
|
|
- **实时层**:React Flow state(交互响应)
|
|||
|
|
- **持久层**:Zustand Store(应用状态)
|
|||
|
|
- **存储层**:localStorage(跨会话)
|
|||
|
|
|
|||
|
|
### 2. 类型感知输入控件
|
|||
|
|
- 根据 `param_schema.type` 动态渲染
|
|||
|
|
- integer → number input(支持步进)
|
|||
|
|
- string → text input(支持占位符)
|
|||
|
|
|
|||
|
|
### 3. 智能连线管理
|
|||
|
|
- 自动删除冲突连线
|
|||
|
|
- 保持 DAG 拓扑约束
|
|||
|
|
- 颜色继承源节点类型
|
|||
|
|
|
|||
|
|
### 4. 渐进式视觉反馈
|
|||
|
|
- 静态:基础颜色 + 细线
|
|||
|
|
- Hover:高亮 + 边框
|
|||
|
|
- 运行:加粗 + 动画 + 发光
|
|||
|
|
- 完成:颜色变化 + 耗时显示
|
|||
|
|
|
|||
|
|
## 🚀 使用示例
|
|||
|
|
|
|||
|
|
### 创建并保存工作流
|
|||
|
|
|
|||
|
|
1. **构建流程**:
|
|||
|
|
- 拖拽 CSVLoader → 配置 file_path
|
|||
|
|
- 拖拽 FilterRows → 配置 column/condition
|
|||
|
|
- 连接 Loader 输出 → Filter 输入
|
|||
|
|
|
|||
|
|
2. **保存工作流**:
|
|||
|
|
- 画布空白处右键
|
|||
|
|
- 点击"保存工作流"
|
|||
|
|
- localStorage 自动存储
|
|||
|
|
|
|||
|
|
3. **导出分享**:
|
|||
|
|
- 画布空白处右键
|
|||
|
|
- 点击"导出工作流"
|
|||
|
|
- 下载 `workflow_1704672000000.json`
|
|||
|
|
|
|||
|
|
### 编辑节点参数
|
|||
|
|
|
|||
|
|
1. **选中节点**:点击 CSVLoader
|
|||
|
|
2. **修改参数**:Inspector 中输入 `data.csv`
|
|||
|
|
3. **实时生效**:节点体显示新值
|
|||
|
|
4. **切换节点**:点击其他节点
|
|||
|
|
5. **验证持久**:再次点击 CSVLoader,值仍为 `data.csv`
|
|||
|
|
|
|||
|
|
### 复制节点快速建模
|
|||
|
|
|
|||
|
|
1. **配置模板节点**:设置 FilterRows 的所有参数
|
|||
|
|
2. **右键复制**:节点上右键 → "复制节点"
|
|||
|
|
3. **微调副本**:修改副本的 column 参数
|
|||
|
|
4. **批量处理**:重复步骤 2-3 创建多个过滤器
|
|||
|
|
|
|||
|
|
## 💡 性能优化
|
|||
|
|
|
|||
|
|
### 防抖优化(未来)
|
|||
|
|
```typescript
|
|||
|
|
// 建议添加参数输入防抖
|
|||
|
|
const debouncedUpdate = useMemo(
|
|||
|
|
() => debounce(updateNodeData, 300),
|
|||
|
|
[]
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 虚拟滚动(未来)
|
|||
|
|
- 当节点数 > 100 时启用
|
|||
|
|
- 使用 `react-window` 优化渲染
|
|||
|
|
|
|||
|
|
### 增量更新
|
|||
|
|
- 仅更新变化的节点
|
|||
|
|
- 避免全量 re-render
|
|||
|
|
|
|||
|
|
## 🐛 已知限制
|
|||
|
|
|
|||
|
|
1. **localStorage 大小限制**
|
|||
|
|
- 最大 5-10MB(浏览器差异)
|
|||
|
|
- 建议大工作流使用导出功能
|
|||
|
|
|
|||
|
|
2. **节点状态手动管理**
|
|||
|
|
- 需要后端返回 status/executionTime
|
|||
|
|
- 目前为演示数据
|
|||
|
|
|
|||
|
|
3. **右键菜单定位**
|
|||
|
|
- 固定定位(可能超出视口)
|
|||
|
|
- 未来可添加自适应调整
|
|||
|
|
|
|||
|
|
## 🔮 下一步建议
|
|||
|
|
|
|||
|
|
### Phase 3: 真实数据处理
|
|||
|
|
1. **DAG 执行引擎**
|
|||
|
|
- 拓扑排序
|
|||
|
|
- 并行执行
|
|||
|
|
- 缓存机制
|
|||
|
|
|
|||
|
|
2. **节点状态管理**
|
|||
|
|
- 后端返回真实 status
|
|||
|
|
- WebSocket 实时更新
|
|||
|
|
- 执行队列可视化
|
|||
|
|
|
|||
|
|
3. **数据流追踪**
|
|||
|
|
- 连线上显示数据量
|
|||
|
|
- 中间结果缓存
|
|||
|
|
- 性能瓶颈分析
|
|||
|
|
|
|||
|
|
### 用户体验增强
|
|||
|
|
1. **快捷键系统**
|
|||
|
|
- Ctrl+S 保存
|
|||
|
|
- Ctrl+C/V 复制粘贴
|
|||
|
|
- Delete 删除节点
|
|||
|
|
|
|||
|
|
2. **撤销/重做**
|
|||
|
|
- 操作历史栈
|
|||
|
|
- Ctrl+Z/Ctrl+Y
|
|||
|
|
|
|||
|
|
3. **迷你地图增强**
|
|||
|
|
- 节点状态色彩映射
|
|||
|
|
- 执行进度可视化
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**完成时间**:2026-01-07
|
|||
|
|
**版本**:Phase 2 Final Polish v1.0
|
|||
|
|
**状态**:✅ 已完成,极致体验达成
|