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 toDisplayItem(ws) { return { id: ws._id, title: ws.title || '', subtitle: ws.subtitle || '', previewImg: ws.previewImg || '', ageBand: ageBand(ws.ageMin || 0, ws.ageMax || 0), ageMin: ws.ageMin || 0, ageMax: ws.ageMax || 0, difficulty: ws.difficulty || 2, difficultyLabel: DIFFICULTY_LABELS[ws.difficulty] || '基础', path: ws.path || '', available: true, likes: (ws.likes || 0) + (ws.likes_seed || 0), downloads: (ws.downloads || 0) + (ws.downloads_seed || 0), date: ws.updatedAt ? new Date(ws.updatedAt).toISOString().slice(0, 10) : new Date().toISOString().slice(0, 10), }; } 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: '', previewImg: 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 { 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 .collection('categories') .orderBy('sortOrder', 'asc') .get(); // Get all active worksheets const { data: worksheets } = await db .collection('worksheets') .where({ status: 'active' }) .orderBy('sortOrder', 'asc') .orderBy('updatedAt', 'desc') .limit(100) .get(); // Group by category const grouped = {}; for (const ws of worksheets) { const cat = ws.category || 'unknown'; if (!grouped[cat]) grouped[cat] = []; grouped[cat].push(toDisplayItem(ws)); } const categoryData = { searchPlaceholder: '搜索练习纸...', categories: categories.map((cat) => ({ id: cat._id, name: cat.name || '', icon: cat.icon || '', items: grouped[cat._id] || [], })), }; const stats = {}; for (const cat of categoryData.categories) { stats[cat.id] = cat.items.length; } 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 }; } function normalizeIdArray(value) { if (!Array.isArray(value)) return []; return value.map((x) => String(x || '').trim()).filter(Boolean); } function extractHomeSeedFromCurrentConfig(home) { const featuredIds = normalizeIdArray(home?.featured?.map((x) => x?.id)); const hotIds = normalizeIdArray(home?.hot?.map((x) => x?.id)); const sections = Array.isArray(home?.sections) ? home.sections .map((s) => ({ id: String(s?.id || '').trim(), subtitle: String(s?.subtitle || '').trim(), morePath: String(s?.morePath || '').trim(), worksheetIds: normalizeIdArray(s?.items?.map((it) => it?.id)), })) .filter((s) => s.id && Array.isArray(s.worksheetIds)) : []; return { featuredIds, hotIds, sections }; } exports.main = async (event) => { try { const db = cloud.database(); const page = String(event.page || '').trim(); if (page !== 'category' && page !== 'home' && page !== 'all') { throw new Error('page 参数不合法,支持 category / home / all'); } const config = await readCurrentConfig(); // 当未提供 featuredIds/hotIds/sections 时,默认从现有 home 配置反推出 worksheetIds // 用于“只想重建展示数据(比如 ageBand/previewImg/difficultyLabel),但不想手工维护 id 列表”的场景。 const homeSeed = extractHomeSeedFromCurrentConfig(config.home || {}); const homeEvent = { ...event, featuredIds: Array.isArray(event.featuredIds) && event.featuredIds.length > 0 ? event.featuredIds : homeSeed.featuredIds, hotIds: Array.isArray(event.hotIds) && event.hotIds.length > 0 ? event.hotIds : homeSeed.hotIds, sections: Array.isArray(event.sections) && event.sections.length > 0 ? event.sections : homeSeed.sections, }; if (page === 'category') { const { categoryData, stats } = await buildCategoryData(db); config.category = categoryData; config.version = (config.version || 0) + 1; config.updatedAt = new Date().toISOString(); await saveConfig(config); return { success: true, data: { version: config.version, updatedAt: config.updatedAt, stats, }, }; } if (page === 'home') { const { homeData } = await buildHomeData(db, homeEvent); config.home = homeData; // [v3.0] 清空 category/age 字段,分类页已改为实时接口查询 // 如需恢复静态配置,注释掉以下两行即可 config.category = null; config.age = null; 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, }, }; } if (page === 'all') { const { categoryData, stats } = await buildCategoryData(db); const { homeData } = await buildHomeData(db, homeEvent); // [v3.0] 清空 category/age 字段,分类页已改为实时接口查询 // 如需恢复静态配置,将下面 null 改回 categoryData 即可 config.category = null; config.age = null; 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, stats, featured: homeData.featured.length, hot: homeData.hot.length, sections: homeData.sections.length, }, }; } } catch (error) { return { success: false, message: error instanceof Error ? error.message : '生成分类页数据失败', }; } };