feat: 我的、设置、打印指南页面开发完成

This commit is contained in:
R524809
2026-05-07 16:32:49 +08:00
parent 0737c6165e
commit c732111e12
43 changed files with 1702 additions and 837 deletions
+138 -104
View File
@@ -1,6 +1,6 @@
# 页面内容管理方案
> 版本:v2.0 | 最后更新:2026-04-30
> 版本:v3.0 | 最后更新:2026-05-07
> 配套文档:[Worksheet 发布方案](./Worksheet发布方案.md) | [小程序云开发方案](./小程序云开发方案.md)
---
@@ -15,7 +15,11 @@ worksheet 已可通过 Debug 发布进入云数据库,但三个展示页仍使
| 分类页 | `pages/category/category.data.ts` | 新 worksheet 发布后不会自动出现 |
| 分龄页 | `pages/age/age.ts` 内 Mock | 推荐内容和周路线无法运营配置 |
**目标:** 通过 Debug 管理页配置三个页面的内容,生成统一配置写入云数据库并同步至云存储,小程序启动时通过**数据预拉取**获取并渲染。
**目标:**
- **首页**:通过 Debug 管理页配置内容,生成配置写入云数据库并同步至云存储,小程序启动时通过数据预拉取获取并渲染
- **分类页**:每次进入时通过云函数实时查询所有已上线 worksheet,前端分组排序展示(v3.0 调整,因需展示收藏/下载等实时数据)
- **分龄页**:独立方案(详见分龄页文档)
---
@@ -23,18 +27,24 @@ worksheet 已可通过 Debug 发布进入云数据库,但三个展示页仍使
### 2.1 数据存储与同步
**数据源(Source of Truth** 云数据库 `page_configs` 集合,文档 `_id = 'current'`
**首页数据:** 通过 `page-config.json` 预拉取方式加载(保持不变)
**数据交付(Delivery):** 云存储 `content/page-config.json`(供小程序预拉取使用
**分类页数据:** 通过云函数接口实时查询(v3.0 调整
每次更新时,云函数先写数据库,再同步上传至云存储,保证两者一致。各管理页更新时只修改配置中自己负责的字段,不影响其他页面数据
分类页需要展示收藏数和下载数等实时数据,不再适合通过定期更新 `page-config.json` 的方式同步。改为每次进入页面时通过云函数实时查询所有已上线 worksheet,前端本地排序和分类展示
**数据源(Source of Truth):** 云数据库 `page_configs` 集合,文档 `_id = 'current'`(仅存储 home 配置)
**数据交付(Delivery):** 云存储 `content/page-config.json`(仅包含 home 数据,供小程序预拉取使用)
每次首页更新时,云函数先写数据库,再同步上传至云存储。
```ts
// page_configs/current 文档结构
type PageConfig = {
home: HomePageData; // 首页配置
category: CategoryPageData; // 分类页配置
age: AgePageData; // 分龄页配置
// category: 已移除,改为实时接口查询
// age: 已移除,改为独立方案
version: number; // 版本号,每次更新递增
updatedAt: string; // 最后更新时间
};
@@ -44,7 +54,7 @@ type PageConfig = {
```
readCurrentConfig() → 从 page_configs/current 读取当前完整配置
config[page] = data → 只覆盖对应页面字段(home / category / age
config.home = data → 只覆盖首页字段
saveConfig(config) → 写回数据库 → 上传云存储 content/page-config.json
```
@@ -60,16 +70,18 @@ saveConfig(config) → 写回数据库 → 上传云存储 content/page-config
│ │ Manage │ │ Manage │ │ Manage │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
└────────┬─────────┘─────────┬─────────┘
▼ ▼
点击「更新」按钮 → 调用云函数 pageContentBuild
▼ ▼ ▼
点击「更新首页」→ 仅管理上下线状态 待开发
调用 pageContentBuild (不再生成分类页配置)
只更新 home 配置
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 1. 从 page_configs/current 读取当前配置 │ │
│ │ 2. 更新对应页面字段(home/category/age │ │
│ │ 3. version++ → 写回数据库 │ │
│ │ 4. 同步上传至云存储 content/page-config.json│ │
│ │ 2. 更新 home 字段 │ │
│ │ 3. 清空 category / age 字段(注释保留) │ │
│ │ 4. version++ → 写回数据库 │ │
│ │ 5. 同步上传至云存储 content/page-config.json│ │
│ └─────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
@@ -86,22 +98,22 @@ saveConfig(config) → 写回数据库 → 上传云存储 content/page-config
│ └─ 未命中 → 调用 pageConfigFetch 云函数兜底 │
│ └─ 成功 → 存入 globalData + storage │
│ │
TabBar 页面 onLoad(首页 / 分类页 / 分龄页)
├─ 配置已就绪 → 从 globalData.pageConfig 读取对应字段 → 渲染
│ └─ 配置未就绪 → 显示加载态 → 监听配置就绪回调 → 渲染 │
首页 onLoad
└─ 从 globalData.pageConfig.home 读取 → 渲染
│ │
TabBar 页面 onShow(后续切换)
│ └─ 直接从 globalData.pageConfig 读取,零延迟
分类页 onShow(每次进入都重新请求)
│ └─ 调用 worksheetsQuery({ status: 'active' })
│ → 前端按分类分组 + 排序 → 渲染 │
└──────────────────────────────────────────────────────────────────┘
```
### 2.3 数据预拉取
小程序数据预拉取通过云函数 `pageConfigFetch` 实现:
小程序数据预拉取通过云函数 `pageConfigFetch` 实现(仅用于首页数据)
-`app.json` 中配置 `fetchDataUrl` 指向 `pageConfigFetch` 云函数
- 小程序冷启动时由微信客户端自动调用,不占用页面加载时间
- 云函数从 `page_configs/current` 读取完整配置并返回
- 云函数从 `page_configs/current` 读取配置并返回(主要是 home 字段)
```js
// cloudfunctions/pageConfigFetch/index.js
@@ -114,39 +126,37 @@ exports.main = async () => {
### 2.4 页面加载策略
三个展示页(首页、分类页、分龄页)均为 TabBar 页面,不再使用本地 `.data.ts` 静态数据渲染,改为**统一从内存读取预拉取配置**,并通过 storage 缓存保证离线可用:
**首页**:通过预拉取 + storage 缓存加载(保持不变)
```
app.onLaunch
├─ Step 1: wx.getBackgroundFetchData('pre') 获取预拉取结果
│ 成功 → 存入 globalData.pageConfig
│ → wx.setStorageSync('pageConfig', data) 写入缓存
│ → 通知页面就绪
│ 成功 → 存入 globalData.pageConfig → 写入 storage 缓存
├─ Step 2: 预拉取失败 → wx.getStorageSync('pageConfig') 读取缓存
│ 命中 → 存入 globalData.pageConfig → 通知页面就绪
│ 命中 → 存入 globalData.pageConfig
└─ Step 3: 缓存也为空 → 调用 pageConfigFetch 云函数兜底
成功 → 存入 globalData.pageConfig
→ wx.setStorageSync('pageConfig', data) 写入缓存
→ 通知页面就绪
成功 → 存入 globalData.pageConfig + storage 缓存
TabBar 页面 onLoad(仅首次进入触发)
├─ globalData.pageConfig 已就绪
│ → 读取对应字段(home / category / age)→ setData 渲染
└─ globalData.pageConfig 未就绪
→ 显示加载态(骨架屏 / loading)
→ 注册回调,配置就绪后自动渲染
TabBar 页面 onShow(后续每次切换触发)
└─ 直接从 globalData.pageConfig 读取,零延迟,无网络请求
首页 onLoad
└─ 从 globalData.pageConfig.home 读取 → 渲染
```
**三级降级策略:** 预拉取 → storage 缓存 → 云函数调用
**分类页**:每次 onShow 实时查询(v3.0 新方案)
```
分类页 onShow(每次进入触发)
├─ 清空当前列表数据,显示 loading
├─ 调用 worksheetsQuery({ status: 'active' }) 获取所有已上线 worksheet
├─ 前端按分类分组
├─ 每个分类内排序:updatedAt 最新的 2 个置顶 → 其余按 downloads 降序
└─ setData 渲染,切换分类时无需再次请求
```
**三级降级策略(首页):** 预拉取 → storage 缓存 → 云函数调用
| 层级 | 数据来源 | 耗时 | 适用场景 |
| --- | ----------------------------------- | ------- | -------------------- |
@@ -154,24 +164,16 @@ TabBar 页面 onShow(后续每次切换触发)
| L2 | `wx.getStorageSync('pageConfig')` | ~1ms | 预拉取失败,但之前成功过 |
| L3 | `pageConfigFetch` 云函数 | 200-500ms | 首次使用或缓存被清理 |
**设计要点:**
- 配置在 `app.onLaunch` 时加载一次,存入 `globalData.pageConfig`,三个页面共享同一份内存数据
- 每次预拉取或云函数获取成功后,同步写入 storage 作为下次启动的缓存兜底
- TabBar 页面 `onLoad` 只执行一次,`onShow` 每次切换都触发,天然适合「启动时加载一次,后续复用」的模式
- 不再维护 `home.data.ts``category.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 的状态 |
| 云函数 | 职责 |
| ----------------------- | ------------------------------------------------- |
| `pageContentBuild` | 生成首页配置(home),写入数据库 + 云存储 |
| `pageConfigFetch` | 数据预拉取接口,从数据库读取配置返回给客户端(首页用) |
| `homeAutoRefresh` | 定时任务,每日 22:00 自动更新 featured 和 hot |
| `homeTimerControl` | 暂停/启动首页定时刷新任务(更新 settings 集合标记位) |
| `worksheetsQuery` | 查询 worksheet 列表(支持按分类、状态筛选,分类页实时调用)|
| `worksheetsUpdateStatus`| 更新单个 worksheet 的状态 |
---
@@ -263,9 +265,15 @@ CATEGORY_LIST.map(category => {
})
→ 云函数批量查询所有引用的 worksheet
→ 构建 home 配置(categoryTabs + ageBands + featured + hot + sections
→ readCurrentConfig() → config.home = homeData → saveConfig()
→ readCurrentConfig()
→ config.home = homeData
→ config.category = null // 清空分类页配置(注释保留,后期可调整)
→ config.age = null // 清空分龄页配置(注释保留,后期可调整)
→ saveConfig()
```
> **注意:** 清空 category 和 age 字段的代码需加注释标记,方便后期对这部分逻辑进行调整或注销。分类页已改为实时接口查询,不再依赖 page-config.json 中的 category 数据。
### 3.6 数据结构
```ts
@@ -297,15 +305,63 @@ type HomeDisplaySection = {
---
## 四、分类页内容管理
## 四、分类页
### 4.1 管理页入口
### 4.1 数据加载方式(v3.0 调整)
分类页不再通过 `page-config.json` 获取数据,改为**每次 onShow 时实时查询云端数据**:
```
分类页 onShow
├─ 清空 displayItems,显示 loading
├─ 调用 worksheetsQuery({ status: 'active' })
│ → 一次性获取所有分类下已上线的 worksheet
├─ 前端按 CATEGORY_LIST 分组
├─ 每个分类内排序(含「全部」):
│ 1. updatedAt 最新的 2 个置顶(最近修改优先展示)
│ 2. 其余按 downloads 降序排列
└─ setData 渲染
→ 切换分类时直接从内存数据筛选,无需再次请求
```
**设计要点:**
- 每次进入分类页都重新请求,保证收藏数、下载数等实时数据的准确性
- 一次请求获取全部 active worksheet,切换分类时前端本地筛选,体验流畅
- 排序规则统一:先展示 2 个最近更新的(让用户看到新内容),再按热度(下载量)排序
### 4.2 排序规则详解
所有分类(包括「全部」)使用相同的排序逻辑:
```ts
function sortWorksheets(items: Worksheet[]): Worksheet[] {
// 按 updatedAt 降序,取前 2 个作为「最新」
const sorted = [...items].sort((a, b) =>
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
);
const recent = sorted.slice(0, 2);
const recentIds = new Set(recent.map(w => w._id));
// 剩余按 downloads 降序
const rest = sorted.filter(w => !recentIds.has(w._id))
.sort((a, b) => (b.downloads || 0) - (a.downloads || 0));
return [...recent, ...rest];
}
```
| 位置 | 排序依据 | 说明 |
| --- | --- | --- |
| 前 2 个 | `updatedAt` 降序 | 最近修改/新上线的 worksheet 优先曝光 |
| 第 3 个起 | `downloads` 降序 | 按热度排序,下载多的排前面 |
### 4.3 内容管理页(仅管理上下线)
`supportPages/categoryContentManage/categoryContentManage`
### 4.2 管理 UI
分类页管理顶部展示分类选择器(来源 `CATEGORY_LIST`),默认选中 `math`
分类页内容管理页**仅保留 worksheet 上下线管理功能**,不再提供「更新分类数据」按钮:
分类头部统计:
@@ -326,7 +382,9 @@ type HomeDisplaySection = {
| `active` | 灰色按钮 | 下架为 hidden |
| `hidden` | 黄色按钮 | 恢复为 draft 或直接激活 |
### 4.3 分类页支持 id 参数
> **已移除:**「更新分类数据」按钮及相关代码已注释,分类页数据改为实时接口查询。
### 4.4 分类页支持 id 参数
分类页 `/pages/category/category` 支持通过 URL 参数 `id` 指定默认选中的分类:
@@ -338,32 +396,6 @@ type HomeDisplaySection = {
首页 sections 的「查看更多」链接使用此参数跳转到对应分类。
### 4.4 数据结构
```ts
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 字段 + 同步云存储
```
---
## 五、分龄页内容管理
@@ -405,11 +437,12 @@ supportPages/debug/debug
2. 实现 `pageConfigFetch` 云函数(数据预拉取接口)
3. 实现 `worksheetsQuery` / `worksheetsUpdateStatus` 云函数
### Phase 2:分类页管理 ✅
### Phase 2:分类页管理 ✅v3.0 调整)
1. 实现分类页管理页(worksheet 列表、状态管理)
2. 实现「更新分类数据」→ 生成 category 配置
2. ~~实现「更新分类数据」→ 生成 category 配置~~(已移除,改为实时接口)
3. 分类页支持 `?id=xxx` 参数默认选中分类
4. 分类页改为 onShow 实时查询 worksheetsQuery + 前端排序
### Phase 3:首页管理 ✅
@@ -432,10 +465,11 @@ supportPages/debug/debug
| 风险 | 处理方式 |
| ----------------- | ----------------------------------------- |
| 预拉取失败 | 依次降级:storage 缓存 → pageConfigFetch 云函数兜底 |
| 首页预拉取失败 | 依次降级:storage 缓存 → pageConfigFetch 云函数兜底 |
| 分类页接口请求失败 | 显示错误提示 + 重试按钮,或降级使用本地 category.data.ts |
| 配置生成失败 | 云函数返回错误,管理页提示重试,线上数据不受影响 |
| 首页配置引用了 hidden 内容 | sections 回显时通过 wsMap 匹配,已下架的自动过滤 |
| 更新某页数据清空其他页 | 云函数从数据库读取完整配置,只覆盖对应字段,其他页面数据不受影响 |
| 分类页每次请求性能 | 一次查询所有 active worksheet(通常 < 100 条),前端分组排序,耗时可控 |
| 分龄内容不适龄 | 选择器按年龄段默认过滤 |
| 并发更新冲突 | 云函数使用 version 乐观锁,冲突时提示重新加载后重试 |
@@ -446,20 +480,20 @@ supportPages/debug/debug
| 文件 / 模块 | 职责 |
| -------------------------------------------- | ------------------------------ |
| `supportPages/homeContentManage/` | 首页内容管理页 |
| `supportPages/categoryContentManage/` | 分类页内容管理页 |
| `cloudfunctions/pageContentBuild/` | 生成 page-config 中指定页面的配置 |
| `cloudfunctions/pageConfigFetch/` | 数据预拉取接口,返回完整页面配置 |
| `supportPages/categoryContentManage/` | 分类页内容管理页(仅上下线管理) |
| `cloudfunctions/pageContentBuild/` | 生成首页配置,写入数据库 + 云存储 |
| `cloudfunctions/pageConfigFetch/` | 数据预拉取接口,返回首页配置 |
| `cloudfunctions/homeAutoRefresh/` | 定时任务,每日自动更新首页推荐位数据 |
| `cloudfunctions/homeTimerControl/` | 暂停/启动首页定时刷新任务 |
| `cloudfunctions/worksheetsQuery/` | 查询 worksheet 列表 |
| `cloudfunctions/worksheetsQuery/` | 查询 worksheet 列表(分类页实时调用) |
| `cloudfunctions/worksheetsUpdateStatus/` | 更新 worksheet 状态 |
| `pages/category/category.ts` | 分类页,支持 `?id=xxx` 参数选中分类 |
| `pages/category/category.ts` | 分类页,onShow 实时查询 + 前端排序 |
| `pages/age/age.config.ts` | 分龄页配置(详见 [分龄页方案](./分龄页内容管理方案.md) |
| `skills/generate-age-abilities/SKILL.md` | AI Skill:生成分龄页能力目标 |
| `skills/generate-age-weekly-plans/SKILL.md` | AI Skill:生成分龄页学习路线 |
| `pages/home/home.data.ts` | 首页兜底数据 |
| `pages/category/category.data.ts` | 分类页兜底数据 |
| `pages/category/category.data.ts` | 分类页兜底数据(接口失败时降级使用) |
| `core/data/categories.ts` | CATEGORY_LIST 分类定义(id/name/icon/path |
| `content/page-config.json`(云存储) | 三个页面的统一配置 JSON |
| `content/page-config.json`(云存储) | 首页配置 JSON(仅 home 字段有效) |
| `page_configs/current`(云数据库) | 配置数据源(Source of Truth |
| `settings/homeAutoRefresh`(云数据库) | 定时任务开关标记 |