xlx_teacher_app/.task/konva-to-dom-migration.md

271 lines
7.3 KiB
Markdown
Raw Permalink 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.

# 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 渲染:
- **背景图片**: `<img>` 标签
- **矩形**: `<div>` + CSS border
- **线条**: `<svg>` + `<polyline>`
- **特殊标记**: `<svg>` 图标
- **文本**: `<div>` + 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 | `<div>` + border | ✅ |
| 绘制线条 | Konva.Line | `<svg><polyline>` | ✅ |
| 特殊标记 | Konva.Group | `<svg>` 图标 | ✅ |
| 文本注释 | Konva.Text | `<div>` | ✅ |
| 擦除工具 | 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
<div
v-for="shape in markingData.shapes"
:key="shape.id"
:style="{
left: `${shape.x}px`,
top: `${shape.y}px`,
width: `${shape.width}px`,
height: `${shape.height}px`,
}"
/>
```
## ✅ 测试清单
### 基础绘制功能
- [ ] 绘制矩形
- [ ] 绘制线条(笔工具)
- [ ] 添加文本注释
- [ ] 添加正确标记 ✓
- [ ] 添加错误标记 ✗
- [ ] 添加半对标记
### 交互功能
- [ ] 擦除工具(点击擦除)
- [ ] 擦除工具(拖动擦除)
- [ ] 撤销操作
- [ ] 重做操作
- [ ] 清空所有标记
### 缩放功能
- [ ] 手动缩放
- [ ] 自适应宽度模式
- [ ] 双指缩放(触摸设备)
### 快捷打分
- [ ] 快捷打分点击模式
- [ ] 加分模式
- [ ] 减分模式
- [ ] 边界检查
### 特殊场景
- [ ] 多图片渲染
- [ ] 只读模式(历史记录查看)
- [ ] 横屏/竖屏切换
- [ ] 数据导入/导出
### 性能测试
- [ ] 大量标记渲染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 响应式系统大大简化了渲染逻辑,提高了代码可维护性。