344 lines
11 KiB
JavaScript
344 lines
11 KiB
JavaScript
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,
|
|
downloads: ws.downloads || 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 : '生成分类页数据失败',
|
|
};
|
|
}
|
|
};
|