# 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 响应式系统大大简化了渲染逻辑，提高了代码可维护性。

