import { resolvePreviewUrl } from '../../config/assets'; import type { ApiCategory, ApiWorksheet } from '../../services/content-api'; export interface CategoryTab { id: string; name: string; icon: string; } export interface CategoryItem { id: string; title: string; subtitle: string; icon: string; previewImg: string; ageBand: string; difficultyLabel: string; path: string; available: boolean; likes: string; downloads: string; date: string; updatedAt: string; skeleton?: boolean; } export interface CategoryDataset { sidebarTabs: CategoryTab[]; groups: Map; } export const BASE_TABS: CategoryTab[] = [ { id: 'all', name: '全部', icon: '📋' }, { id: 'hot', name: '热门推荐', icon: '🔥' }, { id: 'top-downloads', name: '下载最多', icon: '📥' }, ]; const DIFFICULTY_LABELS = ['入门', '基础', '进阶', '挑战']; /** 比较任意长度的非负十进制计数;微信运行时无需 BigInt,也不丢失 64 位精度。 */ export function compareCountDescending(left: string, right: string): number { const a = left.replace(/^0+(?=\d)/, ''); const b = right.replace(/^0+(?=\d)/, ''); return a.length !== b.length ? b.length - a.length : a === b ? 0 : a < b ? 1 : -1; } function time(value: string): number { const parsed = Date.parse(value); return Number.isFinite(parsed) ? parsed : 0; } /** 保留原页面交互:最近更新的两项置顶,其余下载量降序,并列按稳定 ID。 */ function sortWorksheets(items: CategoryItem[]): CategoryItem[] { const byId = (a: CategoryItem, b: CategoryItem) => a.id < b.id ? -1 : a.id === b.id ? 0 : 1; const sorted = [...items].sort( (a, b) => time(b.updatedAt) - time(a.updatedAt) || byId(a, b), ); return [ ...sorted.slice(0, 2), ...sorted .slice(2) .sort( (a, b) => compareCountDescending(a.downloads, b.downloads) || byId(a, b), ), ]; } /** 只展示本次接口返回的分类;并发下架导致目录缺失的题型直接丢弃。 */ export function buildCategoryDataset( categories: ApiCategory[], worksheets: ApiWorksheet[], ): CategoryDataset { const reservedIds = new Set(BASE_TABS.map((tab) => tab.id)); const groups = new Map(); const icons = new Map(); for (const category of categories) { if (reservedIds.has(category.id) || groups.has(category.id)) throw new Error('分类数据异常,请稍后重试'); groups.set(category.id, []); icons.set(category.id, category.icon || '📄'); } for (const row of worksheets) { const group = groups.get(row.category); if (!group) continue; const date = row.contentUpdatedAt || row.createdAt; group.push({ id: row.id, title: row.title, subtitle: row.subtitle, icon: icons.get(row.category) || '📄', previewImg: resolvePreviewUrl(row.previewImg), ageBand: `${row.ageMin}-${row.ageMax}岁`, difficultyLabel: DIFFICULTY_LABELS[row.difficulty - 1] || '基础', path: row.path, available: Boolean(row.path), likes: row.likes, downloads: row.downloads, date: Number.isFinite(Date.parse(date)) ? new Date(date).toISOString().slice(0, 10) : '', updatedAt: row.updatedAt, }); } return { sidebarTabs: [ ...BASE_TABS, ...categories.map(({ id, name, icon }) => ({ id, name, icon })), ], groups, }; } /** 两个榜单沿用旧页面含义:热门推荐按收藏数,下载最多按下载数,各取前 12。 */ export function getCategoryItems( dataset: CategoryDataset | null, categoryId: string, keyword = '', ): CategoryItem[] { if (!dataset) return []; let items: CategoryItem[]; if ( categoryId === 'all' || categoryId === 'hot' || categoryId === 'top-downloads' ) { const all = sortWorksheets(Array.from(dataset.groups.values()).flat()); if (categoryId === 'all') items = all; else { const key = categoryId === 'hot' ? 'likes' : 'downloads'; items = all .sort((a, b) => compareCountDescending(a[key], b[key])) .slice(0, 12); } } else items = sortWorksheets(dataset.groups.get(categoryId) || []); const kw = keyword.trim().toLowerCase(); return kw ? items.filter( (item) => item.title.toLowerCase().includes(kw) || item.subtitle.toLowerCase().includes(kw), ) : items; }