feat(student-portal): 实现参考项目差距功能 - 手写板/图片上传/教师批注/课表/设置/作业分析/加入班级/延期申请/多维筛选
This commit is contained in:
@@ -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 + 2,f'(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)=3,x=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": ["复合函数求导", "高阶导数"]
|
||||
}
|
||||
]
|
||||
}
|
||||
14
apps/student-portal/src/mocks/fixtures/myProfile.json
Normal file
14
apps/student-portal/src/mocks/fixtures/myProfile.json
Normal 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"
|
||||
}
|
||||
272
apps/student-portal/src/mocks/fixtures/mySchedule.json
Normal file
272
apps/student-portal/src/mocks/fixtures/mySchedule.json
Normal 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)班教室"
|
||||
}
|
||||
]
|
||||
12
apps/student-portal/src/mocks/fixtures/studentGrowth.json
Normal file
12
apps/student-portal/src/mocks/fixtures/studentGrowth.json
Normal 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 }
|
||||
]
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
/** 导出工具函数供测试使用 */
|
||||
|
||||
Reference in New Issue
Block a user