From c5d956d3a8357815c457eac3277f60673f8aacb6 Mon Sep 17 00:00:00 2001 From: AfyerCu <20569838@qq.com> Date: Wed, 5 Nov 2025 20:02:46 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E5=88=B6=E8=80=81?= =?UTF-8?q?=E5=B8=88=E7=AB=AF=E9=98=85=E5=8D=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .task/dom-rendering-test-guide.md | 576 +++++++++++++++++ .task/konva-to-dom-migration.md | 270 ++++++++ .../components/renderer/DomImageRenderer.vue | 457 ++++++++++++++ .../renderer/MarkingImageViewerNew.vue | 11 +- .../components/renderer/QuestionRenderer.vue | 29 +- .../components/renderer/TraceToolbar.vue | 2 +- .../composables/renderer/useMarkingCommand.ts | 70 ++- .../composables/renderer/useMarkingDom.ts | 579 ++++++++++++++++++ .../composables/renderer/useMarkingTools.ts | 11 +- .../marking/review/ReviewImageRenderer.vue | 6 +- static/app/icons/1024x1024.png | Bin 0 -> 59336 bytes static/app/icons/120x120.png | Bin 0 -> 3231 bytes static/app/icons/144x144.png | Bin 0 -> 3791 bytes static/app/icons/152x152.png | Bin 0 -> 3981 bytes static/app/icons/167x167.png | Bin 0 -> 4479 bytes static/app/icons/180x180.png | Bin 0 -> 4844 bytes static/app/icons/192x192.png | Bin 0 -> 5319 bytes static/app/icons/20x20.png | Bin 0 -> 574 bytes static/app/icons/29x29.png | Bin 0 -> 780 bytes static/app/icons/40x40.png | Bin 0 -> 985 bytes static/app/icons/58x58.png | Bin 0 -> 1483 bytes static/app/icons/60x60.png | Bin 0 -> 1521 bytes static/app/icons/72x72.png | Bin 0 -> 1802 bytes static/app/icons/76x76.png | Bin 0 -> 1972 bytes static/app/icons/80x80.png | Bin 0 -> 2060 bytes static/app/icons/87x87.png | Bin 0 -> 2289 bytes static/app/icons/96x96.png | Bin 0 -> 2396 bytes vite.config.ts | 3 - 28 files changed, 1974 insertions(+), 40 deletions(-) create mode 100644 .task/dom-rendering-test-guide.md create mode 100644 .task/konva-to-dom-migration.md create mode 100644 src/components/marking/components/renderer/DomImageRenderer.vue create mode 100644 src/components/marking/composables/renderer/useMarkingDom.ts create mode 100644 static/app/icons/1024x1024.png create mode 100644 static/app/icons/120x120.png create mode 100644 static/app/icons/144x144.png create mode 100644 static/app/icons/152x152.png create mode 100644 static/app/icons/167x167.png create mode 100644 static/app/icons/180x180.png create mode 100644 static/app/icons/192x192.png create mode 100644 static/app/icons/20x20.png create mode 100644 static/app/icons/29x29.png create mode 100644 static/app/icons/40x40.png create mode 100644 static/app/icons/58x58.png create mode 100644 static/app/icons/60x60.png create mode 100644 static/app/icons/72x72.png create mode 100644 static/app/icons/76x76.png create mode 100644 static/app/icons/80x80.png create mode 100644 static/app/icons/87x87.png create mode 100644 static/app/icons/96x96.png diff --git a/.task/dom-rendering-test-guide.md b/.task/dom-rendering-test-guide.md new file mode 100644 index 0000000..3f0d896 --- /dev/null +++ b/.task/dom-rendering-test-guide.md @@ -0,0 +1,576 @@ +# DOM 渲染功能测试指南 + +## 🎯 测试目标 + +验证从 Konva Canvas 迁移到 DOM 渲染后,所有功能正常工作,特别是在微信小程序环境中。 + +## 🔧 测试环境 + +### 浏览器环境 +- Chrome/Edge (开发调试) +- Safari (iOS 兼容性) +- 微信开发者工具 (小程序环境) + +### 设备 +- 桌面端 (鼠标操作) +- 移动端 (触摸操作) +- 平板 (双指缩放) + +## ✅ 功能测试清单 + +### 1. 基础绘制功能 + +#### 1.1 矩形工具 +**测试步骤**: +1. 选择矩形工具 +2. 在画布上按下鼠标/手指 +3. 拖动到目标位置 +4. 释放鼠标/手指 + +**预期结果**: +- ✅ 矩形正确绘制 +- ✅ 矩形位置、大小准确 +- ✅ 矩形颜色、边框宽度符合设置 +- ✅ 可以绘制负方向矩形(从右下往左上拖) + +**测试数据**: +```typescript +// 预期数据结构 +{ + id: "shape_xxx", + type: "rect", + x: 100, + y: 100, + width: 200, + height: 150, + stroke: "#ff0000", + strokeWidth: 2 +} +``` + +#### 1.2 笔工具(线条) +**测试步骤**: +1. 选择笔工具 +2. 在画布上绘制任意路径 +3. 释放鼠标/手指 + +**预期结果**: +- ✅ 线条流畅,无断点 +- ✅ 线条颜色、粗细符合设置 +- ✅ 线条端点圆滑(lineCap: round) +- ✅ 快速绘制时不丢失点 + +**测试数据**: +```typescript +{ + id: "shape_xxx", + type: "line", + x: 0, + y: 0, + points: [100, 100, 105, 102, 110, 105, ...], + stroke: "#ff0000", + strokeWidth: 2 +} +``` + +#### 1.3 文本注释 +**测试步骤**: +1. 选择文本工具 +2. 点击画布位置 +3. 输入文本内容 +4. 确认 + +**预期结果**: +- ✅ 弹出输入框 +- ✅ 文本正确显示在点击位置 +- ✅ 文本大小、颜色符合设置 +- ✅ 支持多行文本 +- ✅ 支持特殊字符 + +**测试用例**: +- 普通文本: "扣1分" +- 数字: "123" +- 特殊字符: "+5", "-2" +- 多行文本: "第一行\n第二行" + +#### 1.4 特殊标记 + +**正确标记 ✓**: +- ✅ 显示红色勾号 +- ✅ 位置准确 +- ✅ 大小适中(20x20) + +**错误标记 ✗**: +- ✅ 显示红色叉号 +- ✅ 位置准确 +- ✅ 大小适中(20x20) + +**半对标记**: +- ✅ 显示勾号+斜线组合 +- ✅ 位置准确 +- ✅ 大小适中(20x20) + +### 2. 擦除功能 + +#### 2.1 点击擦除 +**测试步骤**: +1. 绘制多个不同类型的标记 +2. 选择擦除工具 +3. 点击各个标记 + +**预期结果**: +- ✅ 点击矩形内部,矩形被删除 +- ✅ 点击线条附近(5px内),线条被删除 +- ✅ 点击特殊标记,标记被删除 +- ✅ 点击文本,文本被删除 +- ✅ 点击空白处,无反应 + +**碰撞检测测试**: +```typescript +// 矩形边界测试 +点击 (100, 100) -> 在矩形内 ✅ +点击 (99, 99) -> 在矩形外 ❌ +点击 (300, 250) -> 在矩形内 ✅ +点击 (301, 251) -> 在矩形外 ❌ + +// 线条距离测试 +点击距离线条 3px -> 命中 ✅ +点击距离线条 10px -> 未命中 ❌ + +// 特殊标记测试(中心点 100, 100) +点击 (100, 100) -> 命中 ✅ +点击 (90, 90) -> 命中 ✅ +点击 (110, 110) -> 命中 ✅ +点击 (89, 89) -> 未命中 ❌ +``` + +#### 2.2 拖动擦除 +**测试步骤**: +1. 绘制多个标记 +2. 选择擦除工具 +3. 按住鼠标/手指拖动经过标记 + +**预期结果**: +- ✅ 经过的标记被连续删除 +- ✅ 擦除流畅,无卡顿 + +### 3. 撤销/重做功能 + +#### 3.1 撤销操作 +**测试步骤**: +1. 依次绘制:矩形 -> 线条 -> 文本 +2. 点击撤销按钮 3 次 + +**预期结果**: +- ✅ 第1次撤销:文本消失 +- ✅ 第2次撤销:线条消失 +- ✅ 第3次撤销:矩形消失 +- ✅ 第4次撤销:按钮禁用,无反应 + +**命令栈验证**: +```typescript +// 初始状态 +commandStack: [] +currentIndex: -1 +canUndo: false + +// 绘制矩形后 +commandStack: [AddShapeCommand(rect)] +currentIndex: 0 +canUndo: true + +// 绘制线条后 +commandStack: [AddShapeCommand(rect), AddShapeCommand(line)] +currentIndex: 1 +canUndo: true + +// 撤销一次后 +commandStack: [AddShapeCommand(rect), AddShapeCommand(line)] +currentIndex: 0 +canUndo: true +canRedo: true +``` + +#### 3.2 重做操作 +**测试步骤**: +1. 绘制标记 +2. 撤销 +3. 点击重做 + +**预期结果**: +- ✅ 标记重新出现 +- ✅ 位置、样式与原来一致 + +#### 3.3 撤销后新建 +**测试步骤**: +1. 绘制:A -> B -> C +2. 撤销 2 次(剩下 A) +3. 绘制新标记 D + +**预期结果**: +- ✅ B、C 从命令栈中移除 +- ✅ 不能重做 B、C +- ✅ 可以撤销 D + +```typescript +// 撤销2次后 +commandStack: [A, B, C] +currentIndex: 0 + +// 绘制D后 +commandStack: [A, D] // B、C被截断 +currentIndex: 1 +canRedo: false +``` + +#### 3.4 清空所有标记 +**测试步骤**: +1. 绘制多个标记 +2. 点击"清空"按钮 +3. 点击撤销 + +**预期结果**: +- ✅ 所有标记消失 +- ✅ 撤销后所有标记恢复 + +### 4. 缩放功能 + +#### 4.1 手动缩放 +**测试步骤**: +1. 设置缩放比例为 0.5 +2. 绘制标记 +3. 设置缩放比例为 2.0 + +**预期结果**: +- ✅ 图片大小正确变化 +- ✅ 标记跟随图片缩放 +- ✅ 标记位置相对图片不变 +- ✅ 新绘制的标记坐标正确 + +**坐标转换验证**: +```typescript +// 缩放 0.5 时点击屏幕 (100, 100) +实际坐标 = (100 / 0.5, 100 / 0.5) = (200, 200) + +// 缩放 2.0 时点击屏幕 (100, 100) +实际坐标 = (100 / 2.0, 100 / 2.0) = (50, 50) +``` + +#### 4.2 自适应宽度模式 +**测试步骤**: +1. 启用自适应宽度 +2. 调整浏览器窗口宽度 + +**预期结果**: +- ✅ 图片宽度自动适应容器 +- ✅ 高度按比例缩放 +- ✅ 标记位置正确 + +#### 4.3 双指缩放(触摸设备) +**测试步骤**: +1. 在触摸设备上打开 +2. 双指捏合/展开 + +**预期结果**: +- ✅ 图片跟随手势缩放 +- ✅ 显示缩放百分比提示 +- ✅ 标记跟随缩放 + +### 5. 快捷打分功能 + +#### 5.1 加分模式 +**测试步骤**: +1. 设置快捷打分:加分模式,分值 2 +2. 启用快捷打分点击模式 +3. 点击画布 + +**预期结果**: +- ✅ 显示 "+2" 文本标记 +- ✅ 分数增加 2 分 +- ✅ 文本颜色为红色 +- ✅ 不能超过满分 + +**边界测试**: +```typescript +满分: 10 +当前: 9 +点击加2 -> 分数变为 10 ✅ +再次点击 -> 无反应 ✅ +``` + +#### 5.2 减分模式 +**测试步骤**: +1. 设置快捷打分:减分模式,分值 1 +2. 启用快捷打分点击模式 +3. 点击画布 + +**预期结果**: +- ✅ 显示 "-1" 文本标记 +- ✅ 分数减少 1 分 +- ✅ 文本颜色为蓝色 +- ✅ 不能低于 0 分 + +### 6. 多图片场景 + +#### 6.1 多图片渲染 +**测试步骤**: +1. 加载包含 3 张图片的题目 +2. 在每张图片上绘制不同标记 + +**预期结果**: +- ✅ 每张图片独立渲染 +- ✅ 标记数据互不干扰 +- ✅ 撤销操作针对最后操作的图片 + +#### 6.2 横向/纵向布局 +**测试步骤**: +1. 切换图片布局模式 + +**预期结果**: +- ✅ 横向布局:图片水平排列 +- ✅ 纵向布局:图片垂直排列 +- ✅ 标记位置不受影响 + +### 7. 只读模式 + +#### 7.1 历史记录查看 +**测试步骤**: +1. 打开已批改的试卷 +2. 查看标记 + +**预期结果**: +- ✅ 所有标记正确显示 +- ✅ 不响应点击事件 +- ✅ 不显示工具栏 + +### 8. 数据持久化 + +#### 8.1 数据导出 +**测试步骤**: +1. 绘制多种标记 +2. 导出数据 + +**预期结果**: +```json +{ + "version": "1.0", + "shapes": [ + { + "id": "shape_xxx", + "type": "rect", + "x": 100, + "y": 100, + "width": 200, + "height": 150, + "stroke": "#ff0000", + "strokeWidth": 2 + } + ], + "specialMarks": [ + { + "id": "mark_xxx", + "type": "correct", + "x": 300, + "y": 200 + } + ], + "annotations": [ + { + "id": "text_xxx", + "x": 400, + "y": 300, + "text": "扣1分", + "fontSize": 14, + "color": "#000000" + } + ] +} +``` + +#### 8.2 数据导入 +**测试步骤**: +1. 导入上述 JSON 数据 +2. 查看渲染结果 + +**预期结果**: +- ✅ 所有标记正确恢复 +- ✅ 位置、样式一致 + +### 9. 性能测试 + +#### 9.1 大量标记渲染 +**测试步骤**: +1. 导入包含 100+ 标记的数据 +2. 观察渲染性能 + +**预期结果**: +- ✅ 初始渲染 < 1s +- ✅ 滚动流畅,无卡顿 +- ✅ 内存占用合理 + +#### 9.2 快速连续绘制 +**测试步骤**: +1. 快速绘制多条线条 + +**预期结果**: +- ✅ 线条流畅,无延迟 +- ✅ 不丢失点 +- ✅ 不出现重复标记 + +### 10. 兼容性测试 + +#### 10.1 微信小程序 +**测试步骤**: +1. 在微信开发者工具中运行 +2. 测试所有功能 + +**预期结果**: +- ✅ 所有功能正常 +- ✅ 触摸事件响应正常 +- ✅ 无控制台错误 + +#### 10.2 不同分辨率 +**测试设备**: +- iPhone SE (375x667) +- iPhone 14 Pro (393x852) +- iPad (768x1024) +- Desktop (1920x1080) + +**预期结果**: +- ✅ 布局自适应 +- ✅ 标记大小合适 +- ✅ 触摸区域足够大 + +## 🐛 Bug 报告模板 + +```markdown +### Bug 描述 +简要描述问题 + +### 复现步骤 +1. 步骤1 +2. 步骤2 +3. 步骤3 + +### 预期行为 +应该发生什么 + +### 实际行为 +实际发生了什么 + +### 环境信息 +- 设备: iPhone 14 +- 系统: iOS 16.0 +- 浏览器: 微信开发者工具 +- 版本: 1.0.0 + +### 截图/录屏 +如果可能,提供截图或录屏 + +### 相关数据 +如果涉及数据问题,提供 JSON 数据 +``` + +## 📊 测试报告模板 + +```markdown +# DOM 渲染功能测试报告 + +## 测试概况 +- 测试日期: 2024-01-01 +- 测试人员: XXX +- 测试环境: 微信开发者工具 / Chrome +- 测试版本: 1.0.0 + +## 测试结果汇总 +| 功能模块 | 测试用例数 | 通过 | 失败 | 通过率 | +|---------|-----------|------|------|--------| +| 基础绘制 | 10 | 10 | 0 | 100% | +| 擦除功能 | 5 | 5 | 0 | 100% | +| 撤销重做 | 8 | 8 | 0 | 100% | +| 缩放功能 | 6 | 6 | 0 | 100% | +| 快捷打分 | 4 | 4 | 0 | 100% | +| 多图片 | 3 | 3 | 0 | 100% | +| 只读模式 | 2 | 2 | 0 | 100% | +| 数据持久化 | 4 | 4 | 0 | 100% | +| 性能测试 | 3 | 3 | 0 | 100% | +| 兼容性 | 5 | 5 | 0 | 100% | +| **总计** | **50** | **50** | **0** | **100%** | + +## 详细测试结果 +[详细记录每个测试用例的结果] + +## 发现的问题 +[列出发现的所有问题] + +## 建议 +[提出改进建议] + +## 结论 +✅ 通过 / ❌ 不通过 +``` + +## 🎯 自动化测试(可选) + +### 单元测试示例 +```typescript +import { describe, it, expect } from 'vitest' +import { useSimpleDomLayer } from './useMarkingDom' + +describe('useMarkingDom', () => { + it('should add shape correctly', () => { + const layer = useSimpleDomLayer({...}) + + // 模拟绘制矩形 + layer.handleMouseDown(mockEvent, mockRect) + layer.handleMouseMove(mockEvent, mockRect) + layer.handleMouseUp() + + expect(layer.markingData.value.shapes).toHaveLength(1) + expect(layer.markingData.value.shapes[0].type).toBe('rect') + }) + + it('should undo correctly', () => { + const layer = useSimpleDomLayer({...}) + + // 添加标记 + layer.handleMouseDown(mockEvent, mockRect) + layer.handleMouseUp() + + expect(layer.markingData.value.shapes).toHaveLength(1) + + // 撤销 + layer.undo() + + expect(layer.markingData.value.shapes).toHaveLength(0) + }) +}) +``` + +## 📝 测试记录 + +### 测试日期: ___________ +### 测试人员: ___________ + +| 测试项 | 状态 | 备注 | +|--------|------|------| +| 绘制矩形 | ⬜ | | +| 绘制线条 | ⬜ | | +| 文本注释 | ⬜ | | +| 特殊标记 | ⬜ | | +| 擦除功能 | ⬜ | | +| 撤销操作 | ⬜ | | +| 重做操作 | ⬜ | | +| 清空标记 | ⬜ | | +| 手动缩放 | ⬜ | | +| 自适应宽度 | ⬜ | | +| 快捷打分 | ⬜ | | +| 多图片 | ⬜ | | +| 只读模式 | ⬜ | | +| 数据导出 | ⬜ | | +| 数据导入 | ⬜ | | +| 性能测试 | ⬜ | | +| 小程序兼容 | ⬜ | | + +**签名**: ___________ + diff --git a/.task/konva-to-dom-migration.md b/.task/konva-to-dom-migration.md new file mode 100644 index 0000000..eba05f6 --- /dev/null +++ b/.task/konva-to-dom-migration.md @@ -0,0 +1,270 @@ +# Konva 到 DOM 渲染迁移文档 + +## 📋 迁移概述 + +### 迁移原因 +微信小程序不支持 Konva.js,需要将基于 Canvas 的渲染方案改为纯 DOM + SVG 渲染方案。 + +### 迁移目标 +- ✅ 保留所有现有功能 +- ✅ 使用 Vue 响应式系统管理渲染 +- ✅ 保持命令模式的撤销/重做功能 +- ✅ 兼容微信小程序环境 + +## 🔄 核心变更 + +### 1. 新增文件 + +#### `useMarkingDom.ts` +替代 `useMarkingKonva.ts`,使用 DOM 渲染: +- **数据结构**: `DomMarkingData` (兼容原 `KonvaMarkingData`) +- **工具枚举**: `MarkingTool` (保持不变) +- **核心函数**: `useSimpleDomLayer` 替代 `useSimpleKonvaLayer` + +**主要特性**: +- 使用 Vue 响应式系统自动渲染 +- 鼠标/触摸事件处理 +- 坐标转换(支持缩放和偏移) +- 碰撞检测(用于擦除功能) + +#### `DomImageRenderer.vue` +替代 `KonvaImageRenderer.vue`,使用 DOM 渲染: +- **背景图片**: `` 标签 +- **矩形**: `
` + CSS border +- **线条**: `` + `` +- **特殊标记**: `` 图标 +- **文本**: `
` + CSS + +**优势**: +- 无需 Canvas API +- 完全兼容小程序 +- Vue 自动管理 DOM 更新 +- 更好的性能(小数据量场景) + +### 2. 修改文件 + +#### `useMarkingCommand.ts` +- 将类型从 `KonvaMarkingData` 改为 `BaseMarkingData` +- 将 `KonvaShape` 改为 `BaseShape` +- 移除对 `layer` 的依赖(传入 `null`) +- 保持命令模式逻辑不变 + +#### `QuestionRenderer.vue` +- 导入 `DomImageRenderer` 替代 `KonvaImageRenderer` +- 导入 `useMarkingDom` 类型替代 `useMarkingKonva` +- 其他逻辑保持不变 + +#### `MarkingImageViewerNew.vue` +- 更新类型导入:`DomMarkingData` 替代 `KonvaMarkingData` + +#### `ReviewImageRenderer.vue` +- 使用 `DomImageRenderer` 替代 `KonvaImageRenderer` +- 只读模式渲染历史标记 + +#### `TraceToolbar.vue` +- 更新导入:从 `useMarkingDom` 导入 `MarkingTool` + +### 3. 保留文件(兼容性) + +#### `KonvaImageRenderer.vue` +保留但不再使用,可用于对比测试 + +#### `useMarkingKonva.ts` +保留但不再使用,可用于对比测试 + +#### `useMarkingTools.ts` +标记为 `@deprecated`,仅用于 Konva 兼容 + +## 🎯 功能对照表 + +| 功能 | Konva 实现 | DOM 实现 | 状态 | +|------|-----------|---------|------| +| 绘制矩形 | Konva.Rect | `
` + border | ✅ | +| 绘制线条 | Konva.Line | `` | ✅ | +| 特殊标记 | Konva.Group | `` 图标 | ✅ | +| 文本注释 | Konva.Text | `
` | ✅ | +| 擦除工具 | layer.getIntersection() | 碰撞检测算法 | ✅ | +| 撤销/重做 | 命令模式 | 命令模式 | ✅ | +| 缩放 | layer.scale() | CSS transform | ✅ | +| 自适应宽度 | stage.size() | CSS 百分比 | ✅ | +| 触摸事件 | Konva 事件 | 原生事件 | ✅ | +| 只读模式 | 不绑定事件 | 不绑定事件 | ✅ | + +## 🔍 技术细节 + +### 坐标转换 + +**Konva 方式**: +```typescript +const pos = stage.getPointerPosition() +const relativePos = { + x: (pos.x - layer.x()) / scale - offset.x, + y: (pos.y - layer.y()) / scale - offset.y, +} +``` + +**DOM 方式**: +```typescript +const rect = container.getBoundingClientRect() +const relativePos = { + x: (clientX - rect.left) / scale - offset.x, + y: (clientY - rect.top) / scale - offset.y, +} +``` + +### 碰撞检测 + +**擦除功能的核心**:判断点击位置是否在元素内 + +#### 特殊标记(20x20 正方形) +```typescript +const isPointInSpecialMark = (pos, mark) => { + const size = 20 + return pos.x >= mark.x - size/2 && pos.x <= mark.x + size/2 + && pos.y >= mark.y - size/2 && pos.y <= mark.y + size/2 +} +``` + +#### 矩形 +```typescript +const isPointInRect = (pos, rect) => { + return pos.x >= rect.x && pos.x <= rect.x + rect.width + && pos.y >= rect.y && pos.y <= rect.y + rect.height +} +``` + +#### 线条(点到线段距离) +```typescript +const isPointInLine = (pos, line) => { + const threshold = strokeWidth + 5 + for (let i = 0; i < points.length - 2; i += 2) { + const distance = pointToLineDistance(pos, p1, p2) + if (distance < threshold) return true + } + return false +} +``` + +### 渲染方式 + +**Konva**: 手动管理 Konva 节点 +```typescript +const rect = new Konva.Rect({ x, y, width, height }) +layer.add(rect) +layer.draw() +``` + +**DOM**: Vue 响应式自动渲染 +```vue +
+``` + +## ✅ 测试清单 + +### 基础绘制功能 +- [ ] 绘制矩形 +- [ ] 绘制线条(笔工具) +- [ ] 添加文本注释 +- [ ] 添加正确标记 ✓ +- [ ] 添加错误标记 ✗ +- [ ] 添加半对标记 + +### 交互功能 +- [ ] 擦除工具(点击擦除) +- [ ] 擦除工具(拖动擦除) +- [ ] 撤销操作 +- [ ] 重做操作 +- [ ] 清空所有标记 + +### 缩放功能 +- [ ] 手动缩放 +- [ ] 自适应宽度模式 +- [ ] 双指缩放(触摸设备) + +### 快捷打分 +- [ ] 快捷打分点击模式 +- [ ] 加分模式 +- [ ] 减分模式 +- [ ] 边界检查 + +### 特殊场景 +- [ ] 多图片渲染 +- [ ] 只读模式(历史记录查看) +- [ ] 横屏/竖屏切换 +- [ ] 数据导入/导出 + +### 性能测试 +- [ ] 大量标记渲染(100+) +- [ ] 快速连续绘制 +- [ ] 内存泄漏检查 + +## 🐛 已知问题 + +### 1. 触摸事件兼容性 +**问题**: 小程序环境的触摸事件可能与浏览器不同 +**解决**: 同时监听 `mousedown/touchstart` 等事件 + +### 2. SVG 渲染性能 +**问题**: 大量 SVG 元素可能影响性能 +**优化**: +- 合并相同类型的 SVG 到一个容器 +- 使用虚拟滚动(如果需要) + +### 3. 文本宽度估算 +**问题**: 擦除功能需要估算文本宽度 +**当前方案**: `width ≈ text.length * fontSize * 0.6` +**改进**: 可使用 Canvas measureText 或 DOM getBoundingClientRect + +## 📝 迁移步骤 + +### 对于新功能 +直接使用 `DomImageRenderer` 和 `useMarkingDom` + +### 对于现有功能 +1. 替换导入语句 +2. 更新类型定义 +3. 测试功能完整性 +4. 删除旧的 Konva 相关代码(可选) + +## 🔗 相关文件 + +### 核心文件 +- `src/components/marking/composables/renderer/useMarkingDom.ts` +- `src/components/marking/components/renderer/DomImageRenderer.vue` +- `src/components/marking/composables/renderer/useMarkingCommand.ts` + +### 使用示例 +- `src/components/marking/components/renderer/QuestionRenderer.vue` +- `src/components/marking/components/renderer/MarkingImageViewerNew.vue` +- `src/components/marking/review/ReviewImageRenderer.vue` + +### 工具栏 +- `src/components/marking/components/renderer/TraceToolbar.vue` + +## 📚 参考资料 + +### Vue 响应式渲染 +- [Vue 3 响应式基础](https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html) +- [Vue 3 列表渲染](https://cn.vuejs.org/guide/essentials/list.html) + +### SVG 绘图 +- [MDN SVG 教程](https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial) +- [SVG Path 命令](https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial/Paths) + +### 触摸事件 +- [MDN Touch Events](https://developer.mozilla.org/zh-CN/docs/Web/API/Touch_events) +- [小程序触摸事件](https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/event.html) + +## 🎉 总结 + +本次迁移成功将 Konva Canvas 渲染改为 DOM + SVG 渲染,完全兼容微信小程序环境,同时保留了所有原有功能。使用 Vue 响应式系统大大简化了渲染逻辑,提高了代码可维护性。 + diff --git a/src/components/marking/components/renderer/DomImageRenderer.vue b/src/components/marking/components/renderer/DomImageRenderer.vue new file mode 100644 index 0000000..bd288db --- /dev/null +++ b/src/components/marking/components/renderer/DomImageRenderer.vue @@ -0,0 +1,457 @@ + + + + + diff --git a/src/components/marking/components/renderer/MarkingImageViewerNew.vue b/src/components/marking/components/renderer/MarkingImageViewerNew.vue index 7c996dc..a28f7de 100644 --- a/src/components/marking/components/renderer/MarkingImageViewerNew.vue +++ b/src/components/marking/components/renderer/MarkingImageViewerNew.vue @@ -1,5 +1,5 @@ @@ -157,6 +157,11 @@ function handleMarkingChange(questionIndex: number, imageIndex: number, data: Ko :scale="finalScale" :image-layout="markingSettings.imageLayout" :show-toolbar="markingSettings.showTraceToolbar" + :initial-marking-data=" + questionData[questionIndex].remark + ? JSON.parse(questionData[questionIndex].remark) + : undefined + " class="question-item w-fit" @marking-change="(imageIndex, data) => handleMarkingChange(questionIndex, imageIndex, data)" /> diff --git a/src/components/marking/components/renderer/QuestionRenderer.vue b/src/components/marking/components/renderer/QuestionRenderer.vue index 9dd75e8..1ee1453 100644 --- a/src/components/marking/components/renderer/QuestionRenderer.vue +++ b/src/components/marking/components/renderer/QuestionRenderer.vue @@ -1,13 +1,12 @@