# Doodle Mini - 技术架构设计文档

> 版本：v3.2
> 最后更新：2026-03-27
> 配套文档：[现有功能清单](./现有功能清单.md) | [产品设计文档](./产品设计文档.md) | [小程序云开发方案](./小程序云开发方案.md) | [后端部署方案](./后端部署方案.md) | [PC-Web端技术预案](./PC-Web端技术预案.md)

**后端阶段说明（重要）**：**现阶段**后端能力采用**微信小程序云开发**（云数据库、云存储、云函数），与 [小程序云开发方案](./小程序云开发方案.md) 一致。**当前阶段有效期至 2026 年 9 月 15 日**（到期前需结合用量、成本与产品路线，决定续用云开发、升级套餐或迁移至 [后端部署方案](./后端部署方案.md) 所述 NestJS + 自有服务器方案）。前端仍通过 `platform/cloud-adapter` 对接 `wx.cloud`，迁移时再抽象为 HTTP 适配层。

---

## 一、技术决策总览

### 1.1 核心原则


| 原则       | 说明                                                      |
| -------- | ------------------------------------------------------- |
| **成本优先** | 现阶段用云开发免费额度控制成本；**2026-09-15** 前复盘是否续用或迁移自建；核心逻辑仍保持前端执行 |
| **渐进增强** | 先小程序跑通，后续再扩展 PC Web，不为未来过度设计                            |
| **前端为主** | Canvas 渲染、随机生成、PNG 导出等核心逻辑保持前端执行，零服务器成本；后端仅做必要的数据存储和服务  |
| **可迁移**  | 核心绘制逻辑与平台 API 解耦，为将来迁移 Web 做准备                          |


### 1.2 后端方案决策

#### 需要后端的场景 vs 不需要的场景

```
┌─────────────────────────────────────────────────────────┐
│                     不需要后端（前端完成）                 │
├─────────────────────────────────────────────────────────┤
│  ✅ Canvas 绘制所有题型                                  │
│  ✅ 随机题目生成算法                                     │
│  ✅ A4 排版与打印预览                                    │
│  ✅ PNG 导出 → 保存相册                                  │
│  ✅ 题型配置数据（可前端内置 JSON）                        │
│  ✅ 分享功能                                             │
│  ✅ 本地收藏/历史（wx.Storage）                           │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│                  需要后端（轻量级）                        │
├─────────────────────────────────────────────────────────┤
│  📦 内容配置远程下发（热更新题型/分类，无需发版）            │
│  📦 静态素材存储与分发（涂色卡线稿、折纸模板等）            │
│  📦 用户数据同步（收藏、历史记录跨设备）                    │
│  📦 下载统计 / 热度排名                                   │
│  📦 用户标识 / 鉴权（云开发侧 OpenID 等）                   │
│  📦 PDF 多页合并导出（远期增值功能，非核心路径）             │
│  📦 未来 PC Web 端的 API 服务                             │
└─────────────────────────────────────────────────────────┘
```

#### 方案对比与选择


| 维度        | 微信云开发              | 自有云服务器（NestJS）     | 现阶段决策（至 2026-09-15） |
| --------- | ------------------ | ------------------ | ------------------- |
| 接入成本      | ⭐ 极低（原生集成）         | ⭐⭐ 需搭建部署（~3-5天）    | **云开发 ✅**           |
| 运维成本      | ⭐ 免运维              | ⭐⭐ 需简单维护（PM2 守护）   | **云开发 ✅**           |
| 费用        | 免费额度内 ¥0；超出按套餐计费   | 已有服务器可零增量          | **云开发（阶段内）✅**       |
| 小程序集成     | ⭐ 鉴权零成本            | ⭐⭐ 需对接微信登录+JWT     | **云开发 ✅**           |
| PC Web 支持 | ⭐⭐ 需通过 HTTP API 桥接 | ⭐ RESTful API 天然支持 | 远期再评估 NestJS        |
| 灵活性       | ⭐⭐ 受限于云开发 SDK      | ⭐ 完全自由             | 到期后按需评估             |
| 数据迁移      | ⭐⭐ 可导出             | ⭐ 标准 MySQL         | 到期后按需评估             |


**最终决策（分阶段）**

```
现阶段（至 2026-09-15）：微信小程序云开发
  └── 云数据库 + 云存储 + 云函数；详情见 → 小程序云开发方案.md

阶段到期后：在 2026-09-15 前复盘，可选路径包括
  ① 续用云开发（含升级套餐）
  ② 迁移至 NestJS + 自有服务器 → 后端部署方案.md
  ③ 混合方案（按模块拆分）

平台适配层保持抽象：当前 cloud-adapter（wx.cloud），迁移时可替换为 http-adapter，
  业务服务层接口尽量不变（见 platform/ 设计）。
```

---

## 二、系统架构总图

```
┌────────────────────────────────────────────────────────────────┐
│                     微信小程序（当前重心）                       │
│                                                                │
│  ┌──────────────────────────────────────────────────────┐      │
│  │  UI 层 (WXML + WXSS)                                 │      │
│  │  pages/ + components/ — Skyline 渲染                  │      │
│  └────────────────────────┬─────────────────────────────┘      │
│                           │                                    │
│  ┌────────────────────────▼─────────────────────────────┐      │
│  │  业务服务层 (services/ + store/)                       │      │
│  │  worksheet-service / print-service / user-service     │      │
│  └────────────────────────┬─────────────────────────────┘      │
│                           │                                    │
│  ┌────────────────────────▼─────────────────────────────┐      │
│  │  平台适配层 (platform/)                                │      │
│  │  canvas-adapter / storage-adapter / cloud-adapter     │      │
│  └────────────────────────┬─────────────────────────────┘      │
│                           │                                    │
│  ┌────────────────────────▼─────────────────────────────┐      │
│  │  共享核心层 (core/) — 纯 TypeScript，零平台依赖        │      │
│  │  ┌─────────────┐ ┌────────────┐ ┌────────────────┐  │      │
│  │  │ 模板引擎     │ │ 生成器      │ │ 数据模型/工具   │  │      │
│  │  │ 8种渲染器    │ │ 15种生成器  │ │ WorksheetConfig│  │      │
│  │  └─────────────┘ └────────────┘ └────────────────┘  │      │
│  │  🔑 此层可直接在未来 PC Web 项目中 import 复用        │      │
│  └──────────────────────────────────────────────────────┘      │
└───────────────────────────┬────────────────────────────────────┘
                            │  wx.cloud / callFunction
                            ▼
┌────────────────────────────────────────────────────────────────┐
│                        后端服务层（现阶段：微信云开发）            │
│                                                                │
│  ┌──────────────────────────────────────────────────────┐      │
│  │             微信云开发                                  │      │
│  │                                                      │      │
│  │  ┌────────────┐  ┌──────────┐  ┌─────────────┐     │      │
│  │  │  云数据库    │  │  云存储   │  │   云函数     │     │      │
│  │  │ (MongoDB)   │  │  (COS)   │  │  (Node.js)  │     │      │
│  │  │             │  │          │  │             │     │      │
│  │  │ • 题型配置  │  │ • 涂色卡  │  │ • getOpenId │     │      │
│  │  │ • 用户数据  │  │ • 折纸图  │  │ • syncData  │     │      │
│  │  │ • 收藏记录  │  │ • 手工图  │  │ • stats     │     │      │
│  │  │ • 下载统计  │  │ • 预览图  │  │ • genPDF    │     │      │
│  │  │ • 反馈数据  │  │ • 字体    │  │   (增值)    │     │      │
│  │  └────────────┘  └──────────┘  └─────────────┘     │      │
│  └──────────────────────────────────────────────────────┘      │
│                                                                │
│  ┌──────────────────────────────────────────────────────┐      │
│  │                   CDN（云开发资源）                     │      │
│  │  素材图片 / 字体文件 / 预览图 / 分享图                 │      │
│  └──────────────────────────────────────────────────────┘      │
│                                                                │
│  云开发详细设计 → 小程序云开发方案.md                           │
│  阶段到期后可选迁移 → 后端部署方案.md                           │
│  PC Web 远期方案 → PC-Web端技术预案.md                         │
└────────────────────────────────────────────────────────────────┘
```

