import { appSharePageMethods } from '../../base/pageMixin'; import { CATEGORY_LIST_WITH_ALL } from '../../core/data/categories'; import { takePendingCategoryTabId } from '../../utils/index'; import { CATEGORY_DATA, type CategoryItem } from './category.data'; type CategoryTab = { id: string; name: string; icon: string; }; type CategoryDataset = { searchPlaceholder: string; categories: Array<{ id: string; name: string; icon: string; items: CategoryItem[]; }>; }; const DIFFICULTY_LABELS: Record = { 1: '入门', 2: '基础', 3: '进阶', 4: '挑战', }; const DYNAMIC_TABS: CategoryTab[] = [ { id: 'hot', name: '热门推荐', icon: '🔥' }, { id: 'top-downloads', name: '下载最多', icon: '📥' }, ]; const SIDEBAR_TABS: CategoryTab[] = [ { id: 'all', name: '全部', icon: '📋' }, ...DYNAMIC_TABS, ...CATEGORY_LIST_WITH_ALL.filter((c) => c.id !== 'all').map( ({ id, name, icon }) => ({ id, name, icon }), ), ]; const TAB_BAR_PATHS = new Set([ '/pages/home/home', '/pages/category/category', '/pages/age/age', '/pages/favorites/favorites', '/pages/profile/profile', ]); // Page-level mutable state (shared across methods) let _categoryData: CategoryDataset | null = null; // --- 以下仅为本地调试:拉取分类数据后查看各 worksheet 的 downloads / likes,非业务逻辑 --- function debugLogWorksheetStatsAfterCategoryLoad( source: | { kind: 'cloud'; rows: Record[] } | { kind: 'static'; data: CategoryDataset }, ) { let map: Record = {}; if (source.kind === 'cloud') { for (const raw of source.rows) { const id = String(raw._id || '').trim(); if (!id) continue; map[id] = { downloads: Number(raw.downloads) || 0, likes: Number(raw.likes) || 0, }; } } else { for (const group of source.data.categories) { for (const item of group.items) { map[item.id] = { downloads: Number(item.downloads) || 0, likes: Number(item.likes) || 0, }; } } } console.log('worksheetStatsById', JSON.stringify(map, null, 2)); } // --- 调试块结束 --- /** 将云端 worksheet 原始数据转换为 CategoryItem */ function toDisplayItem(raw: Record): CategoryItem { const difficulty = (Number(raw.difficulty) || 2) as 1 | 2 | 3 | 4; 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 || ''), previewImg: String(raw.previewImg || ''), ageBand: `${ageMin}-${ageMax}岁`, ageMin, ageMax, difficulty, difficultyLabel: DIFFICULTY_LABELS[difficulty] || '基础', path: String(raw.path || ''), available: true, likes: Number(raw.likes) || 0, downloads: Number(raw.downloads) || 0, date: raw.updatedAt ? new Date(raw.updatedAt).toISOString().slice(0, 10) : new Date().toISOString().slice(0, 10), updatedAt: raw.updatedAt ? String(raw.updatedAt) : '', }; } /** 排序:updatedAt 最新的 2 个置顶,其余按 downloads 降序 */ function sortWorksheets(items: CategoryItem[]): CategoryItem[] { const byTime = [...items].sort( (a, b) => new Date(b.updatedAt || '').getTime() - new Date(a.updatedAt || '').getTime(), ); const recent = byTime.slice(0, 2); const recentIds = new Set(recent.map((w) => w.id)); const rest = byTime .filter((w) => !recentIds.has(w.id)) .sort((a, b) => (b.downloads || 0) - (a.downloads || 0)); return [...recent, ...rest]; } /** 将云端 worksheet 数组按分类分组,构建 CategoryDataset */ function buildCategoryDataFromCloud( rawList: Record[], ): CategoryDataset { const itemsByCategory: Record = {}; for (const raw of rawList) { const item = toDisplayItem(raw); const cat = String(raw.category || ''); if (!itemsByCategory[cat]) itemsByCategory[cat] = []; itemsByCategory[cat].push(item); } const categories = CATEGORY_LIST_WITH_ALL.filter((c) => c.id !== 'all').map( (cat) => ({ id: cat.id, name: cat.name, icon: cat.icon, items: sortWorksheets(itemsByCategory[cat.id] || []), }), ); return { searchPlaceholder: '搜索练习纸...', categories }; } function buildAllItems(): CategoryItem[] { if (!_categoryData) return []; const items: CategoryItem[] = []; for (const group of _categoryData.categories) { for (const item of group.items) { items.push({ ...item }); } } return sortWorksheets(items); } const DYNAMIC_LIMIT = 12; function buildHotItems(): CategoryItem[] { const all = buildAllItems(); return [...all] .sort((a, b) => (b.likes || 0) - (a.likes || 0)) .slice(0, DYNAMIC_LIMIT); } function buildTopDownloadsItems(): CategoryItem[] { const all = buildAllItems(); return [...all] .sort((a, b) => (b.downloads || 0) - (a.downloads || 0)) .slice(0, DYNAMIC_LIMIT); } function getItemsByCategory(categoryId: string): CategoryItem[] { if (!_categoryData) return []; if (categoryId === 'all') return buildAllItems(); if (categoryId === 'hot') return buildHotItems(); if (categoryId === 'top-downloads') return buildTopDownloadsItems(); const group = _categoryData.categories.find((c) => c.id === categoryId); if (!group) return []; return group.items.map((item) => ({ ...item })); } function filterByKeyword( items: CategoryItem[], keyword: string, ): CategoryItem[] { if (!keyword) return items; const kw = keyword.toLowerCase(); return items.filter( (item) => item.title.toLowerCase().includes(kw) || item.subtitle.toLowerCase().includes(kw), ); } // Skeleton placeholder items for loading state const SKELETON_ITEMS = Array.from({ length: 6 }, (_, i) => ({ id: `skeleton-${i}`, title: '\u00A0', subtitle: '\u00A0', skeleton: true, })); Page({ data: { searchPlaceholder: '搜索练习纸...', searchKeyword: '', sidebarTabs: SIDEBAR_TABS, activeCategoryId: 'all', displayItems: SKELETON_ITEMS as any[], scrollIntoViewId: '', loading: true, }, _defaultCategoryId: 'all', onLoad(options: Record) { if (options.id && options.id !== 'all') { this._defaultCategoryId = options.id; this.setData({ activeCategoryId: options.id }); } }, onShow() { _categoryData = null; this.setData({ displayItems: SKELETON_ITEMS, loading: true }); void this.loadWorksheets(takePendingCategoryTabId() ?? undefined); }, async loadWorksheets(overrideCategoryId?: string | null) { try { const res = await wx.cloud.callFunction({ name: 'worksheetsQuery', data: { status: 'active' }, }); const result = (res?.result as any) || {}; if (!result.success) throw new Error(result.message || '查询失败'); const rows = result.data || []; _categoryData = buildCategoryDataFromCloud(rows); debugLogWorksheetStatsAfterCategoryLoad({ kind: 'cloud', rows }); } catch { _categoryData = CATEGORY_DATA; debugLogWorksheetStatsAfterCategoryLoad({ kind: 'static', data: CATEGORY_DATA, }); } const activeId = overrideCategoryId != null && String(overrideCategoryId).trim() !== '' ? String(overrideCategoryId).trim() : this.data.activeCategoryId || this._defaultCategoryId; const items = getItemsByCategory(activeId); const filtered = filterByKeyword(items, this.data.searchKeyword); this.setData({ loading: false, searchPlaceholder: _categoryData.searchPlaceholder, activeCategoryId: activeId, displayItems: filtered, }); }, onTapCategory(e: WechatMiniprogram.TouchEvent) { const id = e.currentTarget.dataset.id as string; if (!id || id === this.data.activeCategoryId) return; const items = getItemsByCategory(id); const filtered = filterByKeyword(items, this.data.searchKeyword); this.setData({ activeCategoryId: id, displayItems: filtered, scrollIntoViewId: '', }); }, onSearchInput(e: WechatMiniprogram.Input) { const keyword = (e.detail.value ?? '').trim(); const items = getItemsByCategory(this.data.activeCategoryId); const filtered = filterByKeyword(items, keyword); this.setData({ searchKeyword: keyword, displayItems: filtered, }); }, onClearSearch() { const items = getItemsByCategory(this.data.activeCategoryId); this.setData({ searchKeyword: '', displayItems: items, }); }, onTapItem(e: WechatMiniprogram.TouchEvent) { const path = e.currentTarget.dataset.path as string; const title = e.currentTarget.dataset.title as string; const available = e.currentTarget.dataset.available; if (!available || !path) { wx.showToast({ title: title ? `${title} 即将上线` : '即将上线', icon: 'none', }); return; } if (TAB_BAR_PATHS.has(path)) { wx.switchTab({ url: path }); return; } wx.navigateTo({ url: path }); }, ...appSharePageMethods, });