# 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; toDataURL(type?: string, quality?: number): Promise; toBlob(): Promise; // Web 专用 toTempFilePath(): Promise; // 小程序专用 } ``` ### 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; } ``` ### 4.3 题目生成器模式 ```typescript /** * 所有题目生成器实现此接口 * 输入配置参数,输出与平台无关的题目数据 * 绘制服务消费这些数据来渲染 Canvas */ interface IWorksheetGenerator { generate(config: TConfig): TData; getDefaultConfig(): TConfig; } // 示例:加法题生成器 class AdditionGenerator implements IWorksheetGenerator { 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; // 生成器参数(不同生成器不同) 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 { 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; } /** * 模式 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; private generatorRegistry: Map; async render( worksheetConfig: WorksheetConfig, userParams: Record, // 用户在 UI 上调整的参数 ctx: CanvasRenderingContext2D, printConfig: PrintConfig, ): Promise { // 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 页面 ``` --- ## 六、后端(云开发)详细设计 ### 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. 存量题型批量迁移 ├── 批次 1:grid-exercise 类 (~15 种) ├── 批次 2:match-connect 类 (~6 种) └── 批次 3:grid-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/月 │ │ CDN:5 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/ 作为目录组织,未来再拆包 |