feat(portal-shell): 学生域全页面迁移与规范合规修复

- 学生域 32 页全量迁移(含作答/自动保存/提交/诊断)

- 补齐 4 个 MSW mock 缺口,修 diagnostic case 名

- 修 4 处 Tailwind 任意值;新增共享组件与路由
This commit is contained in:
SpecialX
2026-08-31 11:25:21 +08:00
parent 039db5efdd
commit 04b7a40bdc
493 changed files with 70985 additions and 2112 deletions

View File

@@ -0,0 +1,132 @@
/**
* portal-shell 适配trackEvent/EventName 在客户端微前端不存在,
* 服务端埋点由 BFF 负责;此处保留为 no-op 占位以保持类型契约。
*/
type EventName = string;
const trackEvent = async (_event: {
event: EventName;
userId: string;
targetType?: string;
properties?: Record<string, unknown>;
}): Promise<void> => {
/* no-op: portal-shell client-side stub */
};
/**
* portal-shell 适配data-access/recordAiEvent 在客户端微前端不存在,
* 由 BFF 维护事件存储;此处保留为 no-op 占位。
*/
const recordAiEvent = (_event: {
userId: string;
capability: string;
success: boolean;
durationMs: number;
timestamp: number;
errorMessage?: string;
}): void => {
/* no-op: portal-shell client-side stub */
};
export type AiUsageEvent = {
userId: string;
capability:
| "chat"
| "similar_question"
| "grading_assist"
| "lesson_content"
| "question_variant"
| "weakness_analysis"
| "child_summary"
| "study_path"
| "explain_error";
providerId?: string;
model?: string;
success: boolean;
durationMs: number;
tokenUsage?: number;
errorMessage?: string;
};
const AI_EVENT_MAP: Record<AiUsageEvent["capability"], EventName> = {
chat: "ai.chat",
similar_question: "ai.similar_question",
grading_assist: "ai.grading_assist",
lesson_content: "ai.lesson_content",
question_variant: "ai.question_variant",
weakness_analysis: "ai.weakness_analysis",
child_summary: "ai.child_summary",
study_path: "ai.study_path",
explain_error: "ai.explain_error",
};
/**
* AI 使用埋点
*
* 记录每次 AI 调用的元数据,用于监控、成本分析与异常排查。
* 同时写入 data-access 层的内存事件存储(供管理员仪表盘聚合查询)。
* 非阻塞,失败不影响主流程。
*/
export const trackAiUsage = (event: AiUsageEvent): void => {
const eventName = AI_EVENT_MAP[event.capability];
// 写入 data-access 层(供 getAiUsageStats 聚合)
recordAiEvent({
userId: event.userId,
capability: event.capability,
success: event.success,
durationMs: event.durationMs,
timestamp: Date.now(),
errorMessage: event.errorMessage,
});
// 写入全局 trackEvent供外部监控系统
void trackEvent({
event: eventName,
userId: event.userId,
targetType: event.capability,
properties: {
providerId: event.providerId,
model: event.model,
success: event.success,
durationMs: event.durationMs,
tokenUsage: event.tokenUsage,
errorMessage: event.errorMessage,
},
}).catch(() => {
// 静默失败:埋点不应影响业务流程
});
};
/**
* 测量 AI 调用耗时并自动埋点
*/
export const withAiTracking = async <T>(
userId: string,
capability: AiUsageEvent["capability"],
providerId: string | undefined,
fn: () => Promise<{ result: T; model?: string; tokenUsage?: number }>,
): Promise<T> => {
const start = Date.now();
try {
const { result, model, tokenUsage } = await fn();
trackAiUsage({
userId,
capability,
providerId,
model,
success: true,
durationMs: Date.now() - start,
tokenUsage,
});
return result;
} catch (error) {
trackAiUsage({
userId,
capability,
providerId,
success: false,
durationMs: Date.now() - start,
errorMessage: error instanceof Error ? error.message : String(error),
});
throw error;
}
};