Files
doodle-mini/docs/页面内容管理方案.md
T

22 KiB
Raw Blame History

页面内容管理方案

版本:v2.0 | 最后更新:2026-04-30 配套文档:Worksheet 发布方案 | 小程序云开发方案


一、背景与目标

worksheet 已可通过 Debug 发布进入云数据库,但三个展示页仍使用本地静态数据:

页面 当前数据来源 问题
首页 pages/home/home.data.ts featured、hot、分区靠代码手动维护
分类页 pages/category/category.data.ts 新 worksheet 发布后不会自动出现
分龄页 pages/age/age.ts 内 Mock 推荐内容和周路线无法运营配置

目标: 通过 Debug 管理页配置三个页面的内容,生成统一配置写入云数据库并同步至云存储,小程序启动时通过数据预拉取获取并渲染。


二、整体架构

2.1 数据存储与同步

数据源(Source of Truth): 云数据库 page_configs 集合,文档 _id = 'current'

数据交付(Delivery): 云存储 content/page-config.json(供小程序预拉取使用)

每次更新时,云函数先写数据库,再同步上传至云存储,保证两者一致。各管理页更新时只修改配置中自己负责的字段,不影响其他页面数据。

// page_configs/current 文档结构
type PageConfig = {
    home: HomePageData;         // 首页配置
    category: CategoryPageData; // 分类页配置
    age: AgePageData;           // 分龄页配置
    version: number;            // 版本号,每次更新递增
    updatedAt: string;          // 最后更新时间
};

读写流程:

