import type { PaperType } from '../config/worksheets/papers'; import { apiRequest } from './api-request'; /** 小程序使用的公开接口字段,不复用旧云集合的 _id 和管理配置。 */ export interface ApiCategory { id: string; name: string; icon: string; parentId: string | null; status: 'active'; } export interface ApiWorksheet { rendering?: { rendererKey: 'paper-sheet'; paperType: PaperType }; id: string; title: string; subtitle: string; category: string; path: string; ageMin: number; ageMax: number; difficulty: 1 | 2 | 3 | 4; previewImg: string | null; likes: string; downloads: string; createdAt: string; updatedAt: string; contentUpdatedAt: string | null; } interface WorksheetPage { items: ApiWorksheet[]; total: number; page: number; pageSize: number; hasNext: boolean; } /** 分类直接由服务端发布状态决定,不用本地目录补齐或恢复下架分类。 */ export async function listCategories(): Promise { const rows = await apiRequest('/categories', { auth: 'none', }); if ( !Array.isArray(rows) || rows.some( (row) => !row || typeof row.id !== 'string' || !row.id || typeof row.name !== 'string' || typeof row.icon !== 'string' || row.status !== 'active', ) ) { throw new Error('分类数据异常,请稍后重试'); } return rows; } /** 展示计数保持十进制字符串,不转换 Number 或重复叠加展示种子。 */ function validateWorksheet(row: ApiWorksheet): void { if ( !row || !row.id || typeof row.id !== 'string' || typeof row.title !== 'string' || typeof row.subtitle !== 'string' || typeof row.category !== 'string' || typeof row.path !== 'string' || !Number.isInteger(row.ageMin) || !Number.isInteger(row.ageMax) || row.ageMin < 0 || row.ageMax < row.ageMin || ![1, 2, 3, 4].includes(row.difficulty) || typeof row.downloads !== 'string' || !/^\d+$/.test(row.downloads) || typeof row.likes !== 'string' || !/^\d+$/.test(row.likes) || (row.previewImg !== null && typeof row.previewImg !== 'string') ) { throw new Error('题型数据异常,请稍后重试'); } } /** 取完整分页后才能在客户端计算收藏榜和混合排序;分页变化或缺页时整次重试。 */ export async function listAllWorksheets(): Promise { const pageSize = 100; const rows: ApiWorksheet[] = []; const ids = new Set(); let expectedTotal: number | undefined; for (let page = 1; ; page++) { const result = await apiRequest('/worksheets', { auth: 'none', data: { page, pageSize }, }); if ( !result || !Array.isArray(result.items) || !Number.isSafeInteger(result.total) || result.total < 0 || result.page !== page || result.pageSize !== pageSize || result.hasNext !== page * pageSize < result.total || result.items.length !== Math.max( 0, Math.min(pageSize, result.total - (page - 1) * pageSize), ) || (expectedTotal !== undefined && result.total !== expectedTotal) ) { throw new Error('题型列表已变化,请重试'); } expectedTotal = result.total; for (const row of result.items) { validateWorksheet(row); if (ids.has(row.id)) throw new Error('题型列表已变化,请重试'); ids.add(row.id); rows.push(row); } if (!result.hasNext) return rows; } } /** 打开题型前重新检查公开详情,避免已下架内容从旧列表继续进入。 */ export async function getWorksheet(id: string): Promise { const row = await apiRequest( `/worksheets/${encodeURIComponent(id)}`, { auth: 'none' }, ); validateWorksheet(row); if (row.id !== id) throw new Error('题型数据异常,请稍后重试'); return row; }