133 lines
4.2 KiB
TypeScript
133 lines
4.2 KiB
TypeScript
import { apiRequest } from './api-request';
|
|
|
|
/** 小程序使用的公开接口字段,不复用旧云集合的 _id 和管理配置。 */
|
|
export interface ApiCategory {
|
|
id: string;
|
|
name: string;
|
|
icon: string;
|
|
parentId: string | null;
|
|
status: 'active';
|
|
}
|
|
|
|
export interface ApiWorksheet {
|
|
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;
|
|
}
|