# 标记功能实现方案 ## 🎯 功能概述 实现图像标记管理功能,支持预定义标记和用户自定义标记的添加、显示和管理。 ## 📋 需求分析 ### 1. 触发方式 - 用户点击 `FunctionArea.tsx` 中的 "Add Mark" 按钮 - 切换到二级面板 `MarkPanel` ### 2. 面板功能 - **预定义标记按钮**: 保留图片中的所有标记(拉姿、仰卧、俯卧、斜倚、过屈、过伸、内旋、外旋、吸气、呼气、负重、无负重) - **时间戳按钮**: 格式为 `YYYY-MM-DD HH:mm:ss` - **自定义标记**: 用户输入文本后添加到下拉框,选择后添加到图像 ### 3. 交互流程 1. **预定义标记**:用户直接点击预定义标记按钮 → 标记立即显示在图像上 2. **自定义标记**: - 用户输入标记文本 → 点击"添加"按钮 → 文本进入下拉框 - 用户从下拉框选择标记 → 点击"添加"按钮 → 标记显示在图像上 3. **时间戳**:用户点击时间戳按钮 → 当前时间立即显示在图像上 4. 点击"删除标记"按钮 → 删除选中的标记 ## 🏗️ 架构设计 ### 类图 ```mermaid classDiagram class FunctionArea { +AddMark 按钮 +handleButtonClick() } class OperationPanel { -currentPanel: string +renderPanel() +switchToMarkPanel() } class MarkPanel { -inputText: string -selectedMark: string -customMarks: string[] +renderInputArea() +renderSelectArea() +renderPredefinedButtons() +renderDeleteButton() } class ViewerContainer { -selectedViewportIds: string[] +handleMarkAction() +addCustomMark() } class StackViewer { +addLMark() +addRLabel() +addCustomMark() +deleteSelectedMark() } class MarkPanelSlice { -customMarks: string[] -selectedMark: string +addCustomMarkToList() +setSelectedMark() +addMarkToImage() +deleteSelectedMark() } FunctionArea --> OperationPanel : 触发切换 OperationPanel --> MarkPanel : 渲染面板 MarkPanel --> ViewerContainer : 触发标记动作 ViewerContainer --> StackViewer : 调用底层函数 MarkPanelSlice --> MarkPanel : 状态管理 ``` ### 流程图 ```mermaid flowchart TD A[用户点击 AddMark] --> B{是否已显示 MarkPanel?} B -->|否| C[切换到 MarkPanel] B -->|是| D[保持当前面板] C --> E[显示输入框和下拉框] D --> E E --> F[用户输入标记文本] F --> G[点击添加按钮] G --> H[文本进入下拉框] H --> I[用户从下拉框选择标记] I --> J{选择预定义按钮或时间戳?} J -->|预定义| K[添加预定义标记到图像] J -->|时间戳| L[添加当前时间到图像] K --> M[标记显示在图像上] L --> M M --> N{需要删除标记?} N -->|是| O[点击删除标记按钮] N -->|否| P[完成] O --> Q[删除选中的标记] Q --> P ``` ## 🎨 UI 布局设计 ``` ┌─────────────────────────────────────┐ │ ← 标记管理 │ ├─────────────────────────────────────┤ │ 自定义标记输入: │ │ ┌─────────────────────────────────┐ │ │ │ [输入框] [添加] │ │ │ └─────────────────────────────────┘ │ │ │ │ 选择要添加的标记: │ │ ┌─────────────────────────────────┐ │ │ │ [下拉框 ▼] │ │ │ └─────────────────────────────────┘ │ │ │ │ 预定义标记按钮: │ │ [拉姿] [仰卧] [俯卧] [斜倚] │ │ [过屈] [过伸] [内旋] [外旋] │ │ [吸气] [呼气] [负重] [无负重] │ │ [时间戳] │ │ │ │ ┌─────────┐ │ │ │ 删除标记 │ │ │ └─────────┘ │ └─────────────────────────────────────┘ ``` ## 💾 数据结构设计 ### 1. 状态管理 ```typescript interface MarkPanelState { customMarks: string[]; // 用户添加的自定义标记列表 selectedMark: string; // 当前选中的标记文本 inputText: string; // 输入框中的文本 selectedViewportIds: string[]; // 选中的视口ID } ``` ### 2. 预定义标记配置 ```typescript const PREDEFINED_MARKS = [ '拉姿', '仰卧', '俯卧', '斜倚', '过屈', '过伸', '内旋', '外旋', '吸气', '呼气', '负重', '无负重' ]; ``` ### 3. 时间戳格式 ```typescript const TIMESTAMP_FORMAT = 'YYYY-MM-DD HH:mm:ss'; ``` ## 🔄 交互序列图 ```mermaid sequenceDiagram participant 用户 participant FunctionArea participant PanelSwitch participant OperationPanel participant MarkPanel participant Redux participant ViewerContainer participant StackViewer 用户->>FunctionArea: 点击 AddMark 按钮 FunctionArea->>PanelSwitch: dispatch(switchToMarkPanel()) PanelSwitch->>OperationPanel: currentPanel = 'MarkPanel' OperationPanel->>MarkPanel: 渲染 MarkPanel 用户->>MarkPanel: 输入标记文本 用户->>MarkPanel: 点击"添加"按钮 MarkPanel->>Redux: dispatch(addCustomMarkToList(text)) Redux->>MarkPanel: 更新下拉框选项列表 用户->>MarkPanel: 从下拉框选择标记 MarkPanel->>Redux: dispatch(setSelectedMark(text)) 用户->>MarkPanel: 点击预定义标记按钮 MarkPanel->>Redux: dispatch(addMarkToImage(selectedMark)) Redux->>ViewerContainer: dispatch(addCustomMark(selectedMark)) ViewerContainer->>StackViewer: 调用addCustomMark函数 StackViewer->>StackViewer: 使用LabelTool添加标记到图像 StackViewer->>用户: 显示标记在图像上 用户->>MarkPanel: 点击删除标记按钮 MarkPanel->>Redux: dispatch(deleteSelectedMark()) Redux->>ViewerContainer: dispatch(deleteSelectedMark()) ViewerContainer->>StackViewer: 调用deleteSelectedMark函数 StackViewer->>用户: 从图像上删除标记 ``` ## 🧪 测试方案 ### 功能测试场景 1. **面板切换测试** - 点击 AddMark 按钮,验证 MarkPanel 正确定位和显示 - 测试从 MarkPanel 返回 OperationPanel 的功能 2. **自定义标记测试** - 输入文本并点击添加,验证下拉框正确更新 - 从下拉框选择标记,验证能正确添加到图像 3. **预定义标记测试** - 点击各个预定义标记按钮,验证在图像上正确显示 - 测试标记位置是否符合预期 4. **时间戳测试** - 点击时间戳按钮,验证时间格式正确(YYYY-MM-DD HH:mm:ss) - 验证时间戳能正确显示在图像上 5. **删除功能测试** - 测试删除选中标记功能 - 验证多视口标记同步删除 6. **边界情况测试** - 空文本输入处理 - 超长文本截断处理 - 特殊字符处理 ## 🐛 潜在问题分析 ### 1. 性能问题 - **问题**: 大量自定义标记可能导致下拉框性能下降 - **解决方案**: 限制自定义标记数量,超过限制时删除最旧的 ### 2. 状态同步问题 - **问题**: 多视口间标记状态不同步 - **解决方案**: 统一状态管理,确保所有视口标记一致 ### 3. 用户体验问题 - **问题**: 标记文本过长导致界面拥挤 - **解决方案**: 实现文本自动换行和省略号显示 ### 4. 数据持久化问题 - **问题**: 页面刷新后自定义标记列表丢失 - **解决方案**: 考虑添加本地存储功能 ## 📋 详细实现步骤 ### 1. 状态管理层 ✅ - [x] 创建 `src/states/view/markPanelSlice.ts` - 管理标记面板状态 - [x] 更新 `src/states/panelSwitchSliceForView.ts` - 添加 MarkPanel 切换功能 ### 2. MarkPanel 组件 ✅ - [x] 创建 `src/pages/view/components/MarkPanel.tsx` - 标记面板组件 - [x] 实现 FunctionButton 风格的预定义标记按钮 - [x] 实现自定义标记输入和下拉选择功能 - [x] 实现时间戳按钮功能 - [x] 实现删除标记按钮 ### 3. 底层标记功能扩展 ✅ - [x] 扩展 `stack.image.viewer.tsx` - 支持自定义文本标记 - [x] 实现时间戳格式化功能 - [x] 实现标记选择和删除逻辑 ### 4. 集成到主流程 ✅ - [x] 更新 `OperationPanel.tsx` - 添加 MarkPanel 路由 - [x] 更新 `FunctionArea.tsx` - 连接 AddMark 按钮到面板切换 ### 5. 测试和优化 ✅ - [x] 编译测试 - H5开发服务器成功启动 - [x] 功能集成测试 - 所有组件正确连接 - [x] Bug 修复 - 解决标记无法显示的问题(2025-10-22) - [ ] 单元测试各个组件 - [ ] 性能优化和用户体验改进 - [ ] 端到端测试完整流程 ## 🎯 关键实现要点 1. **按钮样式一致性**: 所有按钮都使用 FunctionButton 的样式和布局 2. **时间戳格式**: YYYY-MM-DD HH:mm:ss 3. **下拉框管理**: 动态管理用户添加的自定义标记 4. **状态同步**: 确保标记状态在组件间正确传递 5. **错误处理**: 完善的边界情况和异常处理 这个实现方案确保了功能的完整性和代码的可维护性,同时提供了良好的用户体验和扩展性。 ## 🔧 Bug 修复记录 ### 标记无法显示问题修复(2025-10-22) #### 问题描述 点击预定义标记按钮后,标记无法在图像上显示。 #### 根本原因 发现了两个关键问题: 1. **Action 分发和匹配不一致** ⚠️ - `MarkPanel.tsx` 发送的 action 是 `'AddPredefinedMark:拉姿'` (带参数) - `ViewerContainer.tsx` 的 switch case 匹配的是 `'AddPredefinedMark'` (不带参数) - 导致无法匹配,标记添加逻辑从未执行 2. **StackViewer 缺少 id 属性** 🔴 (关键问题) - `addCustomMark` 函数使用 `document.getElementById(viewportId)` 获取元素 - 但 StackViewer 的根 div 没有设置 id 属性 - 导致获取不到元素,无法计算标记位置 #### 修复方案 **修改的文件:** 1. **src/pages/view/components/ViewerContainer.tsx** ```typescript // 在 switch 前添加预处理逻辑 if (action.startsWith('AddPredefinedMark:')) { const markText = action.substring('AddPredefinedMark:'.length); selectedViewportIds.forEach((viewportId) => { addCustomMark(viewportId, markText); }); dispatch(clearAction()); return; } ``` 2. **src/pages/view/components/MarkPanel.tsx** ```typescript // 实现删除标记功能 const handleDeleteMark = () => { dispatch({ type: 'functionArea/setAction', payload: 'Delete Selected Mark' }); }; ``` 3. **src/pages/view/components/viewers/stack.image.viewer.tsx** ```typescript // 添加 id 属性 return (
); ``` #### 验证结果 修复后,以下功能正常工作: - ✅ 预定义标记 - 点击按钮后标记显示在图像中心 - ✅ 时间戳标记 - 显示当前时间(YYYY-MM-DD HH:mm:ss) - ✅ 自定义标记 - 输入文本、选择并添加到图像 - ✅ 删除标记 - 删除图像上的 L 和 R 标记 详细的修复文档:[标记功能Bug修复总结](./标记功能Bug修复总结.md)