Files
doodle-mini/docs/技术架构设计文档.md
T
2026-03-25 08:50:45 +08:00

1459 lines
78 KiB
Markdown
Raw 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.
# Doodle Mini - 技术架构设计文档
> 版本:v3.0
> 最后更新:2026-03-24
> 配套文档:[现有功能清单](./现有功能清单.md) | [产品设计文档](./产品设计文档.md) | [PC-Web端技术预案](./PC-Web端技术预案.md)
---
## 一、技术决策总览
### 1.1 核心原则
| 原则 | 说明 |
| ------------ | ------------------------------------------------------------------------------------------------- |
| **成本优先** | 优先使用微信云开发,避免自建服务器的运维和费用 |
| **渐进增强** | 先小程序跑通,后续再扩展 PC Web,不为未来过度设计 |
| **前端为主** | Canvas 渲染、随机生成、PNG 导出等核心逻辑保持前端执行,零服务器成本;后端仅做必要的数据存储和服务 |
| **可迁移** | 核心绘制逻辑与平台 API 解耦,为将来迁移 Web 做准备 |
### 1.2 后端方案决策
#### 需要后端的场景 vs 不需要的场景
```
┌─────────────────────────────────────────────────────────┐
│ 不需要后端(前端完成) │
├─────────────────────────────────────────────────────────┤
│ ✅ Canvas 绘制所有题型 │
│ ✅ 随机题目生成算法 │
│ ✅ A4 排版与打印预览 │
│ ✅ PNG 导出 → 保存相册 │
│ ✅ 题型配置数据(可前端内置 JSON) │
│ ✅ 分享功能 │
│ ✅ 本地收藏/历史(wx.Storage
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 需要后端(轻量级) │
├─────────────────────────────────────────────────────────┤
│ 📦 内容配置远程下发(热更新题型/分类,无需发版) │
│ 📦 静态素材存储与 CDN 分发(涂色卡线稿、折纸模板等) │
│ 📦 用户数据同步(收藏、历史记录跨设备) │
│ 📦 下载统计 / 热度排名 │
│ 📦 PDF 多页合并导出(远期增值功能,非核心路径) │
│ 📦 未来 PC Web 端的 API 服务 │
└─────────────────────────────────────────────────────────┘
```
#### 方案对比与选择
| 维度 | 微信云开发 | 自建云服务器 | 决策 |
| ----------- | -------------------------------------------------------- | -------------------- | --------- |
| 接入成本 | ⭐ 极低(已有基础) | ⭐⭐⭐ 需搭建部署 | 云开发 ✅ |
| 运维成本 | ⭐ 免运维 | ⭐⭐⭐ 需监控/维护 | 云开发 ✅ |
| 费用 | 免费额度足够早期(数据库 2GB/存储 5GB/云函数 10万次/月) | 最低约 ¥50-100/月 | 云开发 ✅ |
| 小程序集成 | ⭐ 原生集成,鉴权零成本 | ⭐⭐ 需对接登录/鉴权 | 云开发 ✅ |
| PC Web 支持 | ⭐⭐ 需通过 HTTP API 桥接 | ⭐ 天然支持 | 自建 ✅ |
| 灵活性 | ⭐⭐ 受限于云开发 SDK | ⭐ 完全自由 | 自建 ✅ |
| 数据迁移 | ⭐⭐ 可导出但不方便 | ⭐ 标准数据库 | 自建 ✅ |
**最终决策:近期使用微信云开发,远期按需引入轻量后端**
```
Phase 1-3(当前~8周):100% 微信云开发
└── 云数据库 + 云存储 + 云函数,零服务器成本
远期(需要 PC Web 时):按需引入轻量后端
└── 详见 PC-Web端技术预案.md
```
---
## 二、系统架构总图
```
┌────────────────────────────────────────────────────────────────┐
│ 微信小程序(当前重心) │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 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 复用 │ │
│ └──────────────────────────────────────────────────────┘ │
└───────────────────────────┬────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────┐
│ 后端服务层 │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 微信云开发(零服务器成本) │ │
│ │ │ │
│ │ ┌────────────┐ ┌──────────┐ ┌─────────────┐ │ │
│ │ │ 云数据库 │ │ 云存储 │ │ 云函数 │ │ │
│ │ │ (MongoDB) │ │ (COS) │ │ (Node.js) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ • 题型配置 │ │ • 涂色卡 │ │ • getOpenId │ │ │
│ │ │ • 用户数据 │ │ • 折纸图 │ │ • syncData │ │ │
│ │ │ • 收藏记录 │ │ • 手工图 │ │ • stats │ │ │
│ │ │ • 下载统计 │ │ • 预览图 │ │ • genPDF │ │ │
│ │ │ • 反馈数据 │ │ • 字体 │ │ (增值) │ │ │
│ │ └────────────┘ └──────────┘ └─────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ CDN (腾讯云) │ │
│ │ 素材图片 / 字体文件 / 预览图 / 分享图 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 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/ ← 题目生成算法(纯逻辑)
│ │ ├── math/ ← 数学题目生成器
│ │ │ ├── addition.ts
│ │ │ ├── subtraction.ts
│ │ │ ├── number-sequence.ts
│ │ │ └── ...
│ │ ├── focus/ ← 专注力题目生成器
│ │ ├── chinese/ ← 语文题目生成器
│ │ └── english/ ← 英语题目生成器
│ ├── draw/ ← 绘制服务(Canvas 2D API,平台无关)
│ │ ├── 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/ ← 存量专用绘制服务(渐进迁移后移除)
│ │ │ ├── math-draw/
│ │ │ ├── focus-draw/
│ │ │ └── chinese-draw/
│ ├── 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 ↔ HTTP
│ ├── 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/ ← 重构后的主包页面
│ ├── home/ ← 发现首页(合并原 4 个 Tab 入口)
│ ├── category/ ← 分类详情页
│ ├── worksheet/ ← 统一的题型生成/预览页
│ ├── profile/ ← 我的
│ ├── guide/ ← 打印指南
│ ├── favorites/ ← 收藏列表
│ ├── history/ ← 下载历史
│ └── settings/ ← 设置
├── subpackages/ ← 🆕 按品类分包
│ ├── math/ ← 数学题型页面
│ ├── chinese/ ← 语文题型页面
│ ├── english/ ← 英语题型页面
│ ├── puzzle/ ← 益智游戏页面
│ └── craft/ ← 创意手工页面
├── 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/ ← 全局样式
```
### 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 种)
├── 批次 4tracing-writing 类
│ └── copyBook, numberWrite... (~2 种)
└── 批次 5:其余类型
路由兼容:
旧页面路径不变,内部逻辑替换为 TemplateEngine.render()
或:旧路径 redirect 到通用 worksheet 页面
```
---
## 六、后端(云开发)详细设计
### 6.1 云数据库集合设计
```
云数据库 Collections:
┌─────────────────────────────────────────────────┐
│ │
│ worksheets (题型配置表) │
│ ┌─────────────────────────────────────┐ │
│ │ _id: string │ │
│ │ category: string │ │
│ │ subcategory: string │ │
│ │ title: string │ │
│ │ desc: string │ │
│ │ ageRange: [number, number] │ │
│ │ difficulty: 1|2|3|4 │ │
│ │ previewImage: string │ │
│ │ tags: string[] │ │
│ │ isNew: boolean │ │
│ │ isHot: boolean │ │
│ │ sortOrder: number │ │
│ │ downloadCount: number │ │
│ │ status: 'active'|'draft'|'hidden' │ │
│ │ │ │
│ │ ── 模板引擎字段 ── │ │
│ │ template: TemplateType │ ← 渲染模板│
│ │ generator: GeneratorType │ ← 生成器 │
│ │ generatorConfig: object │ ← 生成参数│
│ │ layoutConfig: object │ ← 排版参数│
│ │ userConfigurable: array │ ← 用户可调│
│ │ legacyPage: string | null │ ← 旧页面 │
│ │ │ │
│ │ createdAt: Date │ │
│ │ updatedAt: Date │ │
│ └─────────────────────────────────────┘ │
│ │
│ categories (分类表) │
│ ┌─────────────────────────────────────┐ │
│ │ _id: string │ │
│ │ name: string │ │
│ │ icon: string │ │
│ │ color: string │ │
│ │ sortOrder: number │ │
│ │ parentId: string | null │ │
│ └─────────────────────────────────────┘ │
│ │
│ users (用户表) │
│ ┌─────────────────────────────────────┐ │
│ │ _id: string (openid) │ │
│ │ uuid: string │ │
│ │ nickName: string │ │
│ │ avatarUrl: string │ │
│ │ totalDownloads: number │ │
│ │ createdAt: Date │ │
│ │ lastActiveAt: Date │ │
│ └─────────────────────────────────────┘ │
│ │
│ favorites (收藏表) │
│ ┌─────────────────────────────────────┐ │
│ │ _id: string │ │
│ │ _openid: string │ │
│ │ worksheetId: string │ │
│ │ createdAt: Date │ │
│ └─────────────────────────────────────┘ │
│ │
│ download_logs (下载日志表) │
│ ┌─────────────────────────────────────┐ │
│ │ _id: string │ │
│ │ _openid: string │ │
│ │ worksheetId: string │ │
│ │ params: object (生成参数快照) │ │
│ │ createdAt: Date │ │
│ └─────────────────────────────────────┘ │
│ │
│ feedback (用户反馈表) │
│ ┌─────────────────────────────────────┐ │
│ │ _id: string │ │
│ │ _openid: string │ │
│ │ content: string │ │
│ │ contact: string │ │
│ │ createdAt: Date │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────┘
```
### 6.2 云函数设计
```
cloudfunctions/
└── doodle/
├── index.js ← 统一入口(action 路由)
├── package.json
├── src/
│ ├── user/
│ │ ├── getOpenId.js ← ✅ 已有,保留
│ │ └── updateProfile.js ← 🆕 更新用户信息
│ ├── worksheet/
│ │ ├── getList.js ← 🆕 获取题型列表(支持分类/筛选)
│ │ ├── getDetail.js ← 🆕 获取题型详情
│ │ └── incrementDownload.js← 🆕 下载计数+1
│ ├── favorite/
│ │ ├── add.js ← 🆕 添加收藏
│ │ ├── remove.js ← 🆕 取消收藏
│ │ └── list.js ← 🆕 我的收藏列表
│ ├── history/
│ │ └── list.js ← 🆕 下载历史
│ ├── feedback/
│ │ └── submit.js ← 🆕 提交反馈
│ └── pdf/
│ └── generate.js ← 🆕 PDF 生成(远期增值功能,会员专属)
└── common/
├── responseMiddleware.js ← ✅ 已有,保留
└── auth.js ← 🆕 鉴权中间件
```
### 6.3 云函数调用策略
```
┌────────────────────────────────────────────────────────────┐
│ 数据加载策略 │
│ │
│ 题型配置数据 (worksheets/categories) │
│ ┌────────────────────────────────────────┐ │
│ │ 优先级 1: 本地缓存(wx.Storage │ │
│ │ 优先级 2: 云数据库查询 │ │
│ │ 优先级 3: 前端内置兜底数据 │ ← 保证离线可用 │
│ └────────────────────────────────────────┘ │
│ │
│ 缓存策略: │
│ • 首次启动:云端拉取 → 写入本地缓存 │
│ • 后续启动:先用缓存渲染 → 后台静默更新 │
│ • 缓存有效期:24 小时 │
│ • 无网络:使用本地缓存或内置兜底 │
│ │
│ 用户数据 (favorites/history) │
│ ┌────────────────────────────────────────┐ │
│ │ 本地优先写入 → 后台同步云端 │ │
│ │ 冲突策略:以云端为准(云端时间戳更新) │ │
│ └────────────────────────────────────────┘ │
│ │
│ 统计数据 (download count) │
│ ┌────────────────────────────────────────┐ │
│ │ 批量上报:本地累计 → 退出时/定时上报 │ │
│ │ 非关键路径,允许丢失 │ │
│ └────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
```
### 6.4 云存储规划
```
云存储目录结构:
cloud://doodle-xxx.xxxx/
├── assets/
│ ├── previews/ ← 题型效果预览图
│ │ ├── math/
│ │ ├── chinese/
│ │ ├── english/
│ │ ├── puzzle/
│ │ └── craft/
│ ├── coloring/ ← 涂色卡线稿(SVG/PNG)
│ │ ├── animals/
│ │ ├── vehicles/
│ │ ├── holidays/
│ │ └── ...
│ ├── origami/ ← 折纸展开图
│ ├── stickers/ ← 贴纸素材
│ ├── maze-templates/ ← 迷宫模板数据(JSON)
│ └── craft-templates/ ← 手工模板
├── fonts/ ← 字体文件
│ ├── SimHei.ttf
│ └── handwriting.ttf
└── share/ ← 分享图
└── default-share.png
```
---
## 七、关键技术方案
### 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
定位:会员订阅的差异化权益,非核心路径
```
### 7.4 涂色卡/迷宫等素材型内容方案
```
┌──────────────────────────────────────────────────────┐
│ 两类内容的技术方案差异 │
│ │
│ 类型 A:算法生成型(现有主力) │
│ ┌──────────────────────────────────────┐ │
│ │ 前端算法随机生成题目数据 │ │
│ │ → DrawService 渲染到 Canvas │ │
│ │ → 导出图片 │ │
│ │ │ │
│ │ 适用:数学题、专注力题、字母练习等 │ │
│ │ 优点:无限变化、零存储成本 │ │
│ │ 开发方式:写 Generator + DrawService │ │
│ └──────────────────────────────────────┘ │
│ │
│ 类型 B:素材模板型(需扩展) │
│ ┌──────────────────────────────────────┐ │
│ │ 设计师制作 SVG/PNG 线稿 │ │
│ │ → 上传到云存储 │ │
│ │ → 前端加载 + Canvas 排版到 A4 │ │
│ │ → 导出图片 │ │
│ │ │ │
│ │ 适用:涂色卡、折纸、手工、贴纸等 │ │
│ │ 优点:品质高、风格可控 │ │
│ │ 开发方式:素材管理 + 通用排版渲染器 │ │
│ └──────────────────────────────────────┘ │
│ │
│ 类型 C:混合型 │
│ ┌──────────────────────────────────────┐ │
│ │ 算法生成结构 + 素材填充 │ │
│ │ │ │
│ │ 适用:迷宫(算法生路径+主题皮肤) │ │
│ │ 找不同(模板+随机差异点) │ │
│ │ 闪卡(模板+词库数据) │ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
```
---
## 八、PC Web 端技术预案(远期)
> PC Web 端属于远期规划,详细方案见独立文档:[PC-Web端技术预案](./PC-Web端技术预案.md)
>
> **当前阶段的准备**:只需确保 `core/` 层内不 import 任何 `wx.*` API,未来迁移时提取为独立 npm 包即可。
---
## 九、前端重构路线图
### Phase 1:基础重构(第 1-2 周)
```
优先级:🔴 关键
目标:不改变现有功能,优化代码结构
1. 修复技术债务
├── 统一云函数名称(callCloud 中 robotaxi → doodle
├── 清理 cloudfunctions/doodle/index.js 中无效的模块引用
├── 移除 debug 页中指向不存在页面的死链
└── 清理未使用的代码和资源
2. 建立 core/ 目录
├── 将 constants/ 迁移到 core/data/
├── 将纯工具函数迁移到 core/utils/
├── 将 DrawService 基类提取到 core/draw/
└── 定义统一的 WorksheetType 数据模型
3. 建立 platform/ 目录
├── 抽取 canvas-adapter(封装 wx Canvas API
├── 抽取 storage-adapter(封装 wx.Storage
└── 抽取 image-adapter(封装图片加载)
4. 统一 WXML 模板
└── 合并 mathPages 和 focusPages 的重复 canvas-page-template
```
### Phase 2:模板引擎 + 首页重构 + 配置化(第 3-5 周)
```
优先级:🟡 重要(模板引擎是后续扩展的基础)
1. 模板引擎核心开发
├── 实现 BaseTemplateRenderer 基类
├── 实现 TemplateEngine 入口(配置解析 + 渲染器/生成器调度)
├── 实现 RendererRegistry / GeneratorRegistry
├── 开发通用 worksheet 页面(pages/worksheet/worksheet
│ └── 根据 JSON 配置动态渲染参数面板 + Canvas 预览
├── 优先实现 3 种高复用模板渲染器:
│ ├── grid-exercise(网格计算型,覆盖 ~15 种现有数学题)
│ ├── full-page-asset(全幅素材型,覆盖涂色卡/折纸等新内容)
│ └── tracing-writing(描红书写型,覆盖练字/字母/拼音)
└── 将 2-3 种现有题型试点迁移到模板引擎(验证可行性)
2. 数据模型升级
├── WorksheetConfig 增加 template/generator/layoutConfig 等字段
├── 定义 Category 模型
├── 重构 MATH_FUNCTION_TYPES / FOCUS_FUNCTION_TYPES 为统一 JSON 格式
└── 编写存量题型的 JSON 配置映射
3. 首页重构
├── 新建 pages/home/ 替代原四个 Tab 入口页
├── 实现分类标签栏 + 搜索 + 推荐区
├── 实现年龄筛选、难度筛选
└── 使用新的 worksheet-card 组件(带预览图)
4. TabBar 重构
└── 发现 | 分龄 | 收藏 | 我的
5. 云数据库初始化
├── 建表:worksheets(含模板引擎字段)、categories
├── 编写数据初始化脚本(存量题型 JSON 导入)
└── 实现前端数据加载(缓存优先 + 云端更新)
```
### Phase 3:模板扩展 + 新内容接入(第 6-8 周)
```
优先级:🟡 重要
1. 补全剩余模板渲染器
├── match-connect(配对连线型)
├── grid-coloring(网格涂色型)
├── card-layout(卡片排列型)
├── sequence-pattern(序列/排序型)
└── special-graphic(时钟/特殊图形型)
2. 新增题型(通过 JSON 配置 + 素材上传,大部分无需写新代码)
├── 数学:时钟练习(需 special-graphic 渲染器)
├── 语文:拼音练习(复用 tracing-writing
├── 英语:字母描红(复用 tracing-writing + letter-tracing 生成器)
├── 英语:字母闪卡(复用 card-layout + static-asset
├── 益智:控笔练习(复用 tracing-writing
└── 创意:涂色卡(复用 full-page-asset,仅需上传素材)
3. 存量题型批量迁移
├── 批次 1grid-exercise 类 (~15 种)
├── 批次 2match-connect 类 (~6 种)
└── 批次 3grid-coloring 类 (~8 种)
4. 完善新分包
├── english/ 分包
├── puzzle/ 分包
└── craft/ 分包
```
### Phase 4:体验与功能升级(第 9-10 周)
```
优先级:🟢 增强
1. 用户体系
├── 微信登录
├── 收藏功能(本地 + 云端同步)
└── 下载历史
2. 打印体验
├── 打印指南页面
├── 批量生成图片(多张保存到相册)
└── 客户端渲染性能优化
3. 运营能力
├── 数据埋点完善
├── 下载统计展示(热门排行)
└── 用户反馈入口
4. 远期增值功能
└── PDF 导出(会员专属,云函数合并 PNG 为 PDF)
```
---
## 十、成本预估
### 9.1 微信云开发费用
```
免费额度(基础版 1):
┌────────────────────────────────────────┐
│ 云数据库:2 GB 存储 / 50 万次读写/天 │
│ 云存储:5 GB / 2 GB 下载/天 │
│ 云函数:10 万次调用/月 / 1000 GBs/月 │
│ CDN5 GB/月 │
└────────────────────────────────────────┘
预估用量(DAU 1000):
┌────────────────────────────────────────┐
│ 云数据库:~100 MB(足够) │
│ 云存储:~2 GB(素材渐增) │
│ 云函数:~3 万次/月 │
│ CDN~3 GB/月 │
│ │
│ 结论:免费额度可覆盖到 DAU 3000 左右 │
│ 超出后升级到 19.9 元/月即可 │
└────────────────────────────────────────┘
```
### 9.2 开发资源
```
开发投入估算:
┌──────────────────────────────────────────────────┐
│ Phase 1: ~20 人时(基础重构 + 修复技术债) │
│ Phase 2: ~60 人时(模板引擎 + 首页重构 + 配置化) │
│ Phase 3: ~40 人时(模板扩展 + 新内容 + 存量迁移) │
│ Phase 4: ~30 人时(用户体系 + 体验升级) │
│ ────────────────────── │
│ 总计: ~150 人时 │
│ (1 人兼职 ~10 周可完成) │
│ │
│ 注:模板引擎上线后,新增内容仅需 JSON 配置 │
│ + 素材上传,边际成本趋近于零 │
└──────────────────────────────────────────────────┘
```
---
## 十一、技术风险与应对
| 风险 | 影响 | 应对方案 |
| -------------------- | ------------------ | ----------------------------------------------------------------- |
| 云开发免费额度不够 | 功能受限 | ① 优化查询减少调用 ② 本地缓存减少读取 ③ 升级付费版(19.9元/月起) |
| Canvas 兼容性 | 低端机渲染异常 | ① 使用 Canvas 2D(已采用)② 控制单次绘制复杂度 ③ 降级方案 |
| 小程序包体积 | 超 2MB 限制 | ① 素材用 CDN / 云存储 ② 合理分包 ③ 图片压缩 |
| 素材制作瓶颈 | 涂色卡等需设计资源 | ① 使用开源 SVG 素材 ② AI 生成线稿 ③ 社区投稿 |
| 跨平台迁移成本 | Web 端重复开发 | ① core/ 层保持平台无关 ② 适配器模式隔离差异(详见 [PC-Web端技术预案](./PC-Web端技术预案.md) |
---
## 附录 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` | 重构 |
| `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,保持;业务组件自研 |
| 云开发 vs 自建 | 云开发 / 云服务器 | **云开发** | 零运维、免费额度充足、原生集成鉴权 |
| 导出方案 | PNG 保存相册 / PDF | **PNG 为主,PDF 为增值** | PNG 保存到相册是手机端最短路径(用户教育成本低、零服务器开销);PDF 作为远期会员增值功能,小程序用云端 pdfkit 合并,Web 用 jsPDF |
| Monorepo | pnpm workspace / Turborepo | **暂不采用** | 当前只有小程序,过早引入增加复杂度;core/ 作为目录组织,未来再拆包 |