feat(student-portal): 实现参考项目差距功能 - 手写板/图片上传/教师批注/课表/设置/作业分析/加入班级/延期申请/多维筛选

This commit is contained in:
SpecialX
2026-07-13 12:54:43 +08:00
parent d6cc70e02e
commit fad6e43b47
23 changed files with 4110 additions and 105 deletions

View File

@@ -0,0 +1,51 @@
{
"homeworkId": "hw-001",
"title": "数学函数与导数练习",
"totalScore": 100,
"avgScore": 78,
"maxScore": 98,
"minScore": 45,
"submitCount": 42,
"questions": [
{
"questionId": "q-001",
"questionText": "求函数 f(x) = x² + 2x 在 x=1 处的导数值",
"questionType": "short-answer",
"correctRate": 0.85,
"referenceAnswer": "f'(x) = 2x + 2f'(1) = 4",
"knowledgePoints": ["导数定义", "求导法则"]
},
{
"questionId": "q-002",
"questionText": "下列哪个函数是奇函数?",
"questionType": "single-choice",
"correctRate": 0.92,
"referenceAnswer": "f(x) = x³",
"knowledgePoints": ["奇偶函数", "函数性质"]
},
{
"questionId": "q-003",
"questionText": "计算定积分 ∫₀¹ (x² + 1) dx 的值",
"questionType": "short-answer",
"correctRate": 0.71,
"referenceAnswer": "∫₀¹ (x² + 1) dx = [x³/3 + x]₀¹ = 1/3 + 1 = 4/3",
"knowledgePoints": ["定积分", "牛顿-莱布尼茨公式"]
},
{
"questionId": "q-004",
"questionText": "讨论函数 f(x) = x³ - 3x + 1 的单调性与极值",
"questionType": "essay",
"correctRate": 0.54,
"referenceAnswer": "f'(x) = 3x² - 3令 f'(x)=0 得 x=±1。x<-1 或 x>1 时单调递增,-1<x<1 时单调递减。x=-1 处取极大值 f(-1)=3x=1 处取极小值 f(1)=-1",
"knowledgePoints": ["导数应用", "单调性", "极值"]
},
{
"questionId": "q-005",
"questionText": "已知 f(x) = ln(x+1),求 f''(x)",
"questionType": "fill-blank",
"correctRate": 0.68,
"referenceAnswer": "f'(x) = 1/(x+1)f''(x) = -1/(x+1)²",
"knowledgePoints": ["复合函数求导", "高阶导数"]
}
]
}

View File

@@ -0,0 +1,14 @@
{
"id": "student-001",
"name": "张小明",
"studentNo": "2024001",
"email": "zhangxiaoming@edu.cn",
"phone": "138****8888",
"gender": "男",
"avatar": "",
"grade": "高三",
"classId": "cls-001",
"className": "高三(2)班",
"bio": "热爱数学与物理,目标是考取理工类大学。",
"preferredSubjectId": "subj-math"
}

View File

