Files
Edu/apps/teacher-portal/src/mocks/handlers-p4.ts
SpecialX d49d211425 feat(teacher-portal): 完成参考项目差距闭环 P3-P7 全量实现
- P3 考试/作业/成绩 mutation + 详情页 + 批改界面 + 乐观更新 + 多 Tab 同步
- P4 知识图谱 SVG 可视化 + 学情分析仪表盘 + parent-portal Remote
- P5 WebSocket 通知中心 + AI 出题(SSE) + AI 教案 + AI 学情报告
- P6 可观测性硬化:Sentry + WebVitals + OTel + A11y + 性能配置 + Cookie 迁移
- P7 参考项目差距闭环:新增 35 个页面覆盖 13 个缺失模块
  - attendance(考勤 4 页)/questions(题库)/textbooks(教材 2 页)
  - classes/[id] 详情 + classes/schedule 课表
  - course-plans(2 页)/diagnostic(2 页)/error-book/practice
  - exams/[id]/build 组卷 + exams/[id]/analytics 考后分析
  - exams/[id]/edit-rich 富文本编辑 + exams/[id]/proctoring 监考
  - grades/entry 批量录入 + grades/stats 统计 + grades/analytics 分析 + grades/report-card 报告卡
  - homework/submissions 列表 + assignments/[id]/submissions 批量批改
  - homework/submissions/[submissionId] 单份批改 + scan-grading 扫描批改
  - lesson-plans 编辑器 + library + calendar + heatmap 5 页
  - elective 选修课 3 页 /leave 请假 /schedule-changes 调课
- P7 基础设施:61 GraphQL operations + 5 handlers + 13 fixtures + 11 viewports
- 集成 browser.ts/server.ts 注册所有 p7 handlers(fallthrough 顺序)
- viewports.ts 扩展 11 个新导航项
- 验证:tsc --noEmit 零错误 + eslint 零错误
- 文档:workline.md 新增 §5 P7 参考项目差距闭环(含完整文件清单)
2026-07-13 14:27:04 +08:00

109 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* MSW P4 Handlers - 知识图谱 + 学情分析 mock 拦截
*
* 独立于 handlers.ts 维护,合并时将 p4Handlers 追加到主 handlers 数组,
* 并在主 handleGraphQL 的 default 之前插入以下 case。
*
* 覆盖 operationName对应 graphql-p4.ts
* - KnowledgeGraph → 知识图谱节点 + 边
* - ClassAnalytics → 班级学情分析
* - StudentAnalytics → 单生学情分析
*
* 维护者ai13teacher-portal
*/
import { http, HttpResponse } from "msw";
import { filterKnowledgeGraph } from "./fixtures/knowledge-graph";
import {
mockClassAnalytics,
findStudentAnalytics,
} from "./fixtures/analytics";
/** GraphQL 响应体urql 期望 { data: ... } */
function graphqlData<T>(data: T) {
return HttpResponse.json({ data });
}
/**
* 根据 operationName 路由 P4 GraphQL mock 响应
*
* 返回 null 表示当前 operationName 不属于 P4交回主 handler 处理。
*/
export function handleP4GraphQL(
operationName: string,
variables: Record<string, unknown>,
) {
switch (operationName) {
case "KnowledgeGraph": {
const subject = (variables.subject as string | undefined) ?? null;
return graphqlData({
knowledgeGraph: filterKnowledgeGraph(subject),
});
}
case "ClassAnalytics": {
// classId 传入但 mock 数据固定,返回时回填以保证字段一致
const classId =
(variables.classId as string | undefined) ?? mockClassAnalytics.classId;
return graphqlData({
classAnalytics: { ...mockClassAnalytics, classId },
});
}
case "StudentAnalytics": {
const studentId =
(variables.studentId as string | undefined) ?? "stu-001";
return graphqlData({
studentAnalytics: findStudentAnalytics(studentId),
});
}
default:
return null;
}
}
/**
* P4 独立 handlerPOST /api/v1/teacher/graphql
*
* 合并策略:将本数组追加到 handlers.ts 的 handlers 数组之前MSW 按顺序匹配,
* 先于主 handler 的 default case 命中 P4 operation
*/
export const p4Handlers = [
http.post("*/api/v1/teacher/graphql", async ({ request }) => {
const body = (await request.json()) as {
operationName?: string;
query?: string;
variables?: Record<string, unknown>;
};
const operationName =
body.operationName ?? extractOperationName(body.query);
const variables = body.variables ?? {};
const p4Result = handleP4GraphQL(operationName, variables);
if (p4Result) {
return p4Result;
}
// 非 P4 operation交回后续 handler返回 passthrough 让 MSW 继续匹配)
return HttpResponse.json(
{
errors: [
{
message: `P4 handler 未覆盖的 GraphQL operation: ${operationName}`,
extensions: { code: "BFF_TEACHER_NOT_IMPLEMENTED" },
},
],
},
{ status: 200 },
);
}),
];
/** 从 query 字符串提取 operationName兜底 */
function extractOperationName(query?: string): string {
if (!query) return "Unknown";
const match = query.match(/(?:query|mutation)\s+(\w+)/);
return match?.[1] ?? "Unknown";
}