feat:完成首页、分类页内容管理功能开发
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"triggers": [
|
||||
{
|
||||
"name": "dailyRefresh",
|
||||
"type": "timer",
|
||||
"config": "0 0 22 * * * *"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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 : '自动刷新首页推荐失败',
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "home-auto-refresh",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "^3.0.4"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
: '更新定时任务状态失败',
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "home-timer-control",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "^3.0.4"
|
||||
}
|
||||
}
|
||||
@@ -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 : '获取页面配置失败',
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "page-config-fetch",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "^3.0.4"
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user