From c112c70bab240349da75db464ee49031977f8269 Mon Sep 17 00:00:00 2001 From: R524809 Date: Thu, 30 Apr 2026 13:30:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=AE=8C=E6=88=90=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E3=80=81=E5=88=86=E7=B1=BB=E9=A1=B5=E5=86=85=E5=AE=B9=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cloudfunctions/homeAutoRefresh/config.json | 9 + cloudfunctions/homeAutoRefresh/index.js | 136 +++++++ cloudfunctions/homeAutoRefresh/package.json | 8 + cloudfunctions/homeTimerControl/index.js | 38 ++ cloudfunctions/homeTimerControl/package.json | 8 + cloudfunctions/pageConfigFetch/index.js | 23 ++ cloudfunctions/pageConfigFetch/package.json | 8 + cloudfunctions/pageContentBuild/index.js | 189 +++++++-- cloudfunctions/worksheetsQuery/index.js | 9 +- docs/TodoList.md | 1 + docs/应用起名.md | 36 ++ docs/页面内容管理方案.md | 54 ++- miniprogram/app.json | 1 + miniprogram/core/data/categories.ts | 43 +- miniprogram/core/models/category.ts | 1 + miniprogram/pages/category/category.ts | 11 + .../categoryContentManage.less | 30 +- .../categoryContentManage.ts | 12 +- .../categoryContentManage.wxml | 20 +- miniprogram/supportPages/debug/debug.ts | 7 + .../homeContentManage/homeContentManage.json | 10 + .../homeContentManage/homeContentManage.less | 277 +++++++++++++ .../homeContentManage/homeContentManage.ts | 371 ++++++++++++++++++ .../homeContentManage/homeContentManage.wxml | 217 ++++++++++ project.private.config.json | 11 +- 25 files changed, 1465 insertions(+), 65 deletions(-) create mode 100644 cloudfunctions/homeAutoRefresh/config.json create mode 100644 cloudfunctions/homeAutoRefresh/index.js create mode 100644 cloudfunctions/homeAutoRefresh/package.json create mode 100644 cloudfunctions/homeTimerControl/index.js create mode 100644 cloudfunctions/homeTimerControl/package.json create mode 100644 cloudfunctions/pageConfigFetch/index.js create mode 100644 cloudfunctions/pageConfigFetch/package.json create mode 100644 docs/TodoList.md create mode 100644 docs/应用起名.md create mode 100644 miniprogram/supportPages/homeContentManage/homeContentManage.json create mode 100644 miniprogram/supportPages/homeContentManage/homeContentManage.less create mode 100644 miniprogram/supportPages/homeContentManage/homeContentManage.ts create mode 100644 miniprogram/supportPages/homeContentManage/homeContentManage.wxml diff --git a/cloudfunctions/homeAutoRefresh/config.json b/cloudfunctions/homeAutoRefresh/config.json new file mode 100644 index 0000000..5c62dd5 --- /dev/null +++ b/cloudfunctions/homeAutoRefresh/config.json @@ -0,0 +1,9 @@ +{ + "triggers": [ + { + "name": "dailyRefresh", + "type": "timer", + "config": "0 0 22 * * * *" + } + ] +} diff --git a/cloudfunctions/homeAutoRefresh/index.js b/cloudfunctions/homeAutoRefresh/index.js new file mode 100644 index 0000000..31930cc --- /dev/null +++ b/cloudfunctions/homeAutoRefresh/index.js @@ -0,0 +1,136 @@ +const cloud = require('wx-server-sdk'); + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); + +const CONFIG_PATH = 'content/page-config.json'; + +const DIFFICULTY_LABELS = { + 1: '入门', + 2: '基础', + 3: '进阶', + 4: '挑战', +}; + +function ageBand(min, max) { + return `${min}-${max}岁`; +} + +function toHomeDisplayItem(ws) { + return { + id: ws._id, + title: ws.title || '', + subtitle: ws.subtitle || '', + category: ws.category || '', + ageBand: ageBand(ws.ageMin || 0, ws.ageMax || 0), + difficulty: DIFFICULTY_LABELS[ws.difficulty] || '基础', + icon: '', + img: ws.previewImg || '', + badge: ws.isNew ? 'NEW' : ws.isHot ? 'HOT' : '', + path: ws.path || '', + available: true, + }; +} + +async function readCurrentConfig() { + const db = cloud.database(); + try { + const { data } = await db + .collection('page_configs') + .doc('current') + .get(); + return data; + } catch { + return { home: null, category: null, age: null, version: 0 }; + } +} + +async function saveConfig(config) { + const db = cloud.database(); + const toSave = { ...config }; + delete toSave._id; + try { + await db.collection('page_configs').doc('current').get(); + await db.collection('page_configs').doc('current').update({ + data: toSave, + }); + } catch { + await db.collection('page_configs').doc('current').set({ + data: toSave, + }); + } + const buffer = Buffer.from(JSON.stringify(config), 'utf-8'); + await cloud.uploadFile({ + cloudPath: CONFIG_PATH, + fileContent: buffer, + }); +} + +async function isEnabled(db) { + try { + const { data } = await db + .collection('settings') + .doc('homeAutoRefresh') + .get(); + return data.enabled !== false; + } catch { + // Document doesn't exist yet — default to enabled + return true; + } +} + +exports.main = async () => { + try { + const db = cloud.database(); + + // Check if timer is enabled + if (!(await isEnabled(db))) { + return { success: true, message: '定时任务已暂停,跳过执行' }; + } + + // Query top 5 by downloads + const { data: topDownloads } = await db + .collection('worksheets') + .where({ status: 'active' }) + .orderBy('downloads', 'desc') + .limit(5) + .get(); + + // Query top 5 by likes + const { data: topLikes } = await db + .collection('worksheets') + .where({ status: 'active' }) + .orderBy('likes', 'desc') + .limit(5) + .get(); + + const featured = topDownloads.map(toHomeDisplayItem); + const hot = topLikes.map(toHomeDisplayItem); + + // Read current config, only update featured & hot + const config = await readCurrentConfig(); + if (!config.home) { + config.home = {}; + } + config.home.featured = featured; + config.home.hot = hot; + config.version = (config.version || 0) + 1; + config.updatedAt = new Date().toISOString(); + + await saveConfig(config); + + return { + success: true, + data: { + version: config.version, + featured: featured.length, + hot: hot.length, + }, + }; + } catch (error) { + return { + success: false, + message: + error instanceof Error ? error.message : '自动刷新首页推荐失败', + }; + } +}; diff --git a/cloudfunctions/homeAutoRefresh/package.json b/cloudfunctions/homeAutoRefresh/package.json new file mode 100644 index 0000000..2a9c892 --- /dev/null +++ b/cloudfunctions/homeAutoRefresh/package.json @@ -0,0 +1,8 @@ +{ + "name": "home-auto-refresh", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "^3.0.4" + } +} diff --git a/cloudfunctions/homeTimerControl/index.js b/cloudfunctions/homeTimerControl/index.js new file mode 100644 index 0000000..847fa81 --- /dev/null +++ b/cloudfunctions/homeTimerControl/index.js @@ -0,0 +1,38 @@ +const cloud = require('wx-server-sdk'); + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); + +exports.main = async (event) => { + try { + const db = cloud.database(); + const enabled = !!event.enabled; + + const collection = db.collection('settings'); + const docId = 'homeAutoRefresh'; + + try { + await collection.doc(docId).get(); + await collection.doc(docId).update({ + data: { enabled, updatedAt: db.serverDate() }, + }); + } catch { + // Document doesn't exist, create it + await collection.doc(docId).set({ + data: { enabled, updatedAt: db.serverDate() }, + }); + } + + return { + success: true, + data: { enabled }, + }; + } catch (error) { + return { + success: false, + message: + error instanceof Error + ? error.message + : '更新定时任务状态失败', + }; + } +}; diff --git a/cloudfunctions/homeTimerControl/package.json b/cloudfunctions/homeTimerControl/package.json new file mode 100644 index 0000000..a6e976b --- /dev/null +++ b/cloudfunctions/homeTimerControl/package.json @@ -0,0 +1,8 @@ +{ + "name": "home-timer-control", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "^3.0.4" + } +} diff --git a/cloudfunctions/pageConfigFetch/index.js b/cloudfunctions/pageConfigFetch/index.js new file mode 100644 index 0000000..cf7e934 --- /dev/null +++ b/cloudfunctions/pageConfigFetch/index.js @@ -0,0 +1,23 @@ +const cloud = require('wx-server-sdk'); + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); + +exports.main = async () => { + try { + const db = cloud.database(); + const { data } = await db + .collection('page_configs') + .doc('current') + .get(); + return { + success: true, + data, + }; + } catch (error) { + return { + success: false, + message: + error instanceof Error ? error.message : '获取页面配置失败', + }; + } +}; diff --git a/cloudfunctions/pageConfigFetch/package.json b/cloudfunctions/pageConfigFetch/package.json new file mode 100644 index 0000000..1ea101d --- /dev/null +++ b/cloudfunctions/pageConfigFetch/package.json @@ -0,0 +1,8 @@ +{ + "name": "page-config-fetch", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "^3.0.4" + } +} diff --git a/cloudfunctions/pageContentBuild/index.js b/cloudfunctions/pageContentBuild/index.js index b6fadde..ad915b0 100644 --- a/cloudfunctions/pageContentBuild/index.js +++ b/cloudfunctions/pageContentBuild/index.js @@ -36,15 +36,68 @@ function toDisplayItem(ws) { }; } +function toHomeDisplayItem(ws) { + return { + id: ws._id, + title: ws.title || '', + subtitle: ws.subtitle || '', + category: ws.category || '', + ageBand: ageBand(ws.ageMin || 0, ws.ageMax || 0), + difficulty: DIFFICULTY_LABELS[ws.difficulty] || '基础', + icon: '', + img: ws.previewImg || '', + badge: ws.isNew ? 'NEW' : ws.isHot ? 'HOT' : '', + path: ws.path || '', + available: true, + }; +} + +const HOME_AGE_BANDS = [ + { key: '3-4', label: '3-4 岁', desc: '启蒙认知', path: '/pages/age/age' }, + { key: '4-5', label: '4-5 岁', desc: '基础练习', path: '/pages/age/age' }, + { key: '5-6', label: '5-6 岁', desc: '能力提升', path: '/pages/age/age' }, + { key: '6-7', label: '6-7 岁', desc: '幼小衔接', path: '/pages/age/age' }, + { key: '7-8', label: '7-8 岁', desc: '知识拓展', path: '/pages/age/age' }, +]; + async function readCurrentConfig() { + const db = cloud.database(); try { - const res = await cloud.downloadFile({ fileID: CONFIG_PATH }); - return JSON.parse(res.fileContent.toString('utf-8')); + const { data } = await db + .collection('page_configs') + .doc('current') + .get(); + return data; } catch { return { home: null, category: null, age: null, version: 0 }; } } +async function saveConfig(config) { + const db = cloud.database(); + + // 1. Save to database (source of truth) + const toSave = { ...config }; + delete toSave._id; // _id can't be in update data + try { + await db.collection('page_configs').doc('current').get(); + await db.collection('page_configs').doc('current').update({ + data: toSave, + }); + } catch { + await db.collection('page_configs').doc('current').set({ + data: toSave, + }); + } + + // 2. Upload to cloud storage (for mini-program pre-fetch) + const buffer = Buffer.from(JSON.stringify(config), 'utf-8'); + await cloud.uploadFile({ + cloudPath: CONFIG_PATH, + fileContent: buffer, + }); +} + async function buildCategoryData(db) { // Get all categories const { data: categories } = await db @@ -87,38 +140,126 @@ async function buildCategoryData(db) { return { categoryData, stats }; } +async function buildHomeData(db, event) { + const featuredIds = Array.isArray(event.featuredIds) ? event.featuredIds : []; + const hotIds = Array.isArray(event.hotIds) ? event.hotIds : []; + const sectionConfigs = Array.isArray(event.sections) ? event.sections : []; + + // Collect all referenced worksheet ids + const allIds = new Set([ + ...featuredIds, + ...hotIds, + ...sectionConfigs.flatMap((s) => s.worksheetIds || []), + ]); + + // Batch query all referenced worksheets + const wsMap = {}; + if (allIds.size > 0) { + const _ = db.command; + const { data } = await db + .collection('worksheets') + .where({ _id: _.in([...allIds]) }) + .limit(100) + .get(); + for (const ws of data) { + wsMap[ws._id] = ws; + } + } + + // Build categoryTabs from categories collection + const { data: categories } = await db + .collection('categories') + .orderBy('sortOrder', 'asc') + .get(); + const categoryTabs = [ + { id: 'all', name: '全部' }, + ...categories.map((c) => ({ id: c._id, name: c.name || '' })), + ]; + + // Build featured / hot arrays + const featured = featuredIds + .filter((id) => wsMap[id]) + .map((id) => toHomeDisplayItem(wsMap[id])); + const hot = hotIds + .filter((id) => wsMap[id]) + .map((id) => toHomeDisplayItem(wsMap[id])); + + // Build sections + const sections = sectionConfigs + .filter((s) => s.id && Array.isArray(s.worksheetIds)) + .map((s) => { + const cat = categories.find((c) => c._id === s.id); + return { + id: s.id, + title: cat ? cat.name : s.id, + subtitle: s.subtitle || '', + morePath: s.morePath || '', + items: (s.worksheetIds || []) + .filter((id) => wsMap[id]) + .map((id) => toHomeDisplayItem(wsMap[id])), + }; + }); + + const homeData = { + searchPlaceholder: '搜索你喜欢的练习册...', + categoryTabs, + ageBands: HOME_AGE_BANDS, + featured, + hot, + sections, + }; + + return { homeData }; +} + exports.main = async (event) => { try { const db = cloud.database(); const page = String(event.page || '').trim(); - if (page !== 'category') { - throw new Error('当前仅支持 page=category'); + if (page !== 'category' && page !== 'home') { + throw new Error('page 参数不合法,支持 category / home'); } - const { categoryData, stats } = await buildCategoryData(db); - - // Read current config, merge, bump version const config = await readCurrentConfig(); - config.category = categoryData; - config.version = (config.version || 0) + 1; - config.updatedAt = new Date().toISOString(); - // Upload to cloud storage - const buffer = Buffer.from(JSON.stringify(config), 'utf-8'); - await cloud.uploadFile({ - cloudPath: CONFIG_PATH, - fileContent: buffer, - }); + if (page === 'category') { + const { categoryData, stats } = await buildCategoryData(db); + config.category = categoryData; + config.version = (config.version || 0) + 1; + config.updatedAt = new Date().toISOString(); - return { - success: true, - data: { - version: config.version, - updatedAt: config.updatedAt, - stats, - }, - }; + await saveConfig(config); + + return { + success: true, + data: { + version: config.version, + updatedAt: config.updatedAt, + stats, + }, + }; + } + + if (page === 'home') { + const { homeData } = await buildHomeData(db, event); + config.home = homeData; + config.version = (config.version || 0) + 1; + config.updatedAt = new Date().toISOString(); + + await saveConfig(config); + + return { + success: true, + data: { + version: config.version, + updatedAt: config.updatedAt, + featured: homeData.featured.length, + hot: homeData.hot.length, + sections: homeData.sections.length, + }, + }; + } } catch (error) { return { success: false, diff --git a/cloudfunctions/worksheetsQuery/index.js b/cloudfunctions/worksheetsQuery/index.js index 489e1a5..6b5684d 100644 --- a/cloudfunctions/worksheetsQuery/index.js +++ b/cloudfunctions/worksheetsQuery/index.js @@ -3,11 +3,11 @@ const cloud = require('wx-server-sdk'); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); const VALID_STATUS = new Set(['draft', 'active', 'hidden']); +const STATUS_ORDER = { draft: 0, hidden: 1, active: 2 }; exports.main = async (event) => { try { const db = cloud.database(); - const _ = db.command; const collection = db.collection('worksheets'); const category = String(event.category || '').trim(); @@ -26,6 +26,13 @@ exports.main = async (event) => { .limit(100) .get(); + // Sort by status order: draft → hidden → active + data.sort((a, b) => { + const orderA = STATUS_ORDER[a.status] ?? 99; + const orderB = STATUS_ORDER[b.status] ?? 99; + return orderA - orderB; + }); + // Compute stats for the category (all statuses) let allData = data; if (status) { diff --git a/docs/TodoList.md b/docs/TodoList.md new file mode 100644 index 0000000..fe7ca4b --- /dev/null +++ b/docs/TodoList.md @@ -0,0 +1 @@ +- [ ] 首页今日推荐、热门推荐创建定时任务,每日获取最新前 5 名并更新数据预拉取数据 diff --git a/docs/应用起名.md b/docs/应用起名.md new file mode 100644 index 0000000..b2782ab --- /dev/null +++ b/docs/应用起名.md @@ -0,0 +1,36 @@ +## 现有名称的问题 + +「数感识字幼儿专注学习卡」堆了四个关键词,读起来像 SEO 标题,而且只覆盖了 math 和 chinese +两个品类,英语、益智、手工都没体现。 + +## 「育儿打印资料」的问题 + +- 「资料」偏正式,和「涂鸦丫」的童趣调性不搭 +- 「育儿」太宽泛,涵盖了喂养、睡眠、情绪管理等,用户搜索「育儿」时预期和你的产品不一定匹配 +- 没有体现核心场景:给 3-8 岁孩子打印练习纸 + +## 几个方向供参考 + +┌─────────────────────────┬──────────────────────────────────────────────────┐ +│ 方案 │ 思路 │ +├─────────────────────────┼──────────────────────────────────────────────────┤ +│ 涂鸦丫-幼儿打印练习纸 │ 直接说清楚产品是什么,「练习纸」比「资料」具体 │ +├─────────────────────────┼──────────────────────────────────────────────────┤ +│ 涂鸦丫-儿童打印学习卡 │ 保留「学习卡」的说法,去掉堆砌的品类词 │ +├─────────────────────────┼──────────────────────────────────────────────────┤ +│ 涂鸦丫-幼儿启蒙打印乐园 │ 偏品牌感,「启蒙」点明年龄段,「乐园」呼应趣味性 │ +├─────────────────────────┼──────────────────────────────────────────────────┤ +│ 涂鸦丫-宝宝练习纸打印 │ 口语化,家长搜索习惯更接近「宝宝 + 练习纸」 │ +└─────────────────────────┴──────────────────────────────────────────────────┘ + +## 我的建议 + +优先考虑「涂鸦丫-幼儿打印练习纸」或「涂鸦丫-儿童打印学习卡」。理由: + +1. 「幼儿/儿童」比「育儿」更精准地指向你的用户群体(家长为孩子找内容) +2. 「打印」是你的核心差异点,必须保留 +3. 「练习纸/学习卡」比「资料」更具象,用户一看就知道能得到什么 +4. 不堆品类词,品广度让用户进来后自己发现 + +微信搜索里,用户更可能搜「幼儿练习纸」「打印学习卡」「数学练习纸打印」这类词,副标题里有「打印」+「练习纸/学习卡」的组合命中 +率会更高。 diff --git a/docs/页面内容管理方案.md b/docs/页面内容管理方案.md index e1ec1a0..7bd2f50 100644 --- a/docs/页面内容管理方案.md +++ b/docs/页面内容管理方案.md @@ -125,30 +125,44 @@ type PageContentUpdatePayload = { ### 3.1 管理 UI -首页管理 tab 分为四个区块: +首页管理页分为以下区块: | 区块 | 对应字段 | 管理方式 | | ------ | -------------- | ----------------------------------- | | 分类 tab | `categoryTabs` | 跟随 `CATEGORY_LIST_WITH_ALL`,一般不单独管理 | | 年龄入口 | `ageBands` | 跟随 `AGE_BANDS`,可管理描述文案 | -| 今日推荐 | `featured` | 手动选择 3-5 个 active worksheet | -| 热门推荐 | `hot` | 手动选择或按 downloads 自动生成 | +| 今日推荐 | `featured` | 自动取 downloads 最多的 5 个 active worksheet,支持手动微调 | +| 热门推荐 | `hot` | 自动取 likes 最多的 5 个 active worksheet,支持手动微调 | | 分类分区 | `sections` | 每个分类选择若干 active worksheet,支持排序 | -交互流程: +### 3.2 自动刷新与定时任务 + +今日推荐和热门推荐采用**自动化 + 手动微调**的方式: + +- **默认数据**:页面加载时自动查询 active worksheets,按 `downloads desc` 取前 5 填入 featured,按 `likes desc` 取前 5 填入 hot +- **定时任务**:云函数 `homeAutoRefresh` 配置定时触发器,每日 22:00 自动执行,查询最新 top5 数据更新 `page-config.json` 中的 `home.featured` 和 `home.hot` +- **开关控制**:通过 `settings` 集合中 `homeAutoRefresh` 文档的 `enabled` 字段控制。管理页提供暂停/启动按钮,调用 `homeTimerControl` 云函数切换状态。`homeAutoRefresh` 执行时先检查此标记,`enabled === false` 则跳过 +- **手动微调**:用户可在自动填充的基础上手动添加/移除 worksheet,点击「更新首页数据」手动生成 ``` -首页 tab 展示当前配置 - → 每个位置点击「选择内容」 - → 弹出 worksheet 选择器(筛选 active 内容) - → 保存配置 - → 点击「更新首页数据」 - → 调用 pageContentUpdate({ page: 'home', data: ... }) +管理页 onLoad + → 查询 active worksheets + → 按 downloads desc 取前 5 → featured + → 按 likes desc 取前 5 → hot + → 用户可手动微调 + → 点击「更新首页数据」→ pageContentBuild({ page: 'home', ... }) + +定时任务(每日 22:00) + → homeAutoRefresh 云函数 + → 检查 settings/homeAutoRefresh.enabled + → 查询 top5 downloads → featured + → 查询 top5 likes → hot + → 更新 page-config.json 中 home.featured 和 home.hot ``` -### 3.2 数据结构 +### 3.3 数据结构 ```ts type HomePageData = { @@ -168,11 +182,21 @@ type HomePageData = { | -------------- | ---------------------------------------------- | | `categoryTabs` | 由 `CATEGORY_LIST_WITH_ALL` 生成 | | `ageBands` | 由 `AGE_BANDS` 生成 | -| `featured` | 优先使用手动选择,不足时补 `isNew == true` | -| `hot` | 优先使用手动选择,不足时补 `isHot == true` 或 downloads 高的内容 | +| `featured` | 自动:downloads 最多的 5 个 active worksheet | +| `hot` | 自动:likes 最多的 5 个 active worksheet | | `sections` | 使用配置中的 worksheetIds,不足时从该分类 active 内容补齐 | +### 3.4 相关云函数 + + +| 云函数 | 职责 | +| ------------------- | ------------------------------------------- | +| `pageContentBuild` | 手动更新首页全量数据(featured + hot + sections) | +| `homeAutoRefresh` | 定时任务,每日 22:00 自动更新 featured 和 hot | +| `homeTimerControl` | 暂停/启动定时任务(更新 settings 集合标记位) | + + --- ## 四、分类页内容管理 @@ -445,7 +469,9 @@ supportPages/debug/debug | 文件 / 模块 | 职责 | | ----------------------------------- | ------------------------------ | | `supportPages/contentManage/` | 三 tab 内容管理页 | -| `cloudfunctions/pageContentUpdate/` | 更新 `page-config.json` 中指定页面的配置 | +| `cloudfunctions/pageContentBuild/` | 生成 `page-config.json` 中指定页面的配置 | +| `cloudfunctions/homeAutoRefresh/` | 定时任务,每日自动更新首页推荐位数据 | +| `cloudfunctions/homeTimerControl/` | 暂停/启动首页定时刷新任务 | | `pages/age/age.config.ts` | 分龄页兜底配置(年龄段、能力目标、默认 worksheet) | | `pages/home/home.data.ts` | 首页兜底数据 | | `pages/category/category.data.ts` | 分类页兜底数据 | diff --git a/miniprogram/app.json b/miniprogram/app.json index f98250b..9a0bb66 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -15,6 +15,7 @@ "debug/debug", "categoryManage/categoryManage", "categoryContentManage/categoryContentManage", + "homeContentManage/homeContentManage", "index/index", "mathIndex/mathIndex", "focusIndex/focusIndex", diff --git a/miniprogram/core/data/categories.ts b/miniprogram/core/data/categories.ts index f989d04..678261b 100644 --- a/miniprogram/core/data/categories.ts +++ b/miniprogram/core/data/categories.ts @@ -4,15 +4,46 @@ export const ALL_CATEGORY: CategoryType = { id: 'all', name: '全部', icon: '📋', + path: '/pages/category/category?id=all', }; export const CATEGORY_LIST: CategoryType[] = [ - { id: 'math', name: '数感启蒙', icon: '📐' }, - { id: 'puzzle', name: '益智游戏', icon: '🧩' }, - { id: 'pinyin', name: '汉语拼音', icon: '🔤' }, - { id: 'chinese', name: '趣味识字', icon: '✏️' }, - { id: 'english', name: '英语启蒙', icon: '🔤' }, - { id: 'craft', name: '创意手工', icon: '🌈' }, + { + id: 'math', + name: '数感启蒙', + icon: '📐', + path: '/pages/category/category?id=math', + }, + { + id: 'puzzle', + name: '益智游戏', + icon: '🧩', + path: '/pages/category/category?id=puzzle', + }, + { + id: 'pinyin', + name: '汉语拼音', + icon: '🔤', + path: '/pages/category/category?id=pinyin', + }, + { + id: 'chinese', + name: '趣味识字', + icon: '✏️', + path: '/pages/category/category?id=chinese', + }, + { + id: 'english', + name: '英语启蒙', + icon: '🔤', + path: '/pages/category/category?id=english', + }, + { + id: 'craft', + name: '创意手工', + icon: '🌈', + path: '/pages/category/category?id=craft', + }, ]; export const CATEGORY_LIST_WITH_ALL: CategoryType[] = [ diff --git a/miniprogram/core/models/category.ts b/miniprogram/core/models/category.ts index 2ac936c..864d473 100644 --- a/miniprogram/core/models/category.ts +++ b/miniprogram/core/models/category.ts @@ -10,6 +10,7 @@ export type CategoryId = export interface CategoryType { id: string; name: string; + path: string; icon: string; img?: string; color?: string; diff --git a/miniprogram/pages/category/category.ts b/miniprogram/pages/category/category.ts index 7fa9962..9269959 100644 --- a/miniprogram/pages/category/category.ts +++ b/miniprogram/pages/category/category.ts @@ -67,6 +67,17 @@ Page({ scrollIntoViewId: '', }, + onLoad(options: Record) { + const id = options.id; + if (id && id !== 'all') { + const items = getItemsByCategory(id); + this.setData({ + activeCategoryId: id, + displayItems: items, + }); + } + }, + onTapCategory(e: WechatMiniprogram.TouchEvent) { const id = e.currentTarget.dataset.id as string; if (!id || id === this.data.activeCategoryId) return; diff --git a/miniprogram/supportPages/categoryContentManage/categoryContentManage.less b/miniprogram/supportPages/categoryContentManage/categoryContentManage.less index 588fd3f..b41ff2f 100644 --- a/miniprogram/supportPages/categoryContentManage/categoryContentManage.less +++ b/miniprogram/supportPages/categoryContentManage/categoryContentManage.less @@ -62,7 +62,7 @@ page { box-shadow: @shadow; } -.ccm-stat + .ccm-stat { +.ccm-stat+.ccm-stat { margin-left: 16rpx; } @@ -96,6 +96,7 @@ page { // ── Worksheet List ── .ccm-page__list { margin-top: 24rpx; + margin-bottom: 80rpx; } .ws-card { @@ -125,11 +126,12 @@ page { } .ws-card__img { - width: 120rpx; - height: 120rpx; + width: 140rpx; + height: 180rpx; border-radius: @radius; flex-shrink: 0; background: @bg-gray; + border: @border; } .ws-card__img--placeholder { @@ -144,6 +146,9 @@ page { flex: 1; min-width: 0; margin-left: 20rpx; + display: flex; + flex-direction: column; + gap: 12rpx; } .ws-card__title { @@ -158,7 +163,6 @@ page { .ws-card__subtitle { display: block; - margin-top: 6rpx; font-size: 22rpx; color: @text-secondary; overflow: hidden; @@ -182,11 +186,20 @@ page { .ws-card__id { display: block; - margin-top: 8rpx; - font-size: 20rpx; + font-size: 22rpx; color: @text-gray; } +.ws-card__tags { + margin-top: 16rpx; +} + +.ws-card__tags-text { + font-size: 22rpx; + color: @text-secondary; + line-height: 1.5; +} + .ws-card__footer { display: flex; align-items: center; @@ -196,10 +209,13 @@ page { } .ws-card__badge { + display: inline-flex; + align-items: center; padding: 6rpx 16rpx; border-radius: 999rpx; font-size: 22rpx; font-weight: 700; + flex-shrink: 0; } .ws-card__badge--active { @@ -234,4 +250,4 @@ page { padding-bottom: calc(24rpx + env(safe-area-inset-bottom)); background: @bg-header; box-shadow: 0 -4rpx 16rpx rgba(50, 46, 37, 0.06); -} +} \ No newline at end of file diff --git a/miniprogram/supportPages/categoryContentManage/categoryContentManage.ts b/miniprogram/supportPages/categoryContentManage/categoryContentManage.ts index 8e2b419..3b60a3b 100644 --- a/miniprogram/supportPages/categoryContentManage/categoryContentManage.ts +++ b/miniprogram/supportPages/categoryContentManage/categoryContentManage.ts @@ -10,6 +10,7 @@ type WorksheetItem = { ageMax: number; difficulty: 1 | 2 | 3 | 4; tags: string[]; + tagsText: string; status: 'draft' | 'active' | 'hidden'; sortOrder: number; updatedAt: string; @@ -19,6 +20,7 @@ type WorksheetItem = { statusText: string; actionText: string; actionTarget: string; + actionType: string; }; type Stats = { @@ -44,11 +46,11 @@ const DIFFICULTY_LABELS: Record = { const STATUS_CONFIG: Record< string, - { text: string; action: string; target: string } + { text: string; action: string; target: string; actionType: string } > = { - draft: { text: '草稿', action: '激活', target: 'active' }, - active: { text: '线上', action: '下架', target: 'hidden' }, - hidden: { text: '已隐藏', action: '恢复', target: 'draft' }, + draft: { text: '草稿', action: '激活', target: 'active', actionType: 'green' }, + active: { text: '线上', action: '下架', target: 'hidden', actionType: 'default' }, + hidden: { text: '已隐藏', action: '恢复', target: 'draft', actionType: 'primary' }, }; function formatWorksheet(raw: Record): WorksheetItem { @@ -68,6 +70,7 @@ function formatWorksheet(raw: Record): WorksheetItem { ageMax, difficulty, tags: Array.isArray(raw.tags) ? raw.tags.map(String) : [], + tagsText: Array.isArray(raw.tags) ? raw.tags.map(String).join('、') : '', status: status as WorksheetItem['status'], sortOrder: Number(raw.sortOrder) || 0, updatedAt: raw.updatedAt ? String(raw.updatedAt) : '', @@ -76,6 +79,7 @@ function formatWorksheet(raw: Record): WorksheetItem { statusText: cfg.text, actionText: cfg.action, actionTarget: cfg.target, + actionType: cfg.actionType, }; } diff --git a/miniprogram/supportPages/categoryContentManage/categoryContentManage.wxml b/miniprogram/supportPages/categoryContentManage/categoryContentManage.wxml index 57c3d47..db6a8ee 100644 --- a/miniprogram/supportPages/categoryContentManage/categoryContentManage.wxml +++ b/miniprogram/supportPages/categoryContentManage/categoryContentManage.wxml @@ -59,21 +59,26 @@ {{item.title}} + {{item._id}} {{item.subtitle}} {{item.ageBand}} - {{item.difficultyLabel}} + {{item.difficultyLabel}} - {{item._id}} + + {{item.tagsText}} + - - {{item.statusText}} - + + {{item.statusText}} + 排序 {{item.sortOrder}} {{building ? '生成中...' : '更新分类页数据'}} - \ No newline at end of file + diff --git a/miniprogram/supportPages/debug/debug.ts b/miniprogram/supportPages/debug/debug.ts index 258bfe3..0ee214a 100644 --- a/miniprogram/supportPages/debug/debug.ts +++ b/miniprogram/supportPages/debug/debug.ts @@ -21,6 +21,13 @@ const DEBUG_ENTRIES: DebugEntry[] = [ icon: '📋', path: '/supportPages/categoryContentManage/categoryContentManage', }, + { + id: 'home-content', + title: '首页内容管理', + subtitle: '编排首页推荐位、热门推荐和分类分区内容。', + icon: '🏠', + path: '/supportPages/homeContentManage/homeContentManage', + }, ]; Page({ diff --git a/miniprogram/supportPages/homeContentManage/homeContentManage.json b/miniprogram/supportPages/homeContentManage/homeContentManage.json new file mode 100644 index 0000000..451c36c --- /dev/null +++ b/miniprogram/supportPages/homeContentManage/homeContentManage.json @@ -0,0 +1,10 @@ +{ + "navigationBarTitleText": "首页内容管理", + "navigationBarTextStyle": "black", + "navigationBarBackgroundColor": "#F8F0E0", + "backgroundColor": "#F8F0E0", + "enablePullDownRefresh": true, + "usingComponents": { + "toy-button": "/toy/button-v2/button" + } +} diff --git a/miniprogram/supportPages/homeContentManage/homeContentManage.less b/miniprogram/supportPages/homeContentManage/homeContentManage.less new file mode 100644 index 0000000..3da8859 --- /dev/null +++ b/miniprogram/supportPages/homeContentManage/homeContentManage.less @@ -0,0 +1,277 @@ +@import '../../style/theme.less'; + +page { + min-height: 100%; + background: @bg-header; +} + +.hcm-page { + min-height: 100vh; + padding: 24rpx; + padding-bottom: 160rpx; + background: @bg-header; + box-sizing: border-box; +} + +// ── Loading ── +.hcm-page__loading { + padding: 24rpx 28rpx; + background: @bg-white; + border-radius: @radius; + font-size: 24rpx; + color: @text-secondary; + box-shadow: @shadow; +} + +// ── Section ── +.hcm-timer-card { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 24rpx; + padding: 28rpx; + background: @bg-white; + border-radius: @radius-xl; + box-shadow: @shadow; +} + +.hcm-timer-card__info { + flex: 1; + min-width: 0; +} + +.hcm-timer-card__title { + display: block; + font-size: 28rpx; + font-weight: 700; + color: @text-title; +} + +.hcm-timer-card__desc { + display: block; + margin-top: 8rpx; + font-size: 22rpx; + color: @text-secondary; +} + +.hcm-timer-card__right { + display: flex; + flex-direction: column; + align-items: center; + gap: 12rpx; + flex-shrink: 0; + margin-left: 20rpx; +} + +.hcm-timer-card__badge { + padding: 6rpx 16rpx; + border-radius: 999rpx; + font-size: 22rpx; + font-weight: 700; +} + +.hcm-timer-card__badge--on { + background: fade(#93d333, 18%); + color: #4a7a12; +} + +.hcm-timer-card__badge--off { + background: fade(#8a8478, 15%); + color: #605b50; +} + +.hcm-section { + margin-bottom: 24rpx; + padding: 28rpx; + background: @bg-white; + border-radius: @radius-xl; + box-shadow: @shadow; + + &.last { + margin-bottom: 60rpx; + } +} + +.hcm-section__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20rpx; +} + +.hcm-section__title { + font-size: 30rpx; + font-weight: 700; + color: @text-title; +} + +.hcm-section__count { + font-size: 22rpx; + color: @text-gray; +} + +.hcm-section__hint { + margin-left: 12rpx; + font-size: 22rpx; + color: @text-gray; +} + +.hcm-section__empty { + padding: 20rpx 0; + font-size: 24rpx; + color: @text-gray; + margin-bottom: 16rpx; +} + +// ── Item List ── +.hcm-item-list { + margin-bottom: 16rpx; +} + +.hcm-item { + display: flex; + align-items: center; + padding: 16rpx 0; + border-bottom: @border; +} + +.hcm-item:last-child { + border-bottom: none; +} + +.hcm-item__img { + width: 80rpx; + height: 80rpx; + border-radius: @radius-sm; + flex-shrink: 0; + background: @bg-gray; +} + +.hcm-item__info { + flex: 1; + min-width: 0; + margin-left: 16rpx; + display: flex; + flex-direction: column; + gap: 6rpx; +} + +.hcm-item__title { + font-size: 26rpx; + font-weight: 600; + color: @text-title; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.hcm-item__id { + font-size: 20rpx; + color: @text-gray; +} + +// ── Build Button ── +.hcm-page__build { + position: fixed; + left: 0; + right: 0; + bottom: 0; + padding: 24rpx 40rpx; + padding-bottom: calc(24rpx + env(safe-area-inset-bottom)); + background: @bg-header; + box-shadow: 0 -4rpx 16rpx rgba(50, 46, 37, 0.06); +} + +// ── Selector Popup ── +.hcm-selector-mask { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.45); + z-index: 100; + display: flex; + align-items: flex-end; +} + +.hcm-selector { + width: 100%; + max-height: 70vh; + background: @bg-white; + border-radius: @radius-xl @radius-xl 0 0; + display: flex; + flex-direction: column; +} + +.hcm-selector__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 28rpx 32rpx; + border-bottom: @border; +} + +.hcm-selector__title { + font-size: 30rpx; + font-weight: 700; + color: @text-title; +} + +.hcm-selector__close { + font-size: 26rpx; + color: @text-secondary; + padding: 8rpx 16rpx; +} + +.hcm-selector__list { + flex: 1; + max-height: 60vh; + padding: 0 32rpx; +} + +.hcm-selector__empty { + padding: 40rpx 0; + text-align: center; + font-size: 24rpx; + color: @text-gray; +} + +.hcm-selector-item { + display: flex; + align-items: center; + padding: 20rpx 0; + border-bottom: @border; +} + +.hcm-selector-item:last-child { + border-bottom: none; +} + +.hcm-selector-item__img { + width: 80rpx; + height: 80rpx; + border-radius: @radius-sm; + flex-shrink: 0; + background: @bg-gray; +} + +.hcm-selector-item__info { + flex: 1; + min-width: 0; + margin-left: 16rpx; + display: flex; + flex-direction: column; + gap: 6rpx; +} + +.hcm-selector-item__title { + font-size: 26rpx; + font-weight: 600; + color: @text-title; +} + +.hcm-selector-item__sub { + font-size: 22rpx; + color: @text-secondary; +} \ No newline at end of file diff --git a/miniprogram/supportPages/homeContentManage/homeContentManage.ts b/miniprogram/supportPages/homeContentManage/homeContentManage.ts new file mode 100644 index 0000000..f68bef8 --- /dev/null +++ b/miniprogram/supportPages/homeContentManage/homeContentManage.ts @@ -0,0 +1,371 @@ +import { CATEGORY_LIST } from '../../core/data/categories'; + +type SimpleWorksheet = { + _id: string; + title: string; + subtitle: string; + category: string; + previewImg: string; + ageMin: number; + ageMax: number; + difficulty: number; + isNew: boolean; + isHot: boolean; + path: string; + ageBand: string; + downloads: number; + likes: number; +}; + +type SectionConfig = { + id: string; + name: string; + icon: string; + subtitle?: string; + morePath?: string; + items: SimpleWorksheet[]; +}; + +type HomeConfigItem = { + id: string; + title?: string; + subtitle?: string; + morePath?: string; + items: Array<{ id: string; path?: string; [key: string]: unknown }>; +}; + +type PageConfig = { + home?: { + featured?: Array<{ id: string; [key: string]: unknown }>; + hot?: Array<{ id: string; [key: string]: unknown }>; + sections?: HomeConfigItem[]; + }; + [key: string]: unknown; +}; + +type CloudFunctionResult = { + success?: boolean; + message?: string; + data?: T; +}; + +function formatSimple(raw: Record): SimpleWorksheet { + const ageMin = Number(raw.ageMin) || 0; + const ageMax = Number(raw.ageMax) || 0; + return { + _id: String(raw._id || ''), + title: String(raw.title || ''), + subtitle: String(raw.subtitle || ''), + category: String(raw.category || ''), + previewImg: String(raw.previewImg || ''), + ageMin, + ageMax, + difficulty: Number(raw.difficulty) || 2, + isNew: !!raw.isNew, + isHot: !!raw.isHot, + path: String(raw.path || ''), + ageBand: `${ageMin}-${ageMax}岁`, + downloads: Number(raw.downloads) || 0, + likes: Number(raw.likes) || 0, + }; +} + +async function callCloudFunction( + name: string, + data?: Record, +): Promise> { + const response = (await wx.cloud.callFunction({ + name, + data: data || {}, + })) as { result?: CloudFunctionResult }; + return response.result || {}; +} + +Page({ + data: { + featured: [] as SimpleWorksheet[], + hot: [] as SimpleWorksheet[], + sections: [] as SectionConfig[], + // worksheet pool (all active) + allActive: [] as SimpleWorksheet[], + // selector popup + selectorVisible: false, + selectorTarget: '', // 'featured' | 'hot' | category id + selectorTargetLabel: '', + selectorItems: [] as SimpleWorksheet[], + // timer + timerEnabled: true, + timerLoading: false, + // states + loading: false, + building: false, + }, + + onLoad() { + this.setData({ + sections: CATEGORY_LIST.map((c) => ({ + id: c.id, + name: c.name, + icon: c.icon, + items: [], + })), + }); + void this.loadData(); + void this.loadTimerStatus(); + }, + + async onPullDownRefresh() { + await this.loadData(); + await this.loadTimerStatus(); + wx.stopPullDownRefresh(); + }, + + async loadTimerStatus() { + try { + const db = wx.cloud.database(); + const { data } = await db + .collection('settings') + .doc('homeAutoRefresh') + .get(); + this.setData({ timerEnabled: (data as { enabled?: boolean }).enabled !== false }); + } catch { + // Document doesn't exist — default enabled + this.setData({ timerEnabled: true }); + } + }, + + async loadData() { + this.setData({ loading: true }); + try { + // Fetch in parallel: active worksheets + saved page config + const [wsResult, configResult] = await Promise.all([ + callCloudFunction('worksheetsQuery', { + status: 'active', + }), + callCloudFunction('pageConfigFetch'), + ]); + + if (!wsResult.success) throw new Error(wsResult.message || '查询失败'); + + const allActive = (wsResult.data || []).map((raw) => + formatSimple(raw as unknown as Record), + ); + + // Build a lookup map for quick access + const wsMap = new Map(allActive.map((w) => [w._id, w])); + + // featured: always top-5 by downloads; hot: always top-5 by likes + const featured: SimpleWorksheet[] = [...allActive] + .sort((a, b) => (b.downloads ?? 0) - (a.downloads ?? 0)) + .slice(0, 5); + const hot: SimpleWorksheet[] = [...allActive] + .sort((a, b) => (b.likes ?? 0) - (a.likes ?? 0)) + .slice(0, 5); + + // Restore saved sections from config + const savedHome = (configResult.data as PageConfig | undefined)?.home; + const savedSections: HomeConfigItem[] = savedHome?.sections || []; + const savedSectionMap = new Map(savedSections.map((s) => [s.id, s])); + + const sections: SectionConfig[] = CATEGORY_LIST.map((c) => { + const saved = savedSectionMap.get(c.id); + const items = saved + ? (saved.items || []) + .map((item) => wsMap.get(item.id)) + .filter((w): w is SimpleWorksheet => !!w) + : []; + return { + id: c.id, + name: c.name, + icon: c.icon, + subtitle: saved?.subtitle || '', + morePath: c.path, + items, + }; + }); + + this.setData({ loading: false, allActive, featured, hot, sections }); + } catch (error) { + this.setData({ loading: false }); + wx.showToast({ + title: error instanceof Error ? error.message : '加载失败', + icon: 'none', + }); + } + }, + + // ── Timer Control ── + + async onToggleTimer() { + if (this.data.timerLoading) return; + const newEnabled = !this.data.timerEnabled; + + this.setData({ timerLoading: true }); + try { + const result = await callCloudFunction('homeTimerControl', { + enabled: newEnabled, + }); + if (!result.success) { + throw new Error(result.message || '操作失败'); + } + this.setData({ timerEnabled: newEnabled }); + wx.showToast({ + title: newEnabled ? '定时任务已启动' : '定时任务已暂停', + icon: 'success', + }); + } catch (error) { + wx.showToast({ + title: error instanceof Error ? error.message : '操作失败', + icon: 'none', + }); + } finally { + this.setData({ timerLoading: false }); + } + }, + + // ── Selector ── + + onTapAdd(e: WechatMiniprogram.TouchEvent) { + const target = e.currentTarget.dataset.target as string; + if (!target) return; + + let label = ''; + let selectedIds: Set; + + if (target === 'featured') { + label = '今日推荐'; + selectedIds = new Set(this.data.featured.map((w) => w._id)); + } else if (target === 'hot') { + label = '热门推荐'; + selectedIds = new Set(this.data.hot.map((w) => w._id)); + } else { + const section = this.data.sections.find((s) => s.id === target); + label = section?.name || target; + selectedIds = new Set((section?.items || []).map((w) => w._id)); + } + + // Filter: show active worksheets not already selected + let pool = this.data.allActive.filter((w) => !selectedIds.has(w._id)); + // For sections, prefer same category + if (target !== 'featured' && target !== 'hot') { + pool = pool.filter((w) => w.category === target); + } + + this.setData({ + selectorVisible: true, + selectorTarget: target, + selectorTargetLabel: label, + selectorItems: pool, + }); + }, + + onCloseSelector() { + this.setData({ selectorVisible: false }); + }, + + onSelectItem(e: WechatMiniprogram.TouchEvent) { + const id = e.currentTarget.dataset.id as string; + if (!id) return; + + const ws = this.data.allActive.find((w) => w._id === id); + if (!ws) return; + + const target = this.data.selectorTarget; + + if (target === 'featured') { + this.setData({ featured: [...this.data.featured, ws] }); + } else if (target === 'hot') { + this.setData({ hot: [...this.data.hot, ws] }); + } else { + const sections = [...this.data.sections]; + const idx = sections.findIndex((s) => s.id === target); + if (idx >= 0) { + sections[idx] = { + ...sections[idx], + items: [...sections[idx].items, ws], + }; + this.setData({ sections }); + } + } + + this.setData({ selectorVisible: false }); + }, + + onRemoveItem(e: WechatMiniprogram.TouchEvent) { + const target = e.currentTarget.dataset.target as string; + const id = e.currentTarget.dataset.id as string; + if (!target || !id) return; + + if (target === 'featured') { + this.setData({ + featured: this.data.featured.filter((w) => w._id !== id), + }); + } else if (target === 'hot') { + this.setData({ + hot: this.data.hot.filter((w) => w._id !== id), + }); + } else { + const sections = [...this.data.sections]; + const idx = sections.findIndex((s) => s.id === target); + if (idx >= 0) { + sections[idx] = { + ...sections[idx], + items: sections[idx].items.filter((w) => w._id !== id), + }; + this.setData({ sections }); + } + } + }, + + // ── Build ── + + async onBuildTap() { + if (this.data.building) return; + + const confirmed = await new Promise((resolve) => { + wx.showModal({ + title: '更新首页数据', + content: '将根据当前配置重新生成首页数据,确认继续?', + success: (res) => resolve(!!res.confirm), + fail: () => resolve(false), + }); + }); + if (!confirmed) return; + + this.setData({ building: true }); + + try { + const result = await callCloudFunction<{ + version: number; + }>('pageContentBuild', { + page: 'home', + featuredIds: this.data.featured.map((w) => w._id), + hotIds: this.data.hot.map((w) => w._id), + sections: this.data.sections + .filter((s) => s.items.length > 0) + .map((s) => ({ + id: s.id, + subtitle: s.subtitle || '', + morePath: s.morePath || '', + worksheetIds: s.items.map((w) => w._id), + })), + }); + + if (!result.success) { + throw new Error(result.message || '生成失败'); + } + + wx.showToast({ + title: `生成成功 v${result.data?.version || 0}`, + icon: 'success', + }); + } catch (error) { + wx.showToast({ + title: error instanceof Error ? error.message : '生成失败', + icon: 'none', + }); + } finally { + this.setData({ building: false }); + } + }, +}); diff --git a/miniprogram/supportPages/homeContentManage/homeContentManage.wxml b/miniprogram/supportPages/homeContentManage/homeContentManage.wxml new file mode 100644 index 0000000..1ce1de0 --- /dev/null +++ b/miniprogram/supportPages/homeContentManage/homeContentManage.wxml @@ -0,0 +1,217 @@ + + + + 正在加载 active worksheet 数据... + + + + + + + 定时自动刷新 + 每日 22:00 自动更新推荐位数据 + + + + {{timerEnabled ? '运行中' : '已暂停'}} + + + {{timerEnabled ? '暂停' : '启动'}} + + + + + + + + + 今日推荐 + 按下载量排序 + + {{featured.length}} 个 + + + + + + {{item.title}} + {{item._id}} + + + 移除 + + + + 暂未选择内容 + + 添加 + + + + + + + + + + 热门推荐 + 按点赞量排序 + + {{hot.length}} 个 + + + + + + {{item.title}} + {{item._id}} + + + 移除 + + + + 暂未选择内容 + + 添加 + + + + + + + {{item.icon}} {{item.name}} + {{item.items.length}} 个 + + + + + + {{ws.title}} + {{ws._id}} + + + 移除 + + + + 暂未选择内容 + + 添加 + + + + + + + + + {{building ? '生成中...' : '更新首页数据'}} + + + + + + + + + 选择内容 - {{selectorTargetLabel}} + 关闭 + + + + 暂无可选内容 + + + + + {{item.title}} + {{item.ageBand}} · {{item._id}} + + + + + + diff --git a/project.private.config.json b/project.private.config.json index 4e66abd..f182445 100644 --- a/project.private.config.json +++ b/project.private.config.json @@ -24,12 +24,19 @@ "miniprogram": { "list": [ { - "name": "englishPages/letterTracing/letterTracing", - "pathName": "englishPages/letterTracing/letterTracing", + "name": "supportPages/debug/debug", + "pathName": "supportPages/debug/debug", "query": "", "scene": null, "launchMode": "default" }, + { + "name": "englishPages/letterTracing/letterTracing", + "pathName": "englishPages/letterTracing/letterTracing", + "query": "", + "launchMode": "default", + "scene": null + }, { "name": "supportPages/debug/debug", "pathName": "supportPages/debug/debug",