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
|
||
**状态**:✅ 已完成,极致体验达成
|