# 答题卡排版编辑器 (Template Editor) 业务流程文档

本文档详细描述了 `src/views/template/template-detail` 模块的业务流程与核心逻辑。

## 1. 核心功能概述

该模块主要用于创建和编辑答题卡模板。用户可以上传底图（支持图片或 PDF），在底图上通过配置参数批量生成答题区域，并对生成的区域进行拖拽、缩放、旋转等排版操作，最终保存模板数据到后端。

## 2. 目录结构

*   `index.vue`: **主入口文件**。负责整体状态管理、组件组装（Canvas + Settings）、数据初始化、保存逻辑以及核心的“生成区域”逻辑。
*   `hooks/useUpload.ts`: **上传逻辑封装**。负责文件处理（PDF 转图片）、OSS 上传、Token 获取等。
*   `modules/TemplateCanvas.vue`: **画布组件**。负责渲染底图、绘制答题区域、处理区域的交互（拖拽、缩放、旋转、多选/单选、对齐辅助线）。
*   `modules/TemplateSettings.vue`: **设置面板组件**。负责展示模板基础信息、提供区域生成的参数配置（行列、宽高、间距）、展示已生成的区域列表。

## 3. 详细业务流程

### 3.1 初始化 (Initialization)

1.  **加载页面**: 进入 `index.vue`。
2.  **获取详情**: `onMounted` 钩子调用 `initData`。
    *   检查 URL 参数 `id`。
    *   如果有 `id`，调用 `fetchTemplateDetail(id)` 获取模板详情。
    *   **数据回填**:
        *   将 `Name`, `Width`, `Height` 回填到 `templateInfo`。
        *   将 `BackGroundUrl` 回填到 `imgSrc` 和 `templateInfo.backGroundUrl`。
        *   解析 `TempContent` (JSON 字符串) 回填到 `regions` 列表。

### 3.2 上传底图 (Upload Background)

逻辑主要在 `hooks/useUpload.ts` 中实现：

1.  **触发上传**: 用户点击“上传底稿”。
2.  **上传检查 (`handleUploadCheck`)**:
    *   如果已有底图，弹出确认框，提示“更换底图将清空当前已生成的布局”。
    *   确认后打开文件选择器。
3.  **文件处理 (`handleUpload`)**:
    *   **PDF 处理**: 使用 `pdfjs-dist` 渲染 PDF 第一页，按 A4 比例（2100px 宽）转换为 Canvas，再导出为 Blob。
    *   **图片处理**: 直接读取图片文件。
4.  **OSS 上传**:
    *   调用 `getAliOssTokenAxios` 获取 STS Token。
    *   使用 `ali-oss` 客户端将文件上传至 OSS。
5.  **状态更新**:
    *   更新 `imgSrc` 为 OSS 返回的 URL。
    *   更新 `templateInfo` 的宽高。
    *   **清空现有区域** (`regions`) 和选中状态 (`selectedRegionIds`)。

### 3.3 区域生成 (Region Generation)

逻辑在 `index.vue` 的 `handleGenerate` 中：

1.  **配置参数**: 用户在 `TemplateSettings` 面板输入行列数、宽高、间距。
2.  **计算编号**:
    *   遍历现有 `regions`，解析 `label` (如 "Q1", "Q2")，找到最大编号 `maxNum`。
    *   新生成的区域编号从 `maxNum + 1` 开始递增。
3.  **批量创建**:
    *   根据行 (`rows`)、列 (`cols`) 双重循环。
    *   计算每个区域的 `x, y` 坐标：`start + index * (size + gap)`。
    *   为这批区域生成同一个 `groupId` (UUID)。
4.  **自动选中**:
    *   将新生成的区域加入 `regions` 列表。
    *   如果生成了多个区域，自动将它们全部加入 `selectedRegionIds`，方便用户立即进行批量拖拽。

### 3.4 画布交互 (Canvas Interaction)

逻辑在 `modules/TemplateCanvas.vue` 中，核心依赖 `vue3-draggable-resizable`：

1.  **渲染**:
    *   底图作为背景。
    *   遍历 `regions` 渲染 `VueDraggableResizable` 组件。
2.  **选择逻辑**:
    *   **单选**: 点击某个区域，清除其他选中，只选中当前。
    *   **多选**: 按住 Shift/Ctrl/Meta 点击，切换当前区域的选中状态。
    *   **取消选中**: 点击画布空白处 (`onCanvasMouseDown`)，清空 `selectedRegionIds`。
    *   **从多选切换单选**: 在多选状态下，**松开鼠标** (`onRegionMouseUp`) 且没有发生拖拽（位移 < 3px）时，切换为单选当前区域。
3.  **拖拽与移动 (`onDragging`)**:
    *   **单体移动**: 拖拽单个元素。
    *   **批量移动**: 如果当前拖拽的元素在选中集合中，计算其位移差 (`deltaX`, `deltaY`)，同步更新所有选中元素的坐标。
    *   **辅助线**: 计算当前拖拽元素中心点与画布中心点的距离，小于阈值时显示吸附辅助线。
4.  **缩放与旋转**:
    *   **缩放**: 通过组件自带句柄调整 `w, h`。
    *   **旋转**: 自定义旋转手柄，计算鼠标角度变化，更新 `rotation` 属性。

