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 参考项目差距闭环(含完整文件清单)
This commit is contained in:
108
apps/teacher-portal/src/mocks/handlers-p4.ts
Normal file
108
apps/teacher-portal/src/mocks/handlers-p4.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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";
|
||||
}
|
||||
Reference in New Issue
Block a user