455 lines
17 KiB
Markdown
455 lines
17 KiB
Markdown
# 页面内容管理方案
|
||
|
||
> 版本:v1.0 | 最后更新:2026-04-29
|
||
> 配套文档:[Worksheet 发布方案](./Worksheet发布方案.md) | [小程序云开发方案](./小程序云开发方案.md)
|
||
|
||
---
|
||
|
||
## 一、背景与目标
|
||
|
||
worksheet 已可通过 Debug 发布进入云数据库,但三个展示页仍使用本地静态数据:
|
||
|
||
|
||
| 页面 | 当前数据来源 | 问题 |
|
||
| --- | --------------------------------- | ---------------------- |
|
||
| 首页 | `pages/home/home.data.ts` | featured、hot、分区靠代码手动维护 |
|
||
| 分类页 | `pages/category/category.data.ts` | 新 worksheet 发布后不会自动出现 |
|
||
| 分龄页 | `pages/age/age.ts` 内 Mock | 推荐内容和周路线无法运营配置 |
|
||
|
||
|
||
**目标:** 通过 Debug 管理页配置三个页面的内容,生成统一 JSON 上传云存储,小程序启动时通过**数据预拉取**获取并渲染。
|
||
|
||
---
|
||
|
||
## 二、整体架构
|
||
|
||
### 2.1 数据流
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ Debug 内容管理页(三个独立 tab) │
|
||
│ │
|
||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||
│ │ 首页管理 │ │ 分类页管理 │ │ 分龄页管理 │ │
|
||
│ └────┬────┘ └────┬────┘ └────┬────┘ │
|
||
│ │ │ │ │
|
||
│ └──────┬──────┘──────┬──────┘ │
|
||
│ ▼ ▼ │
|
||
│ 点击「更新」按钮 → 调用云函数 pageContentUpdate │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ 更新云存储 content/page-config.json 中对应页面的配置 │
|
||
└──────────────────────────────────────────────────────────────────┘
|
||
|
||
▼
|
||
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ 小程序启动 │
|
||
│ │
|
||
│ app.onLaunch │
|
||
│ └─ 数据预拉取 → 获取 content/page-config.json │
|
||
│ │
|
||
│ 页面 onLoad │
|
||
│ ├─ 1. 使用本地 *.data.ts / age.config.ts 渲染兜底首屏 │
|
||
│ ├─ 2. 读取预拉取结果中对应页面的配置 │
|
||
│ └─ 3. 成功则 setData 更新页面 │
|
||
└──────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2.2 统一 JSON 格式
|
||
|
||
云存储路径:`content/page-config.json`
|
||
|
||
```ts
|
||
// content/page-config.json 结构
|
||
type PageConfig = {
|
||
home: HomePageData; // 首页配置
|
||
category: CategoryPageData; // 分类页配置
|
||
age: AgePageData; // 分龄页配置
|
||
version: number; // 版本号,每次更新递增
|
||
updatedAt: string; // 最后更新时间
|
||
};
|
||
```
|
||
|
||
每个管理页更新时,只修改 JSON 中自己负责的字段,通过云函数读取当前 JSON → 合并更新 → 写回云存储。
|
||
|
||
### 2.3 页面加载策略
|
||
|
||
三个页面统一采用**数据预拉取 + 本地兜底**的加载策略:
|
||
|
||
```
|
||
页面 onLoad
|
||
│
|
||
├─ Step 1: 使用本地静态数据渲染首屏(零延迟)
|
||
│ 首页 → home.data.ts
|
||
│ 分类页 → category.data.ts
|
||
│ 分龄页 → age.config.ts
|
||
│
|
||
├─ Step 2: 读取数据预拉取结果
|
||
│ wx.getBackgroundFetchData('pre') 获取 page-config.json
|
||
│ 取出当前页面对应的配置(home / category / age)
|
||
│
|
||
└─ Step 3: 成功 → setData 更新页面内容
|
||
失败 → 保持本地兜底数据,用户无感知
|
||
```
|
||
|
||
> **数据预拉取**在 `app.json` 中配置 `fetchDataUrl`,小程序冷启动时由微信客户端自动发起,不占用页面加载时间。预拉取的数据指向云存储中的 `content/page-config.json`。
|
||
|
||
### 2.4 云函数设计
|
||
|
||
新增一个统一云函数 `pageContentUpdate`:
|
||
|
||
```ts
|
||
// cloudfunctions/pageContentUpdate/index.js
|
||
type PageContentUpdatePayload = {
|
||
page: 'home' | 'category' | 'age'; // 要更新的页面
|
||
data: HomePageData | CategoryPageData | AgePageData; // 该页面的完整配置
|
||
};
|
||
```
|
||
|
||
执行逻辑:
|
||
|
||
```
|
||
接收参数 { page, data }
|
||
→ 从云存储读取当前 content/page-config.json
|
||
→ 合并:config[page] = data
|
||
→ config.version++
|
||
→ config.updatedAt = new Date()
|
||
→ 写回云存储 content/page-config.json
|
||
→ 返回 { success, version }
|
||
```
|
||
|
||
---
|
||
|
||
## 三、首页内容管理
|
||
|
||
### 3.1 管理 UI
|
||
|
||
首页管理 tab 分为四个区块:
|
||
|
||
|
||
| 区块 | 对应字段 | 管理方式 |
|
||
| ------ | -------------- | ----------------------------------- |
|
||
| 分类 tab | `categoryTabs` | 跟随 `CATEGORY_LIST_WITH_ALL`,一般不单独管理 |
|
||
| 年龄入口 | `ageBands` | 跟随 `AGE_BANDS`,可管理描述文案 |
|
||
| 今日推荐 | `featured` | 手动选择 3-5 个 active worksheet |
|
||
| 热门推荐 | `hot` | 手动选择或按 downloads 自动生成 |
|
||
| 分类分区 | `sections` | 每个分类选择若干 active worksheet,支持排序 |
|
||
|
||
|
||
交互流程:
|
||
|
||
```
|
||
首页 tab 展示当前配置
|
||
→ 每个位置点击「选择内容」
|
||
→ 弹出 worksheet 选择器(筛选 active 内容)
|
||
→ 保存配置
|
||
→ 点击「更新首页数据」
|
||
→ 调用 pageContentUpdate({ page: 'home', data: ... })
|
||
```
|
||
|
||
### 3.2 数据结构
|
||
|
||
```ts
|
||
type HomePageData = {
|
||
searchPlaceholder: string;
|
||
categoryTabs: Array<{ id: string; name: string; path?: string }>;
|
||
ageBands: Array<{ key: string; label: string; desc: string; path: string }>;
|
||
featured: HomeDisplayItem[];
|
||
hot: HomeDisplayItem[];
|
||
sections: HomeDisplaySection[];
|
||
};
|
||
```
|
||
|
||
自动生成规则:
|
||
|
||
|
||
| 模块 | 默认规则 |
|
||
| -------------- | ---------------------------------------------- |
|
||
| `categoryTabs` | 由 `CATEGORY_LIST_WITH_ALL` 生成 |
|
||
| `ageBands` | 由 `AGE_BANDS` 生成 |
|
||
| `featured` | 优先使用手动选择,不足时补 `isNew == true` |
|
||
| `hot` | 优先使用手动选择,不足时补 `isHot == true` 或 downloads 高的内容 |
|
||
| `sections` | 使用配置中的 worksheetIds,不足时从该分类 active 内容补齐 |
|
||
|
||
|
||
---
|
||
|
||
## 四、分类页内容管理
|
||
|
||
### 4.1 管理 UI
|
||
|
||
分类页管理 tab 顶部展示分类选择器(来源 `CATEGORY_LIST`),默认选中 `math`。
|
||
|
||
分类头部统计:
|
||
|
||
|
||
| 指标 | 查询规则 |
|
||
| --- | ----------------------------- |
|
||
| 总数 | `worksheets.category == 当前分类` |
|
||
| 草稿 | `status == draft` |
|
||
| 线上 | `status == active` |
|
||
| 已隐藏 | `status == hidden` |
|
||
|
||
|
||
列表展示当前分类下所有 worksheet(按 `status` → `sortOrder` → `updatedAt` 排序),每项显示预览图、标题、ID、年龄/难度/标签、状态、排序权重。
|
||
|
||
操作按钮:
|
||
|
||
|
||
| 当前状态 | 可执行操作 |
|
||
| -------- | --------------- |
|
||
| `draft` | 激活上线 |
|
||
| `active` | 下架为 hidden |
|
||
| `hidden` | 恢复为 draft 或直接激活 |
|
||
| 任意 | 调整排序、预览跳转、刷新 |
|
||
|
||
|
||
### 4.2 数据结构
|
||
|
||
```ts
|
||
type CategoryPageData = {
|
||
searchPlaceholder: string;
|
||
categories: Array<{
|
||
id: string;
|
||
name: string;
|
||
icon: string;
|
||
items: CategoryItem[];
|
||
}>;
|
||
};
|
||
```
|
||
|
||
生成时从 `worksheets` 集合查询 `status == active` 的内容,按分类分组、按 `sortOrder` 排序,转换为 `CategoryItem`。
|
||
|
||
### 4.3 更新流程
|
||
|
||
```
|
||
分类页管理 tab → 管理 worksheet 状态和排序
|
||
→ 点击「更新分类数据」
|
||
→ 查询所有 active worksheets,按 category 分组
|
||
→ 调用 pageContentUpdate({ page: 'category', data: ... })
|
||
→ 云函数更新 page-config.json 中的 category 字段
|
||
```
|
||
|
||
---
|
||
|
||
## 五、分龄页内容管理
|
||
|
||
### 5.1 新增 `age.config.ts`
|
||
|
||
在 `miniprogram/pages/age/` 下新增 `age.config.ts`,将年龄段划分、能力目标、默认 worksheet 写死在配置中,作为兜底数据和管理页的基础结构。
|
||
|
||
```ts
|
||
// miniprogram/pages/age/age.config.ts
|
||
|
||
import { AGE_BANDS, type AgeBandKey } from '../../core/data/difficulty';
|
||
|
||
export type AbilityItem = {
|
||
icon: string;
|
||
title: string;
|
||
desc: string;
|
||
};
|
||
|
||
export type WeekPlan = {
|
||
week: number;
|
||
theme: string;
|
||
worksheetIds: string[]; // worksheet 业务 ID
|
||
worksheetTitles: string[]; // 兜底展示标题
|
||
};
|
||
|
||
export type AgeBandConfig = {
|
||
key: AgeBandKey;
|
||
label: string;
|
||
subLabel: string;
|
||
abilities: AbilityItem[];
|
||
weeks: WeekPlan[];
|
||
};
|
||
|
||
export const AGE_CONFIG: AgeBandConfig[] = [
|
||
{
|
||
key: '3-4',
|
||
label: '3-4 岁',
|
||
subLabel: '启蒙认知',
|
||
abilities: [
|
||
{ icon: '🔢', title: '数感', desc: '认读 1-5、点数对应' },
|
||
{ icon: '✏️', title: '书写', desc: '涂鸦线条、简单描红' },
|
||
{ icon: '🧩', title: '思维', desc: '找相同、简单配对' },
|
||
],
|
||
weeks: [
|
||
{ week: 1, theme: '数感启蒙',
|
||
worksheetIds: [], worksheetTitles: ['找数字涂一涂', '5以内加法', '数数连一连'] },
|
||
{ week: 2, theme: '形状与连线',
|
||
worksheetIds: [], worksheetTitles: ['识别形状', '线条识别', '数字点连线'] },
|
||
{ week: 3, theme: '趣味专注',
|
||
worksheetIds: [], worksheetTitles: ['颜色找规律', '方格推理', '格子仿画'] },
|
||
{ week: 4, theme: '综合练习',
|
||
worksheetIds: [], worksheetTitles: ['10以内加减法', '识字卡', '连连看'] },
|
||
],
|
||
},
|
||
{
|
||
key: '4-5',
|
||
label: '4-5 岁',
|
||
subLabel: '基础练习',
|
||
abilities: [
|
||
{ icon: '🔢', title: '数感', desc: '10 以内数数、比大小' },
|
||
{ icon: '✏️', title: '书写', desc: '笔画模仿、图形描边' },
|
||
{ icon: '🧩', title: '思维', desc: '规律排序、图形分类' },
|
||
],
|
||
weeks: [
|
||
{ week: 1, theme: '数感启蒙',
|
||
worksheetIds: [], worksheetTitles: ['找数字涂一涂', '5以内加法', '数数连一连'] },
|
||
{ week: 2, theme: '形状与连线',
|
||
worksheetIds: [], worksheetTitles: ['识别形状', '线条识别', '数字点连线'] },
|
||
{ week: 3, theme: '趣味专注',
|
||
worksheetIds: [], worksheetTitles: ['颜色找规律', '方格推理', '格子仿画'] },
|
||
{ week: 4, theme: '综合练习',
|
||
worksheetIds: [], worksheetTitles: ['10以内加减法', '识字卡', '连连看'] },
|
||
],
|
||
},
|
||
// 5-6、6-7、7-8 结构相同,abilities 和 weeks 内容不同
|
||
// ... 完整配置见 age.config.ts 文件
|
||
];
|
||
```
|
||
|
||
> `worksheetIds` 初始为空数组,由分龄管理页选择后填入。`worksheetTitles` 作为兜底展示。
|
||
|
||
### 5.2 管理 UI
|
||
|
||
分龄管理 tab 按年龄段切换:`3-4 岁 | 4-5 岁 | 5-6 岁 | 6-7 岁 | 7-8 岁`
|
||
|
||
每个年龄段下的管理内容:
|
||
|
||
|
||
| 模块 | 操作 | 说明 |
|
||
| ---- | ------------ | -------------------------------- |
|
||
| 能力目标 | 只读展示 | 来自 `age.config.ts`,不需要在管理页修改 |
|
||
| 四周路线 | 选择 worksheet | 每周选择 4 个 worksheet,主题来自 config |
|
||
| 推荐内容 | 选择 worksheet | 动态数据,展示 likes 数量最高的 6 个worksheet |
|
||
|
||
|
||
worksheet 选择器默认筛选条件:
|
||
|
||
```
|
||
status == active
|
||
ageMin <= 当前年龄段 maxAge
|
||
ageMax >= 当前年龄段 minAge
|
||
```
|
||
|
||
**核心简化:** 分龄管理页不需要配置能力目标和周主题(这些写死在 `age.config.ts` 中),只需要为不同年龄段、不同周选择对应的 worksheet。
|
||
|
||
### 5.3 数据结构
|
||
|
||
```ts
|
||
type AgePageData = {
|
||
ageTabs: Array<{
|
||
key: AgeBandKey;
|
||
rangeText: string;
|
||
subLabel: string;
|
||
}>;
|
||
bands: Record<AgeBandKey, {
|
||
goalTitle: string;
|
||
abilityItems: AbilityItem[];
|
||
weekPlans: Array<{
|
||
week: number;
|
||
theme: string;
|
||
exercises: Array<{
|
||
id: string;
|
||
title: string;
|
||
path: string;
|
||
previewImg?: string;
|
||
}>;
|
||
}>;
|
||
recommendedItems: Array<{
|
||
id: string;
|
||
title: string;
|
||
image?: string;
|
||
path: string;
|
||
}>;
|
||
}>;
|
||
};
|
||
```
|
||
|
||
### 5.4 更新流程
|
||
|
||
```
|
||
分龄管理 tab → 选择年龄段
|
||
→ 为每周选择 worksheet
|
||
→ 选择推荐内容
|
||
→ 点击「更新分龄数据」
|
||
→ 合并 age.config.ts 的能力目标 + 管理页选择的 worksheet 详情
|
||
→ 调用 pageContentUpdate({ page: 'age', data: ... })
|
||
→ 云函数更新 page-config.json 中的 age 字段
|
||
```
|
||
|
||
---
|
||
|
||
## 六、Debug 页入口结构
|
||
|
||
```
|
||
supportPages/debug/debug
|
||
├─ 分类基础数据管理 (已有:同步 CATEGORY_LIST → categories)
|
||
├─ Worksheet 内容池 (已有:状态、分类、上下架、预览)
|
||
└─ 页面内容管理 (新增)
|
||
├─ Tab: 首页管理 → 编排 featured / hot / sections
|
||
├─ Tab: 分类页管理 → 管理 worksheet 状态和排序
|
||
└─ Tab: 分龄页管理 → 按年龄段选择周 worksheet
|
||
```
|
||
|
||
---
|
||
|
||
## 七、实施计划
|
||
|
||
### Phase 1:基础设施
|
||
|
||
1. 配置 `app.json` 数据预拉取,指向 `content/page-config.json`
|
||
2. 实现 `pageContentUpdate` 云函数
|
||
3. 上传初始 `page-config.json`(从现有 `*.data.ts` 转换)
|
||
4. 三个页面统一接入预拉取加载逻辑
|
||
|
||
### Phase 2:分类页管理
|
||
|
||
1. 实现分类页管理 tab(worksheet 列表、状态管理、排序)
|
||
2. 实现「更新分类数据」→ 生成 category 配置 → 调用云函数更新
|
||
|
||
### Phase 3:首页管理
|
||
|
||
1. 实现首页管理 tab(featured / hot / sections 编排)
|
||
2. 实现 worksheet 选择器弹窗
|
||
3. 实现「更新首页数据」→ 生成 home 配置 → 调用云函数更新
|
||
|
||
### Phase 4:分龄页管理
|
||
|
||
1. 新增 `age.config.ts` 配置文件
|
||
2. 实现分龄管理 tab(年龄段切换、周 worksheet 选择)
|
||
3. 实现「更新分龄数据」→ 合并 config + worksheet 详情 → 调用云函数更新
|
||
|
||
---
|
||
|
||
## 八、风险与约束
|
||
|
||
|
||
| 风险 | 处理方式 |
|
||
| ----------------- | ----------------------------------------- |
|
||
| 预拉取失败 | 本地 `*.data.ts` / `age.config.ts` 兜底,用户无感知 |
|
||
| JSON 生成失败 | 云函数返回错误,管理页提示重试,线上数据不受影响 |
|
||
| 首页配置引用了 hidden 内容 | 生成时只允许 active 内容,不足时返回警告 |
|
||
| 分龄内容不适龄 | 选择器按年龄段默认过滤 |
|
||
| 并发更新冲突 | 云函数使用 version 乐观锁,冲突时提示重新加载后重试 |
|
||
|
||
|
||
---
|
||
|
||
## 九、关键文件索引
|
||
|
||
|
||
| 文件 / 模块 | 职责 |
|
||
| ----------------------------------- | ------------------------------ |
|
||
| `supportPages/contentManage/` | 三 tab 内容管理页 |
|
||
| `cloudfunctions/pageContentUpdate/` | 更新 `page-config.json` 中指定页面的配置 |
|
||
| `pages/age/age.config.ts` | 分龄页兜底配置(年龄段、能力目标、默认 worksheet) |
|
||
| `pages/home/home.data.ts` | 首页兜底数据 |
|
||
| `pages/category/category.data.ts` | 分类页兜底数据 |
|
||
| `content/page-config.json`(云存储) | 三个页面的统一配置 JSON |
|
||
|
||
|