929 lines
50 KiB
Markdown
929 lines
50 KiB
Markdown
# Doodle Mini - 技术架构设计文档
|
||
|
||
> 版本:v2.0
|
||
> 最后更新:2026-03-24
|
||
> 配套文档:[现有功能清单](./现有功能清单.md) | [产品设计文档](./产品设计文档.md)
|
||
|
||
---
|
||
|
||
## 一、技术决策总览
|
||
|
||
### 1.1 核心原则
|
||
|
||
| 原则 | 说明 |
|
||
|------|------|
|
||
| **成本优先** | 优先使用微信云开发,避免自建服务器的运维和费用 |
|
||
| **渐进增强** | 先小程序跑通,后续再扩展 PC Web,不为未来过度设计 |
|
||
| **前端为主** | Canvas 渲染、随机生成等核心逻辑保持前端执行,后端仅做必要的数据存储和服务 |
|
||
| **可迁移** | 核心绘制逻辑与平台 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% 微信云开发
|
||
└── 云数据库 + 云存储 + 云函数,零服务器成本
|
||
|
||
Phase 4+(需要 PC Web 时):引入轻量后端
|
||
└── 方案 A(推荐):云开发 HTTP API + PC Web 直接调用
|
||
└── 方案 B:Cloudflare Workers / Vercel Serverless 做 API 代理层
|
||
└── 方案 C:轻量 Node.js 服务(仅在流量大时考虑)
|
||
```
|
||
|
||
---
|
||
|
||
## 二、系统架构总图
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ 用户端 │
|
||
│ │
|
||
│ ┌──────────────────────┐ ┌──────────────────────┐ │
|
||
│ │ 微信小程序 (当前) │ │ PC Web (未来) │ │
|
||
│ │ │ │ │ │
|
||
│ │ ┌────────────────┐ │ │ ┌────────────────┐ │ │
|
||
│ │ │ UI 层 (WXML) │ │ │ │ UI 层 (React) │ │ │
|
||
│ │ │ Skyline 渲染 │ │ │ │ Tailwind CSS │ │ │
|
||
│ │ └───────┬────────┘ │ │ └───────┬────────┘ │ │
|
||
│ │ │ │ │ │ │ │
|
||
│ │ ┌───────▼────────┐ │ │ ┌───────▼────────┐ │ │
|
||
│ │ │ 业务逻辑层 (TS) │ │ │ │ 业务逻辑层 (TS) │ │ │
|
||
│ │ │ Page + Mixin │ │ │ │ Hooks + Store │ │ │
|
||
│ │ └───────┬────────┘ │ │ └───────┬────────┘ │ │
|
||
│ │ │ │ │ │ │ │
|
||
│ │ ┌───────▼────────┐ │ │ ┌───────▼────────┐ │ │
|
||
│ │ │ 共享核心层 │◄─┼─────────┼──► 共享核心层 │ │ │
|
||
│ │ │ @doodle/core │ │ │ │ @doodle/core │ │ │
|
||
│ │ │ ┌─────────────┐ │ │ │ │ (同一套代码) │ │ │
|
||
│ │ │ │ DrawService │ │ │ │ └───────┬────────┘ │ │
|
||
│ │ │ │ DataModels │ │ │ │ │ │ │
|
||
│ │ │ │ Generators │ │ │ │ │ │ │
|
||
│ │ │ │ Utils │ │ │ │ │ │ │
|
||
│ │ │ └─────────────┘ │ │ │ │ │ │
|
||
│ │ └───────┬────────┘ │ │ │ │ │
|
||
│ │ │ │ │ │ │ │
|
||
│ │ ┌───────▼────────┐ │ │ ┌───────▼────────┐ │ │
|
||
│ │ │ 平台适配层 │ │ │ │ 平台适配层 │ │ │
|
||
│ │ │ wx.* API │ │ │ │ Web API │ │ │
|
||
│ │ │ wx.cloud.* │ │ │ │ HTTP Client │ │ │
|
||
│ │ │ Canvas 2D │ │ │ │ Canvas 2D │ │ │
|
||
│ │ └───────┬────────┘ │ │ └───────┬────────┘ │ │
|
||
│ └──────────┼──────────┘ └──────────┼──────────┘ │
|
||
└──────────────┼───────────────────────────────┼──────────────────┘
|
||
│ │
|
||
▼ ▼
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ 后端服务层 │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────┐ │
|
||
│ │ 微信云开发 (Phase 1-3) │ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────┐ ┌──────────┐ ┌────────────┐ │ │
|
||
│ │ │ 云数据库 │ │ 云存储 │ │ 云函数 │ │ │
|
||
│ │ │ (MongoDB) │ │ (COS) │ │ (Node.js) │ │ │
|
||
│ │ │ │ │ │ │ │ │ │
|
||
│ │ │ • 用户数据 │ │ • 涂色卡 │ │ • getOpenId│ │ │
|
||
│ │ │ • 收藏记录 │ │ • 折纸图 │ │ • genPDF │ │ │
|
||
│ │ │ • 下载统计 │ │ • 手工图 │ │ • syncData │ │ │
|
||
│ │ │ • 内容配置 │ │ • 预览图 │ │ • stats │ │ │
|
||
│ │ │ • 反馈数据 │ │ • 字体 │ │ │ │ │
|
||
│ │ └─────────────┘ └──────────┘ └────────────┘ │ │
|
||
│ └─────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────┐ │
|
||
│ │ 轻量 API 层 (Phase 4+, 仅 PC Web) │ │
|
||
│ │ │ │
|
||
│ │ 方案 A: 云开发 HTTP API (推荐,零额外成本) │ │
|
||
│ │ 方案 B: Cloudflare Workers (极低成本) │ │
|
||
│ │ 方案 C: Vercel Serverless Functions │ │
|
||
│ └─────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────┐ │
|
||
│ │ CDN (腾讯云) │ │
|
||
│ │ 素材图片 / 字体文件 / 预览图 / 分享图 │ │
|
||
│ └─────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 三、前端架构详细设计
|
||
|
||
### 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 ← 基础绘制(纸面、页眉、网格等)
|
||
│ │ ├── math-draw/ ← 数学绘制服务
|
||
│ │ ├── focus-draw/ ← 专注力绘制服务
|
||
│ │ ├── chinese-draw/ ← 语文绘制服务
|
||
│ │ ├── english-draw/ ← 英语绘制服务
|
||
│ │ └── craft-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 云数据库集合设计
|
||
|
||
```
|
||
云数据库 Collections:
|
||
┌─────────────────────────────────────────────────┐
|
||
│ │
|
||
│ worksheets (题型配置表) │
|
||
│ ┌─────────────────────────────────────┐ │
|
||
│ │ _id: string │ │
|
||
│ │ category: string │ │
|
||
│ │ subcategory: string │ │
|
||
│ │ title: string │ │
|
||
│ │ desc: string │ │
|
||
│ │ ageRange: [number, number] │ │
|
||
│ │ difficulty: 1|2|3|4 │ │
|
||
│ │ previewImage: string │ │
|
||
│ │ page: string │ │
|
||
│ │ tags: string[] │ │
|
||
│ │ isNew: boolean │ │
|
||
│ │ isHot: boolean │ │
|
||
│ │ sortOrder: number │ │
|
||
│ │ downloadCount: number │ │
|
||
│ │ status: 'active'|'draft'|'hidden' │ │
|
||
│ │ 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 │ │
|
||
│ └─────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 5.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 生成(Phase 4)
|
||
└── common/
|
||
├── responseMiddleware.js ← ✅ 已有,保留
|
||
└── auth.js ← 🆕 鉴权中间件
|
||
```
|
||
|
||
### 5.3 云函数调用策略
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 数据加载策略 │
|
||
│ │
|
||
│ 题型配置数据 (worksheets/categories) │
|
||
│ ┌────────────────────────────────────────┐ │
|
||
│ │ 优先级 1: 本地缓存(wx.Storage) │ │
|
||
│ │ 优先级 2: 云数据库查询 │ │
|
||
│ │ 优先级 3: 前端内置兜底数据 │ ← 保证离线可用 │
|
||
│ └────────────────────────────────────────┘ │
|
||
│ │
|
||
│ 缓存策略: │
|
||
│ • 首次启动:云端拉取 → 写入本地缓存 │
|
||
│ • 后续启动:先用缓存渲染 → 后台静默更新 │
|
||
│ • 缓存有效期:24 小时 │
|
||
│ • 无网络:使用本地缓存或内置兜底 │
|
||
│ │
|
||
│ 用户数据 (favorites/history) │
|
||
│ ┌────────────────────────────────────────┐ │
|
||
│ │ 本地优先写入 → 后台同步云端 │ │
|
||
│ │ 冲突策略:以云端为准(云端时间戳更新) │ │
|
||
│ └────────────────────────────────────────┘ │
|
||
│ │
|
||
│ 统计数据 (download count) │
|
||
│ ┌────────────────────────────────────────┐ │
|
||
│ │ 批量上报:本地累计 → 退出时/定时上报 │ │
|
||
│ │ 非关键路径,允许丢失 │ │
|
||
│ └────────────────────────────────────────┘ │
|
||
└────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 5.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
|
||
```
|
||
|
||
---
|
||
|
||
## 六、关键技术方案
|
||
|
||
### 6.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% 一致 │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 6.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 实现(未来)
|
||
class WebImageAdapter implements IImageAdapter {
|
||
async loadImage(_canvas: HTMLCanvasElement, src: string) {
|
||
const img = new Image();
|
||
img.crossOrigin = 'anonymous';
|
||
return new Promise((resolve, reject) => {
|
||
img.onload = () => resolve(img);
|
||
img.onerror = reject;
|
||
img.src = src;
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.3 PDF 导出方案(Phase 4)
|
||
|
||
```
|
||
方案对比:
|
||
|
||
A. 云函数生成 PDF(推荐)
|
||
云函数接收绘制参数 JSON → 服务端用 pdfkit 生成 → 上传云存储 → 返回下载链接
|
||
优点:不受客户端限制,可生成多页
|
||
缺点:云函数有执行时间限制(60s),复杂图形渲染较慢
|
||
|
||
B. 客户端多页 Canvas 拼接
|
||
前端生成多张 PNG → 云函数用 pdfkit 合并为 PDF
|
||
优点:渲染在客户端,云函数只做拼接
|
||
缺点:需上传多张图片到云存储,流量开销大
|
||
|
||
C. Web 端直接使用 jsPDF
|
||
仅适用于 PC Web 版本
|
||
优点:零服务器成本
|
||
缺点:小程序不可用
|
||
|
||
推荐路径:
|
||
小程序:方案 B(客户端渲染 + 云端拼接)
|
||
PC Web:方案 C(jsPDF 纯前端)
|
||
```
|
||
|
||
### 6.4 涂色卡/迷宫等素材型内容方案
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────┐
|
||
│ 两类内容的技术方案差异 │
|
||
│ │
|
||
│ 类型 A:算法生成型(现有主力) │
|
||
│ ┌──────────────────────────────────────┐ │
|
||
│ │ 前端算法随机生成题目数据 │ │
|
||
│ │ → DrawService 渲染到 Canvas │ │
|
||
│ │ → 导出图片 │ │
|
||
│ │ │ │
|
||
│ │ 适用:数学题、专注力题、字母练习等 │ │
|
||
│ │ 优点:无限变化、零存储成本 │ │
|
||
│ │ 开发方式:写 Generator + DrawService │ │
|
||
│ └──────────────────────────────────────┘ │
|
||
│ │
|
||
│ 类型 B:素材模板型(需扩展) │
|
||
│ ┌──────────────────────────────────────┐ │
|
||
│ │ 设计师制作 SVG/PNG 线稿 │ │
|
||
│ │ → 上传到云存储 │ │
|
||
│ │ → 前端加载 + Canvas 排版到 A4 │ │
|
||
│ │ → 导出图片 │ │
|
||
│ │ │ │
|
||
│ │ 适用:涂色卡、折纸、手工、贴纸等 │ │
|
||
│ │ 优点:品质高、风格可控 │ │
|
||
│ │ 开发方式:素材管理 + 通用排版渲染器 │ │
|
||
│ └──────────────────────────────────────┘ │
|
||
│ │
|
||
│ 类型 C:混合型 │
|
||
│ ┌──────────────────────────────────────┐ │
|
||
│ │ 算法生成结构 + 素材填充 │ │
|
||
│ │ │ │
|
||
│ │ 适用:迷宫(算法生路径+主题皮肤) │ │
|
||
│ │ 找不同(模板+随机差异点) │ │
|
||
│ │ 闪卡(模板+词库数据) │ │
|
||
│ └──────────────────────────────────────┘ │
|
||
└──────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 七、PC Web 端技术预案
|
||
|
||
### 7.1 时机判断
|
||
|
||
**当前不急于开发 PC Web 的原因:**
|
||
- 小程序用户群与目标用户(家长)高度重合,微信即触达
|
||
- PC Web 需要额外解决支付、登录、SEO 等问题
|
||
- 先把小程序做好,积累内容和用户
|
||
|
||
**何时启动 PC Web:**
|
||
- 小程序 DAU 稳定 > 1000
|
||
- 用户反馈中"想在电脑上用"的需求频繁出现
|
||
- 内容积累到 100+ 种题型
|
||
|
||
### 7.2 技术选型预案
|
||
|
||
```
|
||
PC Web 技术栈(未来):
|
||
┌────────────────────────────────────┐
|
||
│ 框架:Next.js (App Router) │
|
||
│ UI:Tailwind CSS + Radix UI │
|
||
│ Canvas:HTML5 Canvas 2D │
|
||
│ PDF:jsPDF + html2canvas │
|
||
│ 状态:Zustand │
|
||
│ 部署:Vercel │
|
||
│ │
|
||
│ 核心复用: │
|
||
│ @doodle/core (共享核心层) │
|
||
│ └── 绘制服务、生成器、数据模型 │
|
||
│ → 直接 import 使用 │
|
||
│ │
|
||
│ 需新建: │
|
||
│ • Web UI 组件 │
|
||
│ • 平台适配层 (Web 实现) │
|
||
│ • 用户登录(微信扫码/手机号) │
|
||
│ • SEO 优化(SSR/SSG 题型页) │
|
||
└────────────────────────────────────┘
|
||
```
|
||
|
||
### 7.3 代码共享策略
|
||
|
||
```
|
||
仓库结构(Monorepo,未来演进):
|
||
doodle/
|
||
├── packages/
|
||
│ └── core/ ← 共享核心(npm 包)
|
||
│ ├── src/
|
||
│ │ ├── draw/
|
||
│ │ ├── generators/
|
||
│ │ ├── models/
|
||
│ │ └── utils/
|
||
│ ├── package.json
|
||
│ └── tsconfig.json
|
||
├── apps/
|
||
│ ├── mini/ ← 微信小程序
|
||
│ │ ├── miniprogram/
|
||
│ │ ├── cloudfunctions/
|
||
│ │ └── project.config.json
|
||
│ └── web/ ← PC Web(未来)
|
||
│ ├── src/
|
||
│ ├── package.json
|
||
│ └── next.config.js
|
||
├── pnpm-workspace.yaml
|
||
└── package.json
|
||
|
||
当前阶段不需要 Monorepo,只需在小程序项目中:
|
||
1. 将 core/ 作为目录组织代码
|
||
2. 确保 core/ 内不 import 任何 wx.* API
|
||
3. 未来迁移时,将 core/ 提取为独立 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-4 周)
|
||
|
||
```
|
||
优先级:🟡 重要
|
||
|
||
1. 数据模型升级
|
||
├── WorksheetType 增加 category/ageRange/difficulty/tags 等字段
|
||
├── 定义 Category 模型
|
||
└── 重构 MATH_FUNCTION_TYPES / FOCUS_FUNCTION_TYPES 为统一格式
|
||
|
||
2. 首页重构
|
||
├── 新建 pages/home/ 替代原四个 Tab 入口页
|
||
├── 实现分类标签栏 + 搜索 + 推荐区
|
||
├── 实现年龄筛选、难度筛选
|
||
└── 使用新的 worksheet-card 组件(带预览图)
|
||
|
||
3. TabBar 重构
|
||
└── 发现 | 分龄 | 收藏 | 我的
|
||
|
||
4. 云数据库初始化
|
||
├── 建表:worksheets、categories
|
||
├── 编写数据初始化脚本
|
||
└── 实现前端数据加载(缓存优先 + 云端更新)
|
||
```
|
||
|
||
### Phase 3:新内容接入(第 5-6 周)
|
||
|
||
```
|
||
优先级:🟡 重要
|
||
|
||
1. 开发通用素材渲染器
|
||
└── 通用的「加载图片 + A4 排版 + 导出」流程
|
||
|
||
2. 新增题型开发
|
||
├── 数学:时钟练习、形状分类
|
||
├── 语文:拼音练习、笔画练习
|
||
├── 英语:字母描红(复用 numberFind 逻辑)
|
||
├── 益智:控笔练习
|
||
└── 创意:涂色卡(素材型)
|
||
|
||
3. 完善新分包
|
||
├── english/ 分包
|
||
├── puzzle/ 分包
|
||
└── craft/ 分包
|
||
```
|
||
|
||
### Phase 4:体验与功能升级(第 7-8 周)
|
||
|
||
```
|
||
优先级:🟢 增强
|
||
|
||
1. 用户体系
|
||
├── 微信登录
|
||
├── 收藏功能(本地 + 云端同步)
|
||
└── 下载历史
|
||
|
||
2. 打印体验
|
||
├── 打印指南页面
|
||
├── 批量生成(多题一页/多页)
|
||
└── PDF 导出(云函数)
|
||
|
||
3. 运营能力
|
||
├── 数据埋点完善
|
||
├── 下载统计展示(热门排行)
|
||
└── 用户反馈入口
|
||
```
|
||
|
||
---
|
||
|
||
## 九、成本预估
|
||
|
||
### 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: ~40 人时 │
|
||
│ Phase 3: ~40 人时 │
|
||
│ Phase 4: ~30 人时 │
|
||
│ ────────────────────── │
|
||
│ 总计: ~130 人时 │
|
||
│ (1 人兼职 ~8 周可完成) │
|
||
└──────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 十、技术风险与应对
|
||
|
||
| 风险 | 影响 | 应对方案 |
|
||
|------|------|---------|
|
||
| 云开发免费额度不够 | 功能受限 | ① 优化查询减少调用 ② 本地缓存减少读取 ③ 升级付费版(19.9元/月起) |
|
||
| Canvas 兼容性 | 低端机渲染异常 | ① 使用 Canvas 2D(已采用)② 控制单次绘制复杂度 ③ 降级方案 |
|
||
| 小程序包体积 | 超 2MB 限制 | ① 素材用 CDN / 云存储 ② 合理分包 ③ 图片压缩 |
|
||
| 素材制作瓶颈 | 涂色卡等需设计资源 | ① 使用开源 SVG 素材 ② AI 生成线稿 ③ 社区投稿 |
|
||
| 跨平台迁移成本 | Web 端重复开发 | ① core/ 层保持平台无关 ② 适配器模式隔离差异 |
|
||
| 云开发 HTTP API 限制 | PC Web 调用不便 | ① 使用云开发 HTTP API 触发器 ② 必要时引入轻量后端 |
|
||
|
||
---
|
||
|
||
## 附录 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/math-draw/*.ts` | 移动,去除 wx.* |
|
||
| `focusPages/shared/service/*.ts` | `core/draw/focus-draw/*.ts` | 移动,去除 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 自建 | 云开发 / 云服务器 | **云开发** | 零运维、免费额度充足、原生集成鉴权 |
|
||
| PDF 方案 | pdfkit(云) / jsPDF(端) | **两者结合** | 小程序用云端 pdfkit,Web 用 jsPDF |
|
||
| Monorepo | pnpm workspace / Turborepo | **暂不采用** | 当前只有小程序,过早引入增加复杂度;core/ 作为目录组织,未来再拆包 |
|