Files
2026-10-23 16:53:22 +08:00

135 lines
4.3 KiB
TypeScript

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<ApiCategory[]> {
const rows = await apiRequest<ApiCategory[]>('/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<ApiWorksheet[]> {
const pageSize = 100;
const rows: ApiWorksheet[] = [];
const ids = new Set<string>();
let expectedTotal: number | undefined;
for (let page = 1; ; page++) {
const result = await apiRequest<WorksheetPage>('/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<ApiWorksheet> {
const row = await apiRequest<ApiWorksheet>(
`/worksheets/${encodeURIComponent(id)}`,
{ auth: 'none' },
);
validateWorksheet(row);
if (row.id !== id) throw new Error('题型数据异常,请稍后重试');
return row;
}