@@ -0,0 +1,272 @@
[
{
"id": "sch-001",
"dayOfWeek": 1,
"startTime": "08:00",
"endTime": "08:45",
"subject": "语文",
"teacher": "王老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-002",
"dayOfWeek": 1,
"startTime": "09:00",
"endTime": "09:45",
"subject": "数学",
"teacher": "李老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-003",
"dayOfWeek": 1,
"startTime": "10:15",
"endTime": "11:00",
"subject": "英语",
"teacher": "张老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-004",
"dayOfWeek": 1,
"startTime": "14:00",
"endTime": "14:45",
"subject": "物理",
"teacher": "陈老师",
"classroom": "物理实验室"
},
{
"id": "sch-005",
"dayOfWeek": 1,
"startTime": "15:00",
"endTime": "15:45",
"subject": "化学",
"teacher": "刘老师",
"classroom": "化学实验室"
},
{
"id": "sch-006",
"dayOfWeek": 1,
"startTime": "16:15",
"endTime": "17:00",
"subject": "体育",
"teacher": "赵老师",
"classroom": "操场"
},
{
"id": "sch-007",
"dayOfWeek": 2,
"startTime": "08:00",
"endTime": "08:45",
"subject": "数学",
"teacher": "李老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-008",
"dayOfWeek": 2,
"startTime": "09:00",
"endTime": "09:45",
"subject": "语文",
"teacher": "王老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-009",
"dayOfWeek": 2,
"startTime": "10:15",
"endTime": "11:00",
"subject": "物理",
"teacher": "陈老师",
"classroom": "物理实验室"
},
{
"id": "sch-010",
"dayOfWeek": 2,
"startTime": "14:00",
"endTime": "14:45",
"subject": "英语",
"teacher": "张老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-011",
"dayOfWeek": 2,
"startTime": "15:00",
"endTime": "15:45",
"subject": "生物",
"teacher": "周老师",
"classroom": "生物实验室"
},
{
"id": "sch-012",
"dayOfWeek": 2,
"startTime": "16:15",
"endTime": "17:00",
"subject": "历史",
"teacher": "孙老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-013",
"dayOfWeek": 3,
"startTime": "08:00",
"endTime": "08:45",
"subject": "英语",
"teacher": "张老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-014",
"dayOfWeek": 3,
"startTime": "09:00",
"endTime": "09:45",
"subject": "数学",
"teacher": "李老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-015",
"dayOfWeek": 3,
"startTime": "10:15",
"endTime": "11:00",
"subject": "化学",
"teacher": "刘老师",
"classroom": "化学实验室"
},
{
"id": "sch-016",
"dayOfWeek": 3,
"startTime": "14:00",
"endTime": "14:45",
"subject": "语文",
"teacher": "王老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-017",
"dayOfWeek": 3,
"startTime": "15:00",
"endTime": "15:45",
"subject": "地理",
"teacher": "吴老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-018",
"dayOfWeek": 3,
"startTime": "16:15",
"endTime": "17:00",
"subject": "政治",
"teacher": "郑老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-019",
"dayOfWeek": 4,
"startTime": "08:00",
"endTime": "08:45",
"subject": "物理",
"teacher": "陈老师",
"classroom": "物理实验室"
},
{
"id": "sch-020",
"dayOfWeek": 4,
"startTime": "09:00",
"endTime": "09:45",
"subject": "数学",
"teacher": "李老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-021",
"dayOfWeek": 4,
"startTime": "10:15",
"endTime": "11:00",
"subject": "语文",
"teacher": "王老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-022",
"dayOfWeek": 4,
"startTime": "14:00",
"endTime": "14:45",
"subject": "英语",
"teacher": "张老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-023",
"dayOfWeek": 4,
"startTime": "15:00",
"endTime": "15:45",
"subject": "美术",
"teacher": "钱老师",
"classroom": "美术教室"
},
{
"id": "sch-024",
"dayOfWeek": 4,
"startTime": "16:15",
"endTime": "17:00",
"subject": "信息技术",
"teacher": "冯老师",
"classroom": "计算机教室"
},
{
"id": "sch-025",
"dayOfWeek": 5,
"startTime": "08:00",
"endTime": "08:45",
"subject": "数学",
"teacher": "李老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-026",
"dayOfWeek": 5,
"startTime": "09:00",
"endTime": "09:45",
"subject": "英语",
"teacher": "张老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-027",
"dayOfWeek": 5,
"startTime": "10:15",
"endTime": "11:00",
"subject": "生物",
"teacher": "周老师",
"classroom": "生物实验室"
},
{
"id": "sch-028",
"dayOfWeek": 5,
"startTime": "14:00",
"endTime": "14:45",
"subject": "语文",
"teacher": "王老师",
"classroom": "高三(2)班教室"
},
{
"id": "sch-029",
"dayOfWeek": 5,
"startTime": "15:00",
"endTime": "15:45",
"subject": "音乐",
"teacher": "蒋老师",
"classroom": "音乐教室"
},
{
"id": "sch-030",
"dayOfWeek": 5,
"startTime": "16:15",
"endTime": "17:00",
"subject": "班会",
"teacher": "李老师",
"classroom": "高三(2)班教室"
}
]

View File

@@ -0,0 +1,12 @@
[
{ "date": "2026-07-01", "myScore": 82, "classAverage": 78 },
{ "date": "2026-07-05", "myScore": 85, "classAverage": 79 },
{ "date": "2026-07-10", "myScore": 88, "classAverage": 80 },
{ "date": "2026-07-15", "myScore": 86, "classAverage": 81 },
{ "date": "2026-07-20", "myScore": 90, "classAverage": 82 },
{ "date": "2026-07-25", "myScore": 92, "classAverage": 83 },
{ "date": "2026-07-30", "myScore": 89, "classAverage": 82 },
{ "date": "2026-08-04", "myScore": 91, "classAverage": 84 },
{ "date": "2026-08-09", "myScore": 94, "classAverage": 85 },
{ "date": "2026-08-14", "myScore": 95, "classAverage": 86 }
]

View File