---

## 三、前端架构详细设计

### 3.1 现有架构问题

```
当前代码组织：
miniprogram/
├── pages/              ← 主包页面，各自直接引用 mixin
├── mathPages/          ← 数学分包，每个页面有自己的 Draw 服务
│   └── shared/         ← 数学共用（service、templates、assets）
├── focusPages/         ← 专注力分包，结构同上
│   └── shared/
├── service/            ← 识字/练字的 Draw 服务
├── base/               ← pageMixin、callCloud 等基础层
├── components/         ← UI 组件
├── constants/          ← 静态配置数据
└── utils/              ← 工具函数
```

**问题分析：**


| 问题         | 详情                                                            |
| ---------- | ------------------------------------------------------------- |
| Mixin 模式笨重 | `pageMixin` 通过对象展开合并，无类型安全，难以追踪数据流                            |
| Draw 服务分散  | 每个分包下各有 `shared/service/`，大量重复的绘制工具方法                         |
| 平台耦合严重     | Draw 服务直接使用 `wx.createImage`、`canvas.createImage` 等 API       |
| 模板重复       | `mathPages` 和 `focusPages` 的 `canvas-page-template.wxml` 内容相同 |
| 无共享核心      | 数据模型、生成算法与 UI 逻辑混在一起，无法跨平台复用                                  |
| 状态管理原始     | 纯 `setData`，无统一的状态管理方案                                        |


### 3.2 目标架构

```
miniprogram/
├── core/                          ← 🆕 共享核心层（平台无关，可被主包/分包复用）
│   ├── models/                    ← 数据模型定义
│   │   ├── worksheet.ts           ← WorksheetType 统一模型
│   │   ├── category.ts            ← 分类模型
│   │   ├── user.ts                ← 用户数据模型
│   │   └── print-config.ts        ← 打印配置模型
│   ├── generators/                ← 题目生成算法（仅保留跨分包通用部分）
│   │   ├── common/                ← 公共算法（如随机、序列、通用算术）
│   │   └── registry.ts            ← 生成器注册与装配
│   ├── draw/                      ← 绘制服务（仅保留跨分包通用部分）
│   │   ├── base-draw.ts           ← 基础绘制（纸面、页眉、网格等）
│   │   ├── template-engine.ts     ← 🆕 模板引擎入口
│   │   ├── renderer-registry.ts   ← 🆕 渲染器注册表
│   │   ├── base-template-renderer.ts ← 🆕 渲染器基类
│   │   ├── renderers/             ← 🆕 各排版模板渲染器（6-8 种）
│   │   │   ├── grid-exercise.ts
│   │   │   ├── match-connect.ts
│   │   │   ├── grid-coloring.ts
│   │   │   ├── card-layout.ts
│   │   │   ├── tracing-writing.ts
│   │   │   ├── full-page-asset.ts
│   │   │   ├── sequence-pattern.ts
│   │   │   └── special-graphic.ts
│   │   └── legacy/                ← 存量中仍可复用的通用兼容层（逐步收敛）
│   ├── data/                      ← 内容配置数据
│   │   ├── worksheets.ts          ← 所有题型定义（本地兜底）
│   │   ├── categories.ts          ← 分类定义
│   │   ├── words.ts               ← 词库
│   │   └── difficulty.ts          ← 难度与年龄映射
│   └── utils/                     ← 纯工具函数（无平台依赖）
│       ├── random.ts
│       ├── math-utils.ts
│       └── format.ts
│
├── platform/                      ← 🆕 平台适配层
│   ├── canvas-adapter.ts          ← Canvas API 适配（wx ↔ Web）
│   ├── storage-adapter.ts         ← 存储适配（wxStorage ↔ localStorage）
│   ├── cloud-adapter.ts           ← 云服务适配（wx.cloud；迁移 NestJS 后可换 http-adapter）
│   ├── image-adapter.ts           ← 图片加载适配
│   └── share-adapter.ts           ← 分享能力适配
│
├── services/                      ← 🆕 业务服务层
│   ├── worksheet-service.ts       ← 题型数据加载（本地 + 远程兜底）
│   ├── user-service.ts            ← 用户数据管理
│   ├── favorite-service.ts        ← 收藏管理
│   ├── history-service.ts         ← 下载历史管理
│   ├── print-service.ts           ← 打印/导出统一入口
│   ├── stats-service.ts           ← 统计上报
│   └── ad-service.ts              ← 广告管理
│
├── store/                         ← 🆕 状态管理
│   ├── app-store.ts               ← 全局状态（用户/配置/主题）
│   └── page-store.ts              ← 页面级状态工具
│
├── pages/                         ← 主包（受 2MB 限制，只放高频入口与通用页）
│   ├── home/                      ← 发现首页
│   ├── category/                  ← 分类详情页（仅列表，不承载重绘制逻辑）
│   ├── profile/                   ← 我的
│   └── guide/                     ← 打印指南
│
├── mathPages/                     ← 数学分包
│   ├── ...                        ← 数学题型页面
│   └── shared/                    ← 仅数学分包依赖（service/templates/assets/constants/draw/generators）
├── chinesPages/                   ← 语文分包（命名可按实现统一为 chinesePages）
│   ├── ...                        ← 识字/练字及后续语文题型页面
│   └── shared/                    ← 仅语文分包依赖（含语文专用 draw/generators）
├── englishPages/                  ← 英语分包
│   ├── ...                        ← 字母/拼读等页面
│   └── shared/                    ← 仅英语分包依赖（含英语专用 draw/generators）
├── puzzlePages/                   ← 益智分包
│   ├── ...                        ← 迷宫/找不同/连线等页面
│   └── shared/                    ← 仅益智分包依赖（含益智专用 draw/generators）
├── craftPages/                    ← 创意手工分包
│   ├── ...                        ← 涂色卡/折纸/手工模板页面
│   └── shared/                    ← 仅创意手工分包依赖（含手工专用 draw/generators）
│
├── components/                    ← UI 组件
│   ├── shared/                    ← 通用组件
│   │   ├── worksheet-card/        ← 题型卡片（带预览图）
│   │   ├── category-tabs/         ← 分类标签栏
│   │   ├── age-filter/            ← 年龄筛选器
│   │   ├── difficulty-badge/      ← 难度标签
│   │   ├── canvas-preview/        ← Canvas 预览区
│   │   ├── action-bar/            ← 底部操作栏（保存/分享/换一批）
│   │   ├── print-header/          ← 打印页眉配置
│   │   └── empty-state/           ← 空状态
│   └── business/                  ← 业务组件
│       ├── worksheet-list/        ← 题型列表
│       ├── param-panel/           ← 参数设置面板
│       └── ...
│
├── config/                        ← 配置
├── assets/                        ← 静态资源
└── style/                         ← 全局样式
```