readCurrentConfig()  → 从 page_configs/current 读取当前完整配置
config[page] = data  → 只覆盖对应页面字段(home / category / age
saveConfig(config)   → 写回数据库 → 上传云存储 content/page-config.json

2.2 数据流

┌──────────────────────────────────────────────────────────────────┐
│  Debug 内容管理页(独立页面)                                        │
│                                                                    │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────┐          │
│  │ 首页内容管理    │   │ 分类页内容管理  │   │ 分龄页内容管理  │       │
│  │ homeContent   │   │ categoryContent│   │ ageContent    │       │
│  │ Manage        │   │ Manage        │   │ Manage        │       │
│  └──────┬───────┘   └──────┬───────┘   └──────┬───────┘          │
│         │                  │                   │                   │
│         └────────┬─────────┘─────────┬─────────┘                  │
│                  ▼                   ▼                             │
│     点击「更新」按钮 → 调用云函数 pageContentBuild                    │
│                  │                                                 │
│                  ▼                                                 │
│     ┌─────────────────────────────────────────┐                   │
│     │ 1. 从 page_configs/current 读取当前配置    │                 │
│     │ 2. 只更新对应页面字段(home/category/age  │                 │
│     │ 3. version++ → 写回数据库                  │                 │
│     │ 4. 同步上传至云存储 content/page-config.json│                 │
│     └─────────────────────────────────────────┘                   │
└──────────────────────────────────────────────────────────────────┘

                              ▼

┌──────────────────────────────────────────────────────────────────┐
│  小程序启动                                                        │
│                                                                    │
│  app.onLaunch                                                      │
│    └─ wx.getBackgroundFetchData('pre') 获取预拉取结果               │
│       ├─ 成功 → 存入 globalData + storage 缓存                     │
│       └─ 失败 → 读取 storage 缓存                                  │
│                 ├─ 命中 → 存入 globalData                           │
│                 └─ 未命中 → 调用 pageConfigFetch 云函数兜底          │
│                             └─ 成功 → 存入 globalData + storage     │
│                                                                    │
│  TabBar 页面 onLoad(首页 / 分类页 / 分龄页)                        │
│    ├─ 配置已就绪 → 从 globalData.pageConfig 读取对应字段 → 渲染      │
│    └─ 配置未就绪 → 显示加载态 → 监听配置就绪回调 → 渲染               │
│                                                                    │
│  TabBar 页面 onShow(后续切换)                                      │
│    └─ 直接从 globalData.pageConfig 读取,零延迟                      │
└──────────────────────────────────────────────────────────────────┘

2.3 数据预拉取

小程序数据预拉取通过云函数 pageConfigFetch 实现:

  • app.json 中配置 fetchDataUrl 指向 pageConfigFetch 云函数
  • 小程序冷启动时由微信客户端自动调用,不占用页面加载时间
  • 云函数从 page_configs/current 读取完整配置并返回
// cloudfunctions/pageConfigFetch/index.js
exports.main = async () => {
    const db = cloud.database();
    const { data } = await db.collection('page_configs').doc('current').get();
    return { success: true, data };
};

2.4 页面加载策略

三个展示页(首页、分类页、分龄页)均为 TabBar 页面,不再使用本地 .data.ts 静态数据渲染,改为统一从内存读取预拉取配置,并通过 storage 缓存保证离线可用:

app.onLaunch
  │
  ├─ Step 1: wx.getBackgroundFetchData('pre') 获取预拉取结果
  │           成功 → 存入 globalData.pageConfig
  │                → wx.setStorageSync('pageConfig', data) 写入缓存
  │                → 通知页面就绪
  │
  ├─ Step 2: 预拉取失败 → wx.getStorageSync('pageConfig') 读取缓存
  │           命中 → 存入 globalData.pageConfig → 通知页面就绪
  │
  └─ Step 3: 缓存也为空 → 调用 pageConfigFetch 云函数兜底
              成功 → 存入 globalData.pageConfig
                   → wx.setStorageSync('pageConfig', data) 写入缓存
                   → 通知页面就绪

TabBar 页面 onLoad(仅首次进入触发)
  │
  ├─ globalData.pageConfig 已就绪
  │   → 读取对应字段(home / category / age)→ setData 渲染
  │
  └─ globalData.pageConfig 未就绪
      → 显示加载态(骨架屏 / loading)
      → 注册回调,配置就绪后自动渲染

TabBar 页面 onShow(后续每次切换触发)
  │
  └─ 直接从 globalData.pageConfig 读取,零延迟,无网络请求

三级降级策略: 预拉取 → storage 缓存 → 云函数调用

层级 数据来源 耗时 适用场景
L1 wx.getBackgroundFetchData('pre') 0ms 正常冷启动,预拉取已完成
L2 wx.getStorageSync('pageConfig') ~1ms 预拉取失败,但之前成功过
L3 pageConfigFetch 云函数 200-500ms 首次使用或缓存被清理

设计要点:

  • 配置在 app.onLaunch 时加载一次,存入 globalData.pageConfig,三个页面共享同一份内存数据
  • 每次预拉取或云函数获取成功后,同步写入 storage 作为下次启动的缓存兜底
  • TabBar 页面 onLoad 只执行一次,onShow 每次切换都触发,天然适合「启动时加载一次,后续复用」的模式
  • 不再维护 home.data.tscategory.data.ts 等本地静态数据文件,消除双数据源不一致的问题
  • storage 缓存的 key 为 pageConfig,使用 wx.setStorageSync / wx.getStorageSync 同步读写

2.5 云函数总览

云函数 职责
pageContentBuild 生成指定页面配置(home / category),写入数据库 + 云存储
pageConfigFetch 数据预拉取接口,从数据库读取完整配置返回给客户端
homeAutoRefresh 定时任务,每日 22:00 自动更新 featured 和 hot
homeTimerControl 暂停/启动首页定时刷新任务(更新 settings 集合标记位)
worksheetsQuery 查询 worksheet 列表(支持按分类、状态筛选)
worksheetsUpdateStatus 更新单个 worksheet 的状态

三、首页内容管理

3.1 管理页入口

supportPages/homeContentManage/homeContentManage

进入页面时并行调用两个云函数加载数据:

onLoad
  → Promise.all([
      worksheetsQuery({ status: 'active' }),   // 获取所有 active worksheet
      pageConfigFetch()                         // 获取当前已保存的配置
    ])
  → featured: 始终按 downloads desc 取前 5 自动填充
  → hot: 始终按 likes desc 取前 5 自动填充
  → sections: 从 config.home.sections 回显已保存的分类分区数据

3.2 管理 UI

区块 对应字段 数据来源
定时任务卡片 显示定时任务状态(运行中/已暂停),可切换
今日推荐 featured 自动:downloads 最多的 5 个 active worksheet
热门推荐 hot 自动:likes 最多的 5 个 active worksheet
分类分区 sections 从已保存配置回显,支持手动添加/移除 worksheet

3.3 数据回显逻辑

featured / hot(自动填充,不回显历史配置):

每次进入管理页,始终从当前 active worksheets 中实时计算:

  • featured = 按 downloads 降序取前 5
  • hot = 按 likes 降序取前 5

用户可在自动填充基础上手动添加/移除,点击「更新首页数据」后生效。

sections(从配置回显):

pageConfigFetch 返回的 home.sections 中恢复已保存的分区数据:

CATEGORY_LIST.map(category => {
    saved = savedSectionMap.get(category.id)
    items = saved.items → 通过 wsMap 还原为完整 worksheet 对象
    morePath = category.path   // 从 CATEGORY_LIST 获取,如 /pages/category/category?id=math
})
  • name / icon 始终从本地 CATEGORY_LIST 取(保证最新)
  • morePathCATEGORY_LISTpath 字段获取,指向对应分类页并自动选中该分类
  • items 通过 worksheet id 从 active worksheets 中匹配还原,已下架的 worksheet 自动过滤

3.4 自动刷新与定时任务

今日推荐和热门推荐采用自动化 + 手动微调的方式:

  • 定时任务:云函数 homeAutoRefresh 配置定时触发器,每日 22:00 自动执行,查询最新 top5 数据更新 page-config.json 中的 home.featuredhome.hot
  • 开关控制:通过 settings 集合中 homeAutoRefresh 文档的 enabled 字段控制。管理页提供暂停/启动按钮,调用 homeTimerControl 云函数切换状态。homeAutoRefresh 执行时先检查此标记,enabled === false 则跳过
  • 手动更新:用户可在管理页手动添加/移除 worksheet,点击「更新首页数据」调用 pageContentBuild({ page: 'home', ... }) 生成
定时任务(每日 22:00
  → homeAutoRefresh 云函数
  → 检查 settings/homeAutoRefresh.enabled
  → enabled === false → 跳过执行
  → 查询 top5 downloads → featured
  → 查询 top5 likes → hot
  → readCurrentConfig() → 只更新 home.featured 和 home.hot → saveConfig()

3.5 更新首页数据流程

点击「更新首页数据」
  → pageContentBuild({
      page: 'home',
      featuredIds: [...],
      hotIds: [...],
      sections: [
        { id: 'math', subtitle: '', morePath: '/pages/category/category?id=math', worksheetIds: [...] },
        { id: 'english', subtitle: '', morePath: '/pages/category/category?id=english', worksheetIds: [...] },
        ...
      ]
    })
  → 云函数批量查询所有引用的 worksheet
  → 构建 home 配置(categoryTabs + ageBands + featured + hot + sections
  → readCurrentConfig() → config.home = homeData → saveConfig()

3.6 数据结构

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[];
};

type HomeDisplaySection = {
    id: string;           // 分类 id
    title: string;        // 分类名称
    subtitle: string;
    morePath: string;     // 跳转路径,如 /pages/category/category?id=math
    items: HomeDisplayItem[];
};
模块 生成规则
categoryTabs CATEGORY_LIST_WITH_ALL 生成
ageBands AGE_BANDS 生成
featured 自动:downloads 最多的 5 个 active worksheet
hot 自动:likes 最多的 5 个 active worksheet
sections 管理页配置的 worksheetIdsmorePath 来自 CATEGORY_LIST

四、分类页内容管理

4.1 管理页入口

supportPages/categoryContentManage/categoryContentManage

4.2 管理 UI

分类页管理顶部展示分类选择器(来源 CATEGORY_LIST),默认选中 math

分类头部统计:

指标 查询规则
总数 worksheets.category == 当前分类
草稿 status == draft
线上 status == active
已隐藏 status == hidden

列表展示当前分类下所有 worksheet(按 draft → hidden → active 排序),每项显示预览图、标题、ID、年龄/难度/标签、状态、操作按钮。

操作按钮:

当前状态 按钮样式 可执行操作
draft 绿色按钮 激活上线
active 灰色按钮 下架为 hidden
hidden 黄色按钮 恢复为 draft 或直接激活

4.3 分类页支持 id 参数

分类页 /pages/category/category 支持通过 URL 参数 id 指定默认选中的分类:

/pages/category/category?id=english  → 默认选中「英语启蒙」
/pages/category/category?id=math     → 默认选中「数感启蒙」
/pages/category/category              → 默认选中「全部」

首页 sections 的「查看更多」链接使用此参数跳转到对应分类。

4.4 数据结构

type CategoryPageData = {
    searchPlaceholder: string;
    categories: Array<{
        id: string;
        name: string;
        icon: string;
        items: CategoryItem[];
    }>;
};

生成时从 worksheets 集合查询 status == active 的内容,按分类分组、按 sortOrder 排序,转换为 CategoryItem

4.5 更新流程

分类页管理 → 管理 worksheet 状态和排序
  → 点击「更新分类数据」
  → 查询所有 active worksheets,按 category 分组
  → 调用 pageContentBuild({ page: 'category' })
  → 云函数更新 page_configs/current 中的 category 字段 + 同步云存储

五、分龄页内容管理

5.1 新增 age.config.ts

miniprogram/pages/age/ 下新增 age.config.ts,将年龄段划分、能力目标、默认 worksheet 写死在配置中,作为兜底数据和管理页的基础结构。

export type AgeBandConfig = {
    key: AgeBandKey;
    label: string;
    subLabel: string;
    abilities: AbilityItem[];
    weeks: WeekPlan[];
};

worksheetIds 初始为空数组,由分龄管理页选择后填入。worksheetTitles 作为兜底展示。

5.2 管理 UI

分龄管理按年龄段切换:3-4 岁 | 4-5 岁 | 5-6 岁 | 6-7 岁 | 7-8 岁

模块 操作 说明
能力目标 只读展示 来自 age.config.ts,不需要在管理页修改
四周路线 选择 worksheet 每周选择 4 个 worksheet,主题来自 config
推荐内容 选择 worksheet 动态数据,展示 likes 数量最高的 6 个worksheet

5.3 更新流程

分龄管理 → 选择年龄段 → 为每周选择 worksheet → 选择推荐内容
  → 点击「更新分龄数据」
  → 合并 age.config.ts 的能力目标 + 管理页选择的 worksheet 详情
  → 调用 pageContentBuild({ page: 'age', data: ... })
  → 云函数更新 page_configs/current 中的 age 字段 + 同步云存储

六、Debug 页入口结构

supportPages/debug/debug
  ├─ 分类基础数据管理          (已有:同步 CATEGORY_LIST → categories
  ├─ Worksheet 内容池          (已有:状态、分类、上下架、预览)
  └─ 页面内容管理               (新增)
       ├─ 📋 分类页内容管理     → supportPages/categoryContentManage
       ├─ 🏠 首页内容管理       → supportPages/homeContentManage
       └─ 📅 分龄页内容管理     → supportPages/ageContentManage(待开发)

七、实施计划

Phase 1:基础设施

  1. 实现 pageContentBuild 云函数(读写数据库 + 同步云存储)
  2. 实现 pageConfigFetch 云函数(数据预拉取接口)
  3. 实现 worksheetsQuery / worksheetsUpdateStatus 云函数

Phase 2:分类页管理

  1. 实现分类页管理页(worksheet 列表、状态管理)
  2. 实现「更新分类数据」→ 生成 category 配置
  3. 分类页支持 ?id=xxx 参数默认选中分类

Phase 3:首页管理

  1. 实现首页管理页(featured / hot 自动填充 + sections 配置回显)
  2. 实现 worksheet 选择器弹窗
  3. 实现定时任务(homeAutoRefresh + homeTimerControl
  4. 实现「更新首页数据」→ 生成 home 配置

Phase 4:分龄页管理(待开发)

  1. 新增 age.config.ts 配置文件
  2. 实现分龄管理页(年龄段切换、周 worksheet 选择)
  3. 实现「更新分龄数据」→ 合并 config + worksheet 详情

八、风险与约束

风险 处理方式
预拉取失败 依次降级:storage 缓存 → pageConfigFetch 云函数兜底
配置生成失败 云函数返回错误,管理页提示重试,线上数据不受影响
首页配置引用了 hidden 内容 sections 回显时通过 wsMap 匹配,已下架的自动过滤
更新某页数据清空其他页 云函数从数据库读取完整配置,只覆盖对应字段,其他页面数据不受影响
分龄内容不适龄 选择器按年龄段默认过滤
并发更新冲突 云函数使用 version 乐观锁,冲突时提示重新加载后重试

九、关键文件索引

文件 / 模块 职责
supportPages/homeContentManage/ 首页内容管理页
supportPages/categoryContentManage/ 分类页内容管理页
cloudfunctions/pageContentBuild/ 生成 page-config 中指定页面的配置
cloudfunctions/pageConfigFetch/ 数据预拉取接口,返回完整页面配置
cloudfunctions/homeAutoRefresh/ 定时任务,每日自动更新首页推荐位数据
cloudfunctions/homeTimerControl/ 暂停/启动首页定时刷新任务
cloudfunctions/worksheetsQuery/ 查询 worksheet 列表
cloudfunctions/worksheetsUpdateStatus/ 更新 worksheet 状态
pages/category/category.ts 分类页,支持 ?id=xxx 参数选中分类
pages/age/age.config.ts 分龄页兜底配置
pages/home/home.data.ts 首页兜底数据
pages/category/category.data.ts 分类页兜底数据
core/data/categories.ts CATEGORY_LIST 分类定义(id/name/icon/path
content/page-config.json(云存储) 三个页面的统一配置 JSON
page_configs/current(云数据库) 配置数据源(Source of Truth
settings/homeAutoRefresh(云数据库) 定时任务开关标记