### 3.5 保存 (Save)

逻辑在 `index.vue` 的 `handleSave` 中：

1.  **数据组装**:
    *   收集 `templateInfo` (名称、宽高、背景图)。
    *   将 `regions` 数组序列化为 JSON 字符串 (`tempContent`)。
2.  **API 调用**: 调用 `fetchAddTemplate` 提交数据。
3.  **反馈**: 成功后提示并返回上一页。

## 4. 数据结构说明

### Region (区域)
```typescript
interface Region {
  id: string        // 唯一标识
  groupId: string   // 批次ID (同一次生成的区域共享)
  x: number         // X坐标
  y: number         // Y坐标
  w: number         // 宽度
  h: number         // 高度
  label: string     // 标签 (如 Q1)
  selected: boolean // (前端临时状态)
  rotation?: number // 旋转角度
}
```

### TemplateInfo (模板信息)
```typescript
interface TemplateInfo {
  name: string
  width: number
  height: number
  backGroundUrl?: string
}
```

### 核心原理
我们在前端渲染时，是基于 A4 标准尺寸 (210mm × 297mm) 进行比例换算的。

- 校验逻辑 ：如果系统认为当前画布宽度对应 210mm，那么我们可以在画布上绘制一个 10mm × 10mm 的标准网格。
- 测试方法 ：只需要上传一张 带有刻度尺 或 已知网格 的标准 A4 图片/PDF，开启系统网格，观察系统的红线是否与图片上的刻度重合，即可验证位置和比例是否精确。
### 网格辅助层和开关控制，具体修改如下：

1. TemplateCanvas.vue (画布组件)

   - 新增了 showGrid 属性和网格渲染层。
   - 自动计算：根据当前画布宽度（ templateInfo.width ）与 A4 宽度（210mm）的比例，动态计算出 10mm 在屏幕上的像素值。
   - 使用 SVG pattern 技术绘制高精度的 10mm 标准红线网格。
2. index.vue (主页面)

   - 在顶部工具栏增加了 "显示网格/隐藏网格" 按钮。
   - 现在的操作栏顺序为：返回 -> 标题 -> 网格开关 -> 上传按钮 -> 保存按钮。


### 如何进行测试（操作步骤）
1. 准备测试底图 ：找UI设计一张 A4 尺寸 (210x297mm) 的图片或 PDF，在上面画上 10mm 间隔的标尺或网格。（系统已支持直接上传 PDF，会自动渲染为高清底图）。
2. 上传底图 ：在系统中点击“上传底稿”上传该文件。
3. 开启网格 ：点击顶部新增的 "显示网格" 按钮。
4. 视觉验证 ：
   - 观察系统生成的 红色虚线网格 是否与您图片上的 标尺/网格 完美重合。
   - 如果重合，说明系统的坐标映射与物理 A4 纸张是完全对应的。
   - 如果不重合（例如系统网格偏大或偏小），则说明上传的图片本身比例不是标准的 A4 比例，或者存在边缘留白问题。



### A4 尺寸与像素换算说明
下面是页面渲染在页面的真实图片的信息：
Rendered size:	2100 × 2970 px
Rendered aspect ratio:	70∶99
Intrinsic size:	2100 × 2969 px
Intrinsic aspect ratio:	2100∶2969
File size:	526 kB
Current source:	https://oss.qyzhjy.com/temp/1769655687535/book.jpg

比如生成出来的图片尺寸 2100px × 2970px ， ，这是系统专门针对 A4 纸张优化的标准分辨率。
 1. 换算公式
我们的代码（在 useUpload.ts 中）在处理 PDF 转图片时，强制将宽度设定为 2100px 。基于 A4 标准宽度 210mm ，换算关系如下：
 [ o bj ec tO bj ec t ] 1 mm = 10 px
这个比例非常便于计算：

- 宽度 ： [ o bj ec tO bj ec t ] 210 mm × 10 = 2100 px
- 高度 ： [ o bj ec tO bj ec t ] 297 mm × 10 = 2970 px
- DPI ： [ o bj ec tO bj ec t ] 10 px/mm × 25.4 ≈ 254 DPI 2. 如何验证比例是否正确？
是的，只要确保**比例（Aspect Ratio）**是对的，且系统能正确识别这是 A4 纸即可。

我在代码中已经为您实现了 动态网格系统 ，它不依赖固定的像素值，而是根据当前图片宽度反向计算：

1. 系统读取当前图片宽度（例如 2100px）。
2. 系统知道这是 A4 纸（210mm）。
3. 系统自动计算出 10mm = (2100 / 21) = 100px 。
4. 系统在屏幕上绘制间隔为 100px 的网格线。
验证方法： 如果您上传了一张分辨率较低的图片（例如 1050px 宽），系统会自动计算出 1mm = 5px ，网格线会变为 50px 间隔。 只要网格线能与您图片上的物理刻度对齐，就说明换算和位置是绝对精准的。
 3. 结论
- 您当前的图片 2100 x 2970 是 完美标准 。
- 如果您上传其他尺寸，只要长宽比接近 A4 (1:1.414)，我们的网格系统都能自动适配并准确显示 10mm 刻度。
- 您可以使用我在顶部新增的 “显示网格” 按钮来直观验证这一点。