> 分包约束（新增）：为控制主包体积与首屏加载，**题型页面、重绘制逻辑、题型私有素材、题型私有工具、题型私有 draw 与 generators** 必须下沉到对应分包；仅该分包使用的依赖统一放到该分包的 `shared/` 目录中（延续当前 `mathPages/shared`、`focusPages/shared` 的组织方式）。
>
> 归属原则（draw / generators）：
>
> 1. **跨分包复用**（2 个及以上分包共用）→ 放 `core/draw`、`core/generators`；
> 2. **仅单一分包使用** → 放对应 `<xxx>Pages/shared/draw`、`<xxx>Pages/shared/generators`；
> 3. 如后续从“单分包”演进为“跨分包”，再从分包 `shared` 上提到 `core`，避免过早抽象导致主包膨胀。

### 3.3 分层职责

```
┌──────────────────────────────────────────────────────┐
│                      UI 层                            │
│  pages/ + components/ (WXML + WXSS + 页面 TS)        │
│  职责：界面渲染、用户交互、调用 services              │
├──────────────────────────────────────────────────────┤
│                    业务服务层                          │
│  services/ + store/                                  │
│  职责：组合 core 能力，管理状态，对接平台 API          │
├──────────────────────────────────────────────────────┤
│                    平台适配层                          │
│  platform/                                           │
│  职责：抹平 wx.* 和 Web API 差异                     │
├──────────────────────────────────────────────────────┤
│                    共享核心层                          │
│  core/ (纯 TypeScript，零平台依赖)                    │
│  职责：绘制服务、生成算法、数据模型                    │
│  🔑 可直接在 Web 项目中 import 使用                   │
└──────────────────────────────────────────────────────┘
```

---

## 四、共享核心层设计（@doodle/core）

这是未来跨平台复用的关键。所有与 `wx.*` 无关的逻辑都应该放在这一层。

### 4.1 Canvas 适配器接口

```typescript
/**
 * 平台无关的 Canvas 上下文接口
 * 微信小程序和 Web 的 Canvas 2D API 基本一致，
 * 但图片加载、字体加载等需要适配
 */
interface ICanvasAdapter {
    getContext(): CanvasRenderingContext2D;
    setSize(width: number, height: number): void;
    loadImage(src: string): Promise<CanvasImageSource>;
    toDataURL(type?: string, quality?: number): Promise<string>;
    toBlob(): Promise<Blob>; // Web 专用
    toTempFilePath(): Promise<string>; // 小程序专用
}
```

### 4.2 绘制服务基类（平台无关）

```typescript
abstract class BaseDrawService {
    protected ctx: CanvasRenderingContext2D;
    protected paperWidth: number;
    protected paperHeight: number;

    // 这些方法仅使用标准 Canvas 2D API，不依赖任何平台
    protected drawGrid(rows: number, cols: number): void {
        /* ... */
    }
    protected drawText(
        text: string,
        x: number,
        y: number,
        options: TextOptions,
    ): void {
        /* ... */
    }
    protected drawLine(x1: number, y1: number, x2: number, y2: number): void {
        /* ... */
    }
    protected drawHeader(config: PrintConfig): void {
        /* ... */
    }
    protected drawRoundedRect(
        x: number,
        y: number,
        w: number,
        h: number,
        r: number,
    ): void {
        /* ... */
    }

    abstract draw(params: DrawParams): Promise<void>;
}
```

### 4.3 题目生成器模式

```typescript
/**
 * 所有题目生成器实现此接口
 * 输入配置参数，输出与平台无关的题目数据
 * 绘制服务消费这些数据来渲染 Canvas
 */
interface IWorksheetGenerator<TConfig, TData> {
    generate(config: TConfig): TData;
    getDefaultConfig(): TConfig;
}

// 示例：加法题生成器
class AdditionGenerator
    implements IWorksheetGenerator<AdditionConfig, AdditionData>
{
    generate(config: AdditionConfig): AdditionData {
        const questions = [];
        for (let i = 0; i < config.count; i++) {
            const a = randomInt(0, config.maxNumber);
            const b = randomInt(0, config.maxNumber - a);
            questions.push({ a, b, answer: a + b });
        }
        return { questions, config };
    }
}
```

---

## 五、模板引擎架构（核心设计）

> 本章解决一个根本性问题：**每新增一种可打印资料，都需要新写前端页面和绘制代码，如何实现内容的动态扩展？**

### 5.1 问题分析

现有模式下，新增一种题型的完整链路是：

```
新增「时钟练习」题型：
  ① 新建 clockPractice/ 页面目录（.ts + .wxml + .wxss + .json）
  ② 新写 ClockPracticeDraw 绘制服务（~200-400 行 TS）
  ③ 新写 ClockGenerator 生成器
  ④ 在 constants 注册题型元数据
  ⑤ 提交代码 → 审核 → 发版
  ─────────────────────────
  成本：~1-3 天开发 + 1-3 天审核
```

如果要达到产品目标的 150+ 种可打印资料，按这种模式需要写 150+ 个页面和绘制服务，**不可持续**。

### 5.2 核心洞察：排版模式是收敛的

