feat: 完成分龄页开发

This commit is contained in:
R524809
2026-05-06 17:28:51 +08:00
parent 50394154f7
commit 9bdf850f20
54 changed files with 3499 additions and 1021 deletions
@@ -0,0 +1,315 @@
type CloudFunctionResult<T> = {
success?: boolean;
message?: string;
data?: T;
dryRun?: boolean;
total?: number;
failed?: number;
results?: Array<{ id: string | null; ok: boolean; error?: string }>;
};
async function callCloudFunction<T>(
name: string,
data?: Record<string, unknown>,
): Promise<CloudFunctionResult<T>> {
const response = (await wx.cloud.callFunction({
name,
data: data || {},
})) as { result?: CloudFunctionResult<T> };
return response.result || {};
}
type LocalDef = {
id: string;
ageMin: number;
ageMax: number;
tags: readonly string[];
};
function inferGradeFromAge(ageMin: number, ageMax: number): number {
const centerAge = Math.round((ageMin + ageMax) / 2);
const ageGradeMap: Record<number, number> = {
2: -4,
3: -3,
4: -2,
5: -1,
6: 0,
7: 1,
8: 2,
9: 3,
10: 4,
11: 5,
12: 6,
};
return ageGradeMap[centerAge] ?? 0;
}
type ConfigModule = Record<string, readonly LocalDef[] | unknown>;
type ConfigSource = {
label: string;
modulePath: string;
exportName: string;
};
const CONFIG_SOURCES: ConfigSource[] = [
{
label: 'math',
modulePath: '../../mathPages/mathDraw/mathDraw.config',
exportName: 'MATH_WORKSHEET_DEFINITIONS',
},
{
label: 'focus',
modulePath: '../../focusPages/focusDraw/focusDraw.config',
exportName: 'FOCUS_WORKSHEET_DEFINITIONS',
},
{
label: 'letterTracing',
modulePath: '../../englishPages/letterTracing/letterTracing.config',
exportName: 'LETTER_TRACING_WORKSHEET_DEFINITIONS',
},
];
function isLocalDef(value: unknown): value is LocalDef {
const row = value as Partial<LocalDef>;
return (
!!row &&
typeof row.id === 'string' &&
typeof row.ageMin === 'number' &&
typeof row.ageMax === 'number' &&
Array.isArray(row.tags)
);
}
async function loadConfigSource(source: ConfigSource): Promise<LocalDef[]> {
const mod = (await require.async(source.modulePath)) as ConfigModule;
const rows = mod[source.exportName];
if (!Array.isArray(rows) || !rows.every(isLocalDef)) {
throw new Error(`${source.label} 配置格式不正确`);
}
return [...rows];
}
async function collectAll(): Promise<LocalDef[]> {
const groups = await Promise.all(CONFIG_SOURCES.map(loadConfigSource));
return groups.flat();
}
function assertUniqueIds(rows: LocalDef[]) {
const set = new Set<string>();
for (const r of rows) {
if (set.has(r.id)) {
throw new Error(`本地配置存在重复 id${r.id}`);
}
set.add(r.id);
}
}
function summarizeResult(
title: string,
result: CloudFunctionResult<unknown>,
): string {
if (!result.success) {
return `${title}失败:${result.message || '未知错误'}`;
}
const total = result.total ?? 0;
const failed = result.failed ?? 0;
const ok = total - failed;
const firstError = result.results?.find((r) => !r.ok)?.error;
return `${title}完成:成功 ${ok}/${total}${
failed ? `,失败 ${failed}(如:${firstError || '未知原因'}` : ''
}`;
}
Page({
data: {
configLoading: true,
configReady: false,
configError: '',
configTotal: 0,
configSourceCount: CONFIG_SOURCES.length,
loadingAges: false,
loadingTags: false,
loadingRebuild: false,
statusText: '配置加载中,请稍候',
},
onLoad() {
void this.loadConfigs();
},
async onPullDownRefresh() {
await this.loadConfigs();
wx.stopPullDownRefresh();
},
async loadConfigs() {
this.setData({
configLoading: true,
configReady: false,
configError: '',
statusText: '配置加载中,请稍候',
});
try {
const rows = await collectAll();
assertUniqueIds(rows);
this.setData({
configLoading: false,
configReady: true,
configTotal: rows.length,
statusText: `配置已加载:${rows.length} 个 worksheet,可执行同步`,
});
} catch (error) {
const msg = error instanceof Error ? error.message : '配置加载失败';
this.setData({
configLoading: false,
configReady: false,
configError: msg,
statusText: msg,
});
wx.showToast({ title: msg, icon: 'none' });
}
},
async sync(mode: 'ages' | 'tags') {
if (!this.data.configReady) {
wx.showToast({ title: '配置未加载完成', icon: 'none' });
return;
}
const rows = await collectAll();
assertUniqueIds(rows);
const patches =
mode === 'ages'
? rows.map((r) => ({
id: r.id,
ageMin: r.ageMin,
ageMax: r.ageMax,
grade: inferGradeFromAge(r.ageMin, r.ageMax),
}))
: rows.map((r) => ({
id: r.id,
tags: [...r.tags],
}));
wx.showLoading({ title: '同步中...' });
const startedAt = Date.now();
try {
const patchRes = await callCloudFunction('worksheetsBatchPatch', {
patches,
dryRun: false,
});
if (!patchRes.success) {
throw new Error(patchRes.message || '同步失败');
}
const elapsed = Math.round((Date.now() - startedAt) / 1000);
const text = [
summarizeResult(
mode === 'ages'
? '同步年龄(worksheetsBatchPatch'
: '同步标签(worksheetsBatchPatch',
patchRes,
),
'page-config 未自动重建,如需刷新前台展示请点击“重建配置”。',
`耗时:${elapsed}s`,
].join('\n');
this.setData({ statusText: text });
wx.showToast({ title: '已完成', icon: 'success' });
} catch (error) {
const msg = error instanceof Error ? error.message : '同步失败';
this.setData({ statusText: msg });
wx.showToast({ title: msg, icon: 'none' });
} finally {
wx.hideLoading();
}
},
async rebuildPageConfig() {
wx.showLoading({ title: '重建中...' });
const startedAt = Date.now();
try {
const rebuildRes = await callCloudFunction('pageContentBuild', {
page: 'all',
});
const elapsed = Math.round((Date.now() - startedAt) / 1000);
const text = [
summarizeResult(
'重建 page-configpageContentBuild: all',
rebuildRes,
),
`耗时:${elapsed}s`,
].join('\n');
this.setData({ statusText: text });
if (!rebuildRes.success) {
throw new Error(rebuildRes.message || '重建失败');
}
wx.showToast({ title: '已重建', icon: 'success' });
} catch (error) {
const msg = error instanceof Error ? error.message : '重建失败';
this.setData({ statusText: msg });
wx.showToast({ title: msg, icon: 'none' });
} finally {
wx.hideLoading();
}
},
async onSyncAges() {
if (
!this.data.configReady ||
this.data.loadingAges ||
this.data.loadingTags ||
this.data.loadingRebuild
)
return;
this.setData({ loadingAges: true });
try {
await this.sync('ages');
} finally {
this.setData({ loadingAges: false });
}
},
async onSyncTags() {
if (
!this.data.configReady ||
this.data.loadingAges ||
this.data.loadingTags ||
this.data.loadingRebuild
)
return;
this.setData({ loadingTags: true });
try {
await this.sync('tags');
} finally {
this.setData({ loadingTags: false });
}
},
async onRebuildConfig() {
if (
this.data.loadingAges ||
this.data.loadingTags ||
this.data.loadingRebuild
)
return;
this.setData({ loadingRebuild: true });
try {
await this.rebuildPageConfig();
} finally {
this.setData({ loadingRebuild: false });
}
},
});