@@ -36,6 +36,10 @@ import myWeaknessFixture from "./fixtures/myWeakness.json";
import myTrendFixture from "./fixtures/myTrend.json";
import myNotificationsFixture from "./fixtures/myNotifications.json";
import serverTimeFixture from "./fixtures/serverTime.json";
import myScheduleFixture from "./fixtures/mySchedule.json";
import studentGrowthFixture from "./fixtures/studentGrowth.json";
import assignmentAnalysisFixture from "./fixtures/assignmentAnalysis.json";
import myProfileFixture from "./fixtures/myProfile.json";
// ---------------------------------------------------------------------------
// GraphQL 请求/响应类型
@@ -253,6 +257,24 @@ function handleQuery(
}),
);
case "mySchedule":
return graphqlOk("mySchedule", ok(myScheduleFixture));
case "studentGrowth":
return graphqlOk("studentGrowth", ok(studentGrowthFixture));
case "assignmentAnalysis": {
const homeworkId = variables["homeworkId"] ?? "hw-001";
const detail =
homeworkId === assignmentAnalysisFixture.homeworkId
? assignmentAnalysisFixture
: { ...assignmentAnalysisFixture, homeworkId: homeworkId as string };
return graphqlOk("assignmentAnalysis", ok(detail));
}
case "myProfile":
return graphqlOk("myProfile", ok(myProfileFixture));
default: {
// 未覆盖的查询操作名,返回错误
return graphqlError(
@@ -350,6 +372,67 @@ function handleMutation(
return graphqlOk("updateNotificationPreference", ok(result));
}
case "updateProfile": {
const input = (variables["input"] ?? {}) as Record<string, unknown>;
const result = {
id: myProfileFixture.id,
name: (input["name"] as string) ?? myProfileFixture.name,
email: (input["email"] as string) ?? myProfileFixture.email,
phone: (input["phone"] as string) ?? myProfileFixture.phone,
gender: (input["gender"] as string) ?? myProfileFixture.gender,
bio: (input["bio"] as string) ?? myProfileFixture.bio,
preferredSubjectId:
(input["preferredSubjectId"] as string) ??
myProfileFixture.preferredSubjectId,
};
return graphqlOk("updateProfile", ok(result));
}
case "changePassword": {
return graphqlOk("changePassword", ok({ ok: true }));
}
case "requestExtension": {
const input = (variables["input"] ?? {}) as Record<string, unknown>;
const homeworkId =
(input["homeworkId"] as string) ?? "hw-001";
const reason = (input["reason"] as string) ?? "";
const newDue = new Date();
newDue.setDate(newDue.getDate() + 2);
const result = {
homeworkId,
approved: true,
newDueDate: newDue.toISOString(),
reason,
};
return graphqlOk("requestExtension", ok(result));
}
case "joinClass": {
const input = (variables["input"] ?? {}) as Record<string, unknown>;
const inviteCode = (input["inviteCode"] as string) ?? "";
if (inviteCode.length !== 6) {
return graphqlOk(
"joinClass",
fail(ERROR_CODES.BFF_STUDENT_VALIDATION_FAILED, "邀请码必须为 6 位"),
);
}
const result = {
classId: `class-${Date.now()}`,
className: "高三(3)班",
grade: "高三",
inviteCode,
};
return graphqlOk("joinClass", ok(result));
}
case "leaveClass": {
const input = (variables["input"] ?? {}) as Record<string, unknown>;
const classId = (input["classId"] as string) ?? "";
const result = { classId, ok: true };
return graphqlOk("leaveClass", ok(result));
}
default: {
// 未覆盖的变更操作名,返回错误
return graphqlError(
@@ -382,6 +465,10 @@ const QUERY_NAMES = new Set<QueryOperationName>([
"myTrend",
"myNotifications",
"serverTime",
"mySchedule",
"studentGrowth",
"assignmentAnalysis",
"myProfile",
]);
/** 变更操作名集合 */
@@ -394,6 +481,11 @@ const MUTATION_NAMES = new Set<MutationOperationName>([
"recordExamViolation",
"recordPasteEvent",
"updateNotificationPreference",
"updateProfile",
"changePassword",
"requestExtension",
"joinClass",
"leaveClass",
]);
/**
@@ -475,6 +567,10 @@ export const fixtures = {
myTrend: myTrendFixture,
myNotifications: myNotificationsFixture,
serverTime: serverTimeFixture,
mySchedule: myScheduleFixture,
studentGrowth: studentGrowthFixture,
assignmentAnalysis: assignmentAnalysisFixture,
myProfile: myProfileFixture,
};
/** 导出工具函数供测试使用 */