虽然题型看起来有几十上百种，但**排版模式（Layout Pattern）只有有限的几种**：

```
┌──────────────────────────────────────────────────────────────────┐
│                                                                  │
│  模式 A：网格计算型                                               │
│  ┌────────────────────────┐                                      │
│  │  3 + 5 = ___           │  适用：加减法、混合运算、凑十/破十、  │
│  │  7 - 2 = ___           │  乘法口诀、数字填空、比大小等        │
│  │  9 + 1 = ___           │                                      │
│  │  ...                   │  变化点：题目数据、运算符、行列数、   │
│  └────────────────────────┘  是否带图示辅助                      │
│                                                                  │
│  模式 B：配对连线型                                               │
│  ┌────────────────────────┐                                      │
│  │  3  ╌╌╌╌  🍎🍎🍎       │  适用：数物连线、连连看、数字点连线、│
│  │  5  ╌╌╌╌  🍎🍎         │  译码连线、配对连线等              │
│  │  2  ╌╌╌╌  🍎🍎🍎🍎     │                                      │
│  └────────────────────────┘  变化点：左右两列内容、连线方式      │
│                                                                  │
│  模式 C：网格涂色型                                               │
│  ┌────────────────────────┐                                      │
│  │  ┌─┬─┬─┬─┬─┐          │  适用：找数字涂色、按数涂色、方位涂  │
│  │  │3│7│3│5│3│          │  色、格子仿画、图形符号配对等        │
│  │  ├─┼─┼─┼─┼─┤          │                                      │
│  │  │2│3│8│3│1│          │  变化点：网格大小、单元格内容/颜色、  │
│  └────────────────────────┘  涂色规则说明                        │
│                                                                  │
│  模式 D：卡片排列型                                               │
│  ┌────────────────────────┐                                      │
│  │  [图片]  [图片]         │  适用：涂色卡、闪卡、识字卡、字母卡、│
│  │  苹果    香蕉           │  贴纸、翻翻卡等                    │
│  │  [图片]  [图片]         │                                      │
│  └────────────────────────┘  变化点：图片源、文字、布局方式      │
│                                                                  │
│  模式 E：描红书写型                                               │
│  ┌────────────────────────┐                                      │
│  │  ┌田┐┌田┐┌田┐         │  适用：练字、笔画练习、字母描红、    │
│  │  │大││大││  │         │  数字描红、拼音描红等               │
│  │  └──┘└──┘└──┘         │                                      │
│  └────────────────────────┘  变化点：字符数据、格子类型、淡化规则│
│                                                                  │
│  模式 F：全幅素材型                                               │
│  ┌────────────────────────┐                                      │
│  │                        │  适用：涂色卡（整页）、折纸展开图、  │
│  │   [整页图片/SVG]        │  手工模板、迷宫底图等              │
│  │                        │                                      │
│  └────────────────────────┘  变化点：素材文件本身               │
│                                                                  │
│  模式 G：序列/排序型                                              │
│  ┌────────────────────────┐                                      │
│  │  2 → __ → 4 → __ → 6  │  适用：数字排序、缺失数字、颜色找    │
│  │                        │  规律、图形规律等                    │
│  └────────────────────────┘  变化点：序列数据、空位位置          │
│                                                                  │
│  模式 H：时钟/特殊图形型                                          │
│  ┌────────────────────────┐                                      │
│  │    🕐  → 3:00          │  适用：时钟练习、形状分类、货币认知  │
│  │    🕑  → ___           │  等需要特殊图形绘制的题型            │
│  └────────────────────────┘                                      │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘
```

**结论：只需要实现 6-8 种 TemplateRenderer，就能覆盖绝大多数题型。**

### 5.3 模板引擎架构

```
┌──────────────────────────────────────────────────────────────┐
│                      模板引擎工作流                            │
│                                                              │
│                    ┌─────────────┐                           │
│                    │ 题型 JSON   │ ← 本地内置 / 云端下发      │
│                    │ 配置数据    │                            │
│                    └──────┬──────┘                           │
│                           │                                  │
│              ┌────────────┼────────────┐                     │
│              ▼            ▼            ▼                     │
│     ┌────────────┐ ┌───────────┐ ┌──────────┐              │
│     │ template   │ │ generator │ │ layout   │              │
│     │ 选择渲染器  │ │ 选择生成器│ │ 排版参数  │              │
│     └─────┬──────┘ └─────┬─────┘ └────┬─────┘              │
│           │              │             │                     │
│           ▼              ▼             │                     │
│     ┌───────────┐  ┌──────────┐       │                     │
│     │ Template  │  │Generator │       │                     │
│     │ Renderer  │  │ 生成数据  │       │                     │
│     │ (6-8种)   │  │          │       │                     │
│     └─────┬─────┘  └────┬─────┘       │                     │
│           │             │              │                     │
│           ▼             ▼              ▼                     │
│     ┌────────────────────────────────────┐                  │
│     │     Canvas 2D 渲染                  │                  │
│     │  TemplateRenderer.render(data, layout)                │
│     └───────────────────┬────────────────┘                  │
│                         │                                    │
│                         ▼                                    │
│                  ┌─────────────┐                             │
│                  │  A4 打印图片 │                             │
│                  └─────────────┘                             │
└──────────────────────────────────────────────────────────────┘
```

### 5.4 JSON 配置 Schema

每个题型由一条 JSON 配置定义，包含三个核心部分：「用哪个模板渲染」「用哪个生成器产出数据」「排版参数」。

```typescript
/**
 * 题型配置（可云端下发，可前端内置）
 */
interface WorksheetConfig {
    // ─── 元数据（展示用）───
    id: string;
    title: string;
    desc: string;
    category: 'math' | 'chinese' | 'english' | 'puzzle' | 'craft';
    subcategory: string;
    ageRange: [number, number];
    difficulty: 1 | 2 | 3 | 4;
    previewImage: string;
    tags: string[];
    isNew: boolean;
    sortOrder: number;
    status: 'active' | 'draft' | 'hidden';

    // ─── 模板引擎配置（渲染用）───
    template: TemplateType; // 使用哪个排版模板
    generator: GeneratorType; // 使用哪个数据生成器
    generatorConfig: Record<string, any>; // 生成器参数（不同生成器不同）
    layoutConfig: LayoutConfig; // 排版参数
    userConfigurable?: UserConfigField[]; // 用户可调整的参数定义
}

/**
 * 排版模板枚举（前端代码实现，新增需发版）
 */
type TemplateType =
    | 'grid-exercise' // 模式 A：网格计算型
    | 'match-connect' // 模式 B：配对连线型
    | 'grid-coloring' // 模式 C：网格涂色型
    | 'card-layout' // 模式 D：卡片排列型
    | 'tracing-writing' // 模式 E：描红书写型
    | 'full-page-asset' // 模式 F：全幅素材型
    | 'sequence-pattern' // 模式 G：序列/排序型
    | 'special-graphic'; // 模式 H：时钟/特殊图形型

/**
 * 数据生成器枚举（前端代码实现，新增需发版）
 */
type GeneratorType =
    | 'arithmetic' // 加减乘除运算题
    | 'number-sequence' // 数列/排序
    | 'number-decompose' // 数的分与合
    | 'counting' // 数数类
    | 'comparison' // 比大小
    | 'shape-grid' // 图形网格
    | 'color-pattern' // 颜色规律
    | 'character-tracing' // 汉字描红（需 SVG 笔画数据）
    | 'letter-tracing' // 字母描红
    | 'pinyin-tracing' // 拼音描红
    | 'static-asset' // 静态素材（不生成，直接用素材）
    | 'maze' // 迷宫算法
    | 'dot-connect' // 点连线
    | 'clock' // 时钟
    | 'custom'; // 自定义（需指定 customGeneratorId）

/**
 * 排版配置
 */
interface LayoutConfig {
    columns?: number; // 列数
    rows?: number; // 行数
    fontSize?: number; // 字号
    showBorder?: boolean; // 是否显示边框
    showTitle?: boolean; // 是否显示标题
    padding?: number; // 内边距
    itemSpacing?: number; // 元素间距
    showInstruction?: boolean; // 是否显示题目说明文字
    instructionText?: string; // 说明文字内容
}

/**
 * 用户可配置的参数定义（渲染到参数设置面板）
 */
interface UserConfigField {
    key: string; // 对应 generatorConfig 中的 key
    label: string; // 显示名称
    type: 'select' | 'slider' | 'switch';
    options?: { label: string; value: any }[]; // select 类型的选项
    min?: number; // slider 最小值
    max?: number; // slider 最大值
    defaultValue: any;
}
```

### 5.5 配置示例

**示例 1：算法生成型 — 10 以内加法**

```json
{
    "id": "addition-10",
    "title": "10以内加法",
    "desc": "练习10以内的加法运算，图形化展示",
    "category": "math",
    "subcategory": "basic-arithmetic",
    "ageRange": [4, 6],
    "difficulty": 2,

    "template": "grid-exercise",
    "generator": "arithmetic",
    "generatorConfig": {
        "operators": ["+"],
        "maxNumber": 10,
        "count": 20,
        "showDots": true,
        "showAnswer": false
    },
    "layoutConfig": {
        "columns": 2,
        "showBorder": true,
        "fontSize": 18,
        "showInstruction": true,
        "instructionText": "算一算，填上答案"
    },
    "userConfigurable": [
        {
            "key": "count",
            "label": "题目数量",
            "type": "select",
            "options": [
                { "label": "10 题", "value": 10 },
                { "label": "20 题", "value": 20 },
                { "label": "30 题", "value": 30 }
            ],
            "defaultValue": 20
        },
        {
            "key": "showDots",
            "label": "显示圆点辅助",
            "type": "switch",
            "defaultValue": true
        }
    ]
}
```

**示例 2：素材型 — 恐龙涂色卡**

```json
{
    "id": "coloring-dinosaur-01",
    "title": "恐龙涂色卡",
    "desc": "可爱恐龙线稿，涂出你喜欢的颜色",
    "category": "craft",
    "subcategory": "coloring",
    "ageRange": [3, 6],
    "difficulty": 1,

    "template": "full-page-asset",
    "generator": "static-asset",
    "generatorConfig": {
        "assetUrl": "cloud://doodle-xxx/assets/coloring/dinosaur-01.svg",
        "assetType": "svg"
    },
    "layoutConfig": {
        "padding": 20,
        "showTitle": true
    },
    "userConfigurable": []
}
```

**示例 3：混合型 — 字母描红**

```json
{
    "id": "letter-tracing-uppercase",
    "title": "大写字母描红",
    "desc": "A-Z 大写字母描红练习",
    "category": "english",
    "subcategory": "letter-tracing",
    "ageRange": [4, 7],
    "difficulty": 1,

    "template": "tracing-writing",
    "generator": "letter-tracing",
    "generatorConfig": {
        "letters": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
        "case": "upper",
        "lettersPerPage": 4,
        "repetitions": 6,
        "gridType": "four-line"
    },
    "layoutConfig": {
        "showInstruction": true,
        "instructionText": "沿虚线描写字母，注意笔画顺序"
    },
    "userConfigurable": [
        {
            "key": "lettersPerPage",
            "label": "每页字母数",
            "type": "select",
            "options": [
                { "label": "2 个", "value": 2 },
                { "label": "4 个", "value": 4 },
                { "label": "6 个", "value": 6 }
            ],
            "defaultValue": 4
        }
    ]
}
```

### 5.6 TemplateRenderer 实现结构

```typescript
/**
 * 所有模板渲染器的基类
 */
abstract class BaseTemplateRenderer {
    protected ctx: CanvasRenderingContext2D;
    protected paper: PaperConfig;

    constructor(ctx: CanvasRenderingContext2D, paper: PaperConfig) {
        this.ctx = ctx;
        this.paper = paper;
    }

    async render(
        data: any,
        layout: LayoutConfig,
        printConfig: PrintConfig,
    ): Promise<void> {
        this.drawBackground();
        this.drawHeader(printConfig);
        if (layout.showInstruction) {
            this.drawInstruction(layout.instructionText!);
        }
        await this.drawContent(data, layout);
        this.drawFooter(printConfig);
    }

    protected abstract drawContent(
        data: any,
        layout: LayoutConfig,
    ): Promise<void>;
}

/**
 * 模式 A：网格计算型渲染器
 * 可渲染所有「一行一题」或「多列排布」的练习题
 */
class GridExerciseRenderer extends BaseTemplateRenderer {
    protected async drawContent(data: ArithmeticData, layout: LayoutConfig) {
        const { columns = 2, fontSize = 16, showBorder = true } = layout;
        const { questions } = data;
        // 按 columns 分列排布，每题绘制：操作数 + 运算符 + 等号 + 空位
        // 如果 showDots，绘制圆点图示辅助
    }
}

/**
 * 模式 F：全幅素材型渲染器
 * 加载图片/SVG → 居中适配到 A4 页面
 */
class FullPageAssetRenderer extends BaseTemplateRenderer {
    protected async drawContent(data: AssetData, layout: LayoutConfig) {
        const image = await this.loadImage(data.assetUrl);
        // 计算等比缩放，居中绘制到 A4 内容区
    }
}
```

```
core/draw/
├── base-template-renderer.ts       ← 渲染器基类
├── renderers/
│   ├── grid-exercise.ts            ← 模式 A：网格计算型
│   ├── match-connect.ts            ← 模式 B：配对连线型
│   ├── grid-coloring.ts            ← 模式 C：网格涂色型
│   ├── card-layout.ts              ← 模式 D：卡片排列型
│   ├── tracing-writing.ts          ← 模式 E：描红书写型
│   ├── full-page-asset.ts          ← 模式 F：全幅素材型
│   ├── sequence-pattern.ts         ← 模式 G：序列/排序型
│   └── special-graphic.ts          ← 模式 H：时钟/特殊图形型
├── renderer-registry.ts            ← 渲染器注册表
└── template-engine.ts              ← 模板引擎入口
```

### 5.7 模板引擎入口

```typescript
/**
 * 模板引擎：根据 JSON 配置，选择渲染器和生成器，完成绘制
 * 这是通用 worksheet 页面的核心调用逻辑
 */
class TemplateEngine {
    private rendererRegistry: Map<TemplateType, BaseTemplateRenderer>;
    private generatorRegistry: Map<GeneratorType, IWorksheetGenerator>;

    async render(
        worksheetConfig: WorksheetConfig,
        userParams: Record<string, any>, // 用户在 UI 上调整的参数
        ctx: CanvasRenderingContext2D,
        printConfig: PrintConfig,
    ): Promise<void> {
        // 1. 合并用户参数到 generatorConfig
        const mergedConfig = {
            ...worksheetConfig.generatorConfig,
            ...userParams,
        };

        // 2. 选择生成器，生成题目数据
        const generator = this.generatorRegistry.get(worksheetConfig.generator);
        const data = generator.generate(mergedConfig);

        // 3. 选择渲染器，绘制到 Canvas
        const renderer = this.rendererRegistry.get(worksheetConfig.template);
        await renderer.render(data, worksheetConfig.layoutConfig, printConfig);
    }
}
```

### 5.8 通用 Worksheet 页面

有了模板引擎后，**不再需要为每种题型创建独立页面**。只需一个通用页面：

```
pages/worksheet/worksheet          ← 通用题型生成页
```

```typescript
// pages/worksheet/worksheet.ts（伪代码）
createPage({
    onLoad(options) {
        const worksheetId = options.id;
        // 从本地缓存或云端获取该题型的 JSON 配置
        const config = WorksheetService.getConfig(worksheetId);
        this.setData({
            title: config.title,
            userFields: config.userConfigurable, // 动态渲染参数面板
            userParams: getDefaultParams(config),
        });
    },
    onCanvasReady() {
        this.drawCanvas();
    },
    async drawCanvas() {
        await TemplateEngine.render(
            this.config,
            this.data.userParams,
            this.ctx,
            getApp().globalData.printConfig,
        );
    },
    onParamChange(e) {
        // 用户调整参数 → 重新绘制
        this.setData({ userParams: { ...this.data.userParams, ...e.detail } });
        this.drawCanvas();
    },
    onRefresh() {
        // 换一批 → 重新生成+绘制
        this.drawCanvas();
    },
});
```

### 5.9 什么能动态更新 vs 什么需要发版

```
┌────────────────────────────────────────────────────────────────┐
│                                                                │
│  ✅ 可动态更新（修改 JSON 配置 / 上传素材，无需发版）           │
│  ──────────────────────────────────────────────────            │
│  • 已有模板+已有生成器 组合出的新题型                           │
│    （例：用 grid-exercise + arithmetic 配出「50以内减法」）     │
│  • 修改题型参数（数量范围、难度、显示选项等）                   │
│  • 上下架/排序/分类调整/标签修改                               │
│  • 新增素材型内容（涂色卡、折纸模板、闪卡图片等）              │
│  • 修改页眉/页脚样式配置                                       │
│  • 新增分类/子分类                                             │
│                                                                │
│  ❌ 需要发版（新增前端代码）                                    │
│  ──────────────────────────────────────────────────            │
│  • 全新的排版模板（TemplateRenderer）                          │
│  • 全新的生成算法（Generator）                                 │
│  • 新的用户交互模式                                             │
│  • 新的 UI 组件                                                 │
│                                                                │
│  📊 预估覆盖率                                                  │
│  ──────────────────────────────────────────────────            │
│  • 实现 8 种模板 + 15 种生成器后                                │
│  • 约 80% 的新题型可通过 JSON 配置动态上线                      │
│  • 剩余 20% 需要新模板/生成器，但复用已有基础代码               │
│                                                                │
└────────────────────────────────────────────────────────────────┘
```

### 5.10 存量题型迁移策略

现有 40+ 种题型已经稳定运行，**不建议一次性全部推翻重写**。推荐渐进式迁移：

```
阶段 1（与模板引擎同步开发）：
├── 新题型全部走模板引擎
└── 旧题型保持原有专用 DrawService，正常运行

阶段 2（按模板类型逐批迁移）：
├── 批次 1：所有 grid-exercise 类
│   └── addition, subtraction, calculationPractice,
│       missingNumber, compare, makeTen, breakTen... (~15 种)
├── 批次 2：所有 match-connect 类
│   └── countMatch, numberObjectMatch, matchConnect,
│       codeConnect, dotConnect... (~6 种)
├── 批次 3：所有 grid-coloring 类
│   └── numberFind, positionColoring, gridDrawing,
│       shapeSymbol, colorPattern... (~8 种)
├── 批次 4：tracing-writing 类
│   └── copyBook, numberWrite... (~2 种)
└── 批次 5：其余类型

路由兼容：
  旧页面路径不变，内部逻辑替换为 TemplateEngine.render()
  或：旧路径 redirect 到通用 worksheet 页面
```

---

## 六、后端概要设计

> **现阶段（至 2026-09-15）**：云数据库、云存储、云函数及调用策略的**完整设计**见 → [小程序云开发方案](./小程序云开发方案.md)。  
> **阶段到期后可选迁移**：NestJS 项目结构、Prisma、REST API、部署等见 → [后端部署方案](./后端部署方案.md)。

### 6.1 现阶段：微信云开发（摘要）


| 组件    | 技术                           | 说明                                  |
| ----- | ---------------------------- | ----------------------------------- |
| 数据    | 云数据库（文档型）                    | worksheets / categories / users 等集合 |
| 文件    | 云存储（COS）                     | 预览图、涂色线稿、字体等                        |
| 逻辑    | 云函数（Node.js）                 | 统一入口路由、业务 action                    |
| 小程序接入 | `wx.cloud` + `cloud-adapter` | 与业务服务层解耦，便于日后替换为 HTTP               |


### 6.2 远期可选：自建 NestJS（摘要）


| 组件  | 技术                    | 说明               |
| --- | --------------------- | ---------------- |
| 应用  | NestJS + TypeScript   | RESTful API，多端统一 |
| 数据  | MySQL 8.0 + Prisma    | 关系型存储与迁移         |
| 文件  | 本地磁盘 + Nginx          | 静态素材 URL 由自有域名提供 |
| 鉴权  | 微信 code2Session + JWT | 小程序与 Web 可共用逻辑   |


（接口路径、表结构、部署步骤等以 [后端部署方案](./后端部署方案.md) 为准，此处不重复。）

### 6.3 数据加载策略（两阶段一致的产品策略）

无论后端是云开发还是 HTTP API，**客户端侧优先级**保持一致：

```
┌────────────────────────────────────────────────────────────┐
│                    数据加载策略                              │
│                                                            │
│  题型配置数据 (worksheets/categories)                       │
│  ┌────────────────────────────────────────┐                │
│  │  优先级 1: 本地缓存（wx.Storage）        │                │
│  │  优先级 2: 云端拉取（云 DB 或 HTTP API） │                │
│  │  优先级 3: 前端内置兜底数据              │  ← 保证离线可用 │
│  └────────────────────────────────────────┘                │
│                                                            │
│  缓存策略：                                                │
│  • 首次启动：云端拉取 → 写入本地缓存                        │
│  • 后续启动：先用缓存渲染 → 后台静默更新                    │
│  • 缓存有效期：24 小时                                      │
│  • 无网络：使用本地缓存或内置兜底                            │
└────────────────────────────────────────────────────────────┘
```

### 6.4 静态素材 URL（现阶段）

素材使用 **云存储 fileID / 临时 URL**，例如：

```
cloud://doodle-xxx.xxxx/assets/coloring/animals/dinosaur-01.svg
```

（目录规划见 [小程序云开发方案](./小程序云开发方案.md) 第六节。迁移自建后改为 `https://你的域名/static/...`。）

---

## 七、关键技术方案

### 7.1 Canvas 渲染架构（改进）

**现有问题**：预览区尺寸初始化和 A4 导出尺寸是两阶段，`setPaper()` 会重设 Canvas 尺寸。

**改进方案**：

```
┌─────────────────────────────────────────────────────┐
│                Canvas 渲染管线                        │
│                                                     │
│  ① 逻辑坐标系统（所有 Draw 服务统一使用）             │
│     固定使用 A4 逻辑坐标: 595 × 842                  │
│     所有绘制操作都在这个坐标系下                      │
│                                                     │
│  ② 预览渲染                                         │
│     Canvas 物理尺寸 = 容器宽度 × dpr                 │
│     ctx.scale(容器宽度/595 × dpr, ...)              │
│     → 绘制服务使用 A4 坐标 → 自动缩放到预览区        │
│                                                     │
│  ③ 导出渲染                                         │
│     Canvas 物理尺寸 = 1240 × 1754 (150DPI A4)       │
│     ctx.scale(1240/595, 1754/842)                   │
│     → 同一套绘制代码 → 导出高清图                    │
│                                                     │
│  好处：                                              │
│  • Draw 服务完全不感知预览/导出差异                   │
│  • 同一套代码在小程序和 Web 上直接可用               │
│  • 预览和导出结果 100% 一致                          │
└─────────────────────────────────────────────────────┘
```

### 7.2 图片加载适配

```typescript
// platform/image-adapter.ts — 小程序实现
class WxImageAdapter implements IImageAdapter {
    async loadImage(canvas: WechatMiniprogram.Canvas, src: string) {
        const img = canvas.createImage();
        return new Promise((resolve, reject) => {
            img.onload = () => resolve(img);
            img.onerror = reject;
            img.src = src;
        });
    }
}
// Web 端适配见 PC-Web端技术预案.md
```

### 7.3 导出方案

```
核心路径：客户端 Canvas 渲染 → PNG 图片 → 保存到相册

┌─────────────────────────────────────────────────────┐
│  为什么选择 PNG 保存到相册作为主路径：                   │
│                                                     │
│  1. 用户认知成本最低                                  │
│     相册是手机用户最熟悉的文件存储位置                   │
│     PDF 保存后，大量用户找不到文件在哪里                 │
│                                                     │
│  2. 打印路径最短                                      │
│     相册 → 手机连打印机 → 直接打印                     │
│     无需额外 App 打开 PDF                              │
│                                                     │
│  3. 分享最便捷                                        │
│     微信聊天直接发图片，接收方零门槛查看               │
│                                                     │
│  4. 零服务器成本                                      │
│     Canvas 本地渲染，不消耗云函数算力与云存储（主路径）   │
│     用户量增长不会带来额外成本                          │
│                                                     │
│  5. 技术架构成熟                                      │
│     现有 Canvas → PNG 链路已完善且稳定                  │
└─────────────────────────────────────────────────────┘

批量生成方案（P4）：
  → 一次渲染多张不同题目 → 批量保存到相册
  → 用户打印时多选图片即可

PDF 导出方案（远期增值功能，会员专属）：
  小程序：客户端渲染多张 PNG → 上传云存储 → 云函数 pdfkit 合并为 PDF（迁移自建后同逻辑可落在 NestJS）
  定位：会员订阅的差异化权益，非核心路径
```

### 7.4 涂色卡/迷宫等素材型内容方案

```
┌──────────────────────────────────────────────────────┐
│  两类内容的技术方案差异                                │
│                                                      │
│  类型 A：算法生成型（现有主力）                        │
│  ┌──────────────────────────────────────┐            │
│  │  前端算法随机生成题目数据                │            │
│  │  → DrawService 渲染到 Canvas           │            │
│  │  → 导出图片                            │            │
│  │                                        │            │
│  │  适用：数学题、专注力题、字母练习等      │            │
│  │  优点：无限变化、零存储成本              │            │
│  │  开发方式：写 Generator + DrawService   │            │
│  └──────────────────────────────────────┘            │
│                                                      │
│  类型 B：素材模板型（需扩展）                          │
│  ┌──────────────────────────────────────┐            │
│  │  设计师制作 SVG/PNG 线稿               │            │
│  │  → 上传到云存储                        │            │
│  │  → 前端加载 + Canvas 排版到 A4         │            │
│  │  → 导出图片                            │            │
│  │                                        │            │
│  │  适用：涂色卡、折纸、手工、贴纸等       │            │
│  │  优点：品质高、风格可控                 │            │
│  │  开发方式：素材管理 + 通用排版渲染器    │            │
│  └──────────────────────────────────────┘            │
│                                                      │
│  类型 C：混合型                                       │
│  ┌──────────────────────────────────────┐            │
│  │  算法生成结构 + 素材填充               │            │
│  │                                        │            │
│  │  适用：迷宫（算法生路径+主题皮肤）      │            │
│  │       找不同（模板+随机差异点）         │            │
│  │       闪卡（模板+词库数据）            │            │
│  └──────────────────────────────────────┘            │
└──────────────────────────────────────────────────────┘
```

## 十、成本预估

### 10.1 后端费用

```
现阶段（微信云开发，至 2026-09-15 复盘）：
┌────────────────────────────────────────┐
│  免费额度内：云数据库 / 云存储 / 云函数   │
│  预估早期 DAU 下可落在免费额度内         │
│  超出后按套餐计费（如 19.9 元/月起）      │
│                                        │
│  详见 → 小程序云开发方案.md 第七节       │
└────────────────────────────────────────┘

到期后若迁移自建（参考 后端部署方案.md）：
┌────────────────────────────────────────┐
│  已有云服务器可做到增量 ¥0（仅运维）     │
│  需在 2026-09-15 前对比两路径总成本      │
└────────────────────────────────────────┘
```

### 10.2 开发资源

```
开发投入估算：
┌──────────────────────────────────────────────────┐
│  Phase 1: ~20 人时（基础重构 + 修复技术债）        │
│  Phase 2: ~60 人时（模板引擎 + 首页重构 + 配置化） │
│  Phase 3: ~40 人时（模板扩展 + 新内容 + 存量迁移） │
│  Phase 4: ~30 人时（用户体系 + 体验升级）          │
│  ──────────────────────                           │
│  总计: ~150 人时                                   │
│  (1 人兼职 ~10 周可完成)                           │
│                                                    │
│  注：模板引擎上线后，新增内容仅需 JSON 配置        │
│  + 素材上传，边际成本趋近于零                      │
└──────────────────────────────────────────────────┘
```

---

## 十一、技术风险与应对


| 风险            | 影响        | 应对方案                                                                    |
| ------------- | --------- | ----------------------------------------------------------------------- |
| 云开发免费额度不足     | 功能受限或需付费  | ① 优化查询与缓存 ② 控制云函数调用 ③ **2026-09-15 前**复盘是否升级套餐或迁移 [后端部署方案](./后端部署方案.md) |
| 服务器宕机（迁移自建后）  | 后端不可用     | ① PM2 自动重启 ② 前端兜底数据 ③ 定期备份数据库                                           |
| Canvas 兼容性    | 低端机渲染异常   | ① 使用 Canvas 2D（已采用）② 控制单次绘制复杂度 ③ 降级方案                                   |
| 小程序包体积        | 超 2MB 限制  | ① 素材用云存储 / CDN ② 合理分包 ③ 图片压缩                                            |
| 素材制作瓶颈        | 涂色卡等需设计资源 | ① 使用开源 SVG 素材 ② AI 生成线稿 ③ 社区投稿                                          |
| 跨平台迁移成本       | Web 端重复开发 | ① core/ 层保持平台无关 ② 适配器隔离差异（详见 [PC-Web端技术预案](./PC-Web端技术预案.md)）           |
| 带宽与加载（迁移自建远期） | 素材加载慢     | ① 图片压缩 ② 长缓存 ③ 可接入 CDN                                                  |


---

## 附录 A：现有代码与目标架构映射


| 现有文件                             | 目标位置                                  | 迁移动作                                    |
| -------------------------------- | ------------------------------------- | --------------------------------------- |
| `constants/mathFunctions.ts`     | `core/data/worksheets.ts`             | 合并，增加字段                                 |
| `constants/focusFunctions.ts`    | `core/data/worksheets.ts`             | 合并，增加字段                                 |
| `constants/words.ts`             | `core/data/words.ts`                  | 移动                                      |
| `constants/colors.ts`            | `core/data/colors.ts`                 | 移动                                      |
| `constants/shapes.ts`            | `core/data/shapes.ts`                 | 移动                                      |
| `service/baseDraw.ts`            | `core/draw/base-draw.ts`              | 重构，去除 wx. 依赖                            |
| `service/wordDrawService.ts`     | `core/draw/chinese-draw/word-draw.ts` | 移动，去除 wx.                               |
| `service/drawServiceFactory.ts`  | `core/draw/draw-factory.ts`           | 扩展                                      |
| `mathPages/shared/service/*.ts`  | `core/draw/legacy/math-draw/*.ts`     | 移入 legacy，去除 wx.，逐步迁移到模板引擎              |
| `focusPages/shared/service/*.ts` | `core/draw/legacy/focus-draw/*.ts`    | 移入 legacy，去除 wx.，逐步迁移到模板引擎              |
| `base/pageMixin.ts`              | `services/print-service.ts` + 页面代码    | 拆分职责                                    |
| `base/callCloud.ts`              | `platform/cloud-adapter.ts`           | 封装 wx.cloud；迁移 NestJS 后再考虑 http-adapter |
| `utils/saveImage.ts`             | `platform/canvas-adapter.ts`          | 合并到适配器                                  |
| `utils/downloadPrint.ts`         | `services/print-service.ts`           | 整合                                      |
| `utils/tracker.ts`               | `services/stats-service.ts`           | 整合                                      |
| `config/config.ts`               | `core/models/print-config.ts`         | 类型化                                     |


## 附录 B：技术选型决策记录


| 决策点      | 选项                         | 决策                       | 理由                                                                         |
| -------- | -------------------------- | ------------------------ | -------------------------------------------------------------------------- |
| 跨端框架     | Taro / uni-app / 原生        | **原生**                   | 项目已用原生开发，迁移成本高；Taro 等框架对 Canvas 2D 支持有限；core/ 层抽取足以实现代码复用                  |
| 状态管理     | MobX / 自定义 / 原生 setData    | **轻量自定义**                | 小程序场景简单，不需要 Redux 级方案；页面级 setData + 全局 Store 够用                            |
| UI 组件库   | Vant / 自研                  | **Vant + 自定义组件**         | 已用 Vant，保持；业务组件自研                                                          |
| 后端方案     | 云开发 / 自有云服务器               | **现阶段云开发（至 2026-09-15）** | 详见 [小程序云开发方案](./小程序云开发方案.md)；到期后可选迁移 [后端部署方案](./后端部署方案.md)（NestJS）         |
| 导出方案     | PNG 保存相册 / PDF             | **PNG 为主，PDF 为增值**       | PNG 保存到相册是手机端最短路径（用户教育成本低、零服务器开销）；PDF 作为远期会员增值功能，服务端 pdfkit 合并，Web 用 jsPDF |
| Monorepo | pnpm workspace / Turborepo | **暂不采用**                 | 当前只有小程序，过早引入增加复杂度；core/ 作为目录组织，未来再拆包                                       |


