- 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 参考项目差距闭环(含完整文件清单)
109 lines
3.1 KiB
TypeScript
109 lines
3.1 KiB
TypeScript
/**
|
||
* MSW P4 Handlers - 知识图谱 + 学情分析 mock 拦截
|
||
*
|
||
* 独立于 handlers.ts 维护,合并时将 p4Handlers 追加到主 handlers 数组,
|
||
* 并在主 handleGraphQL 的 default 之前插入以下 case。
|
||
*
|
||
* 覆盖 operationName(对应 graphql-p4.ts):
|
||
* - KnowledgeGraph → 知识图谱节点 + 边
|
||
* - ClassAnalytics → 班级学情分析
|
||
* - StudentAnalytics → 单生学情分析
|
||
*
|
||
* 维护者:ai13(teacher-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 独立 handler(POST /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";
|
||
}
|