feat: 新增备课模块并修复全模块 P0/P1/P2 缺陷
Some checks failed
Security / deep-security-scan (push) Failing after 20m5s
DR Drill / dr-drill (push) Failing after 1m31s
CI / scheduled-backup (push) Failing after 1m31s
CI / backup-verify (push) Has been skipped
CI / weekly-dr-drill (push) Failing after 0s
CI / build-deploy (push) Has been cancelled
CI / security-scan (push) Has been cancelled

主要变更:

- 新增 lesson-preparation 模块: 备课编辑器、节点编辑、AI 建议、知识点选择、版本历史、作业发布

- 新增 shared 通用组件: charts/question-bank-filters/schedule-list/ui (chip-nav/filter-bar/page-header/stat-card/stat-item)

- 新增 student/admin 端 loading.tsx 与 error.tsx, 优化加载与错误态体验

- 新增 teacher/lesson-plans 页面 (列表/新建/编辑)

- 新增 drizzle 迁移 0002_tiny_lionheart 及 snapshot

- 新增 textbooks/schema.ts 与 exams/utils/normalize-structure.ts

- 修复 Tiptap v3 SSR hydration 崩溃 (rich-text-block immediatelyRender: false)

- 重构多模块 data-access/actions/组件, 修复权限校验与类型规范

- 同步架构文档 004/005 反映新增模块、导出、依赖关系

- 归档 bugs/* 测试报告与 e2e 测试脚本 (admin/parent/student/teacher web_test)
This commit is contained in:
SpecialX
2026-06-22 01:06:16 +08:00
parent d8962aba96
commit 978d9a8309
327 changed files with 34070 additions and 5642 deletions

493
bugs/parent_web_test.json Normal file
View File

@@ -0,0 +1,493 @@
{
"test_date": "2026-06-20 12:28:43",
"test_target": "家长端 (Parent)",
"base_url": "http://localhost:3000",
"parent_email": "parent_g1c1_1@xiaoxue.edu.cn",
"summary": {
"total": 24,
"passed": 17,
"failed": 7,
"warnings": 0
},
"pages": {
"parent_dashboard": {
"url": "http://localhost:3000/parent/dashboard",
"route": "/parent/dashboard",
"category": "Dashboard",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/parent/dashboard",
"redirect_url": null,
"errors": [],
"warnings": [],
"content_checks": []
},
"parent_grades": {
"url": "http://localhost:3000/parent/grades",
"route": "/parent/grades",
"category": "Grades",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/parent/grades",
"redirect_url": null,
"errors": [],
"warnings": [],
"content_checks": []
},
"parent_attendance": {
"url": "http://localhost:3000/parent/attendance",
"route": "/parent/attendance",
"category": "Attendance",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/parent/attendance",
"redirect_url": null,
"errors": [],
"warnings": [],
"content_checks": []
},
"announcements": {
"url": "http://localhost:3000/announcements",
"route": "/announcements",
"category": "Announcements",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/announcements",
"redirect_url": null,
"errors": [],
"warnings": [],
"content_checks": []
},
"messages": {
"url": "http://localhost:3000/messages",
"route": "/messages",
"category": "Messages",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/messages",
"redirect_url": null,
"errors": [],
"warnings": [],
"content_checks": []
},
"messages_compose": {
"url": "http://localhost:3000/messages/compose",
"route": "/messages/compose",
"category": "Messages",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/messages/compose",
"redirect_url": null,
"errors": [],
"warnings": [],
"content_checks": []
},
"profile": {
"url": "http://localhost:3000/profile",
"route": "/profile",
"category": "Profile",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/profile",
"redirect_url": null,
"errors": [],
"warnings": [],
"content_checks": []
},
"settings": {
"url": "http://localhost:3000/settings",
"route": "/settings",
"category": "Settings",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/settings",
"redirect_url": null,
"errors": [],
"warnings": [],
"content_checks": []
},
"settings_security": {
"url": "http://localhost:3000/settings/security",
"route": "/settings/security",
"category": "Settings",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/settings/security",
"redirect_url": null,
"errors": [],
"warnings": [],
"content_checks": []
},
"parent_children_user_s_g1c1_1": {
"url": "http://localhost:3000/parent/children/user_s_g1c1_1",
"route": "/parent/children/user_s_g1c1_1",
"category": "Child Detail",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/parent/children/user_s_g1c1_1",
"redirect_url": null,
"errors": [],
"warnings": [
"Error text on page: Due 2026年6月18日"
],
"content_checks": []
},
"forbidden_admin_dashboard": {
"url": "http://localhost:3000/admin/dashboard",
"route": "/admin/dashboard",
"category": "Cross-Role Access Control",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/parent/dashboard?from=%2Fadmin%2Fdashboard&reason=forbidden",
"redirect_url": "http://localhost:3000/parent/dashboard?from=%2Fadmin%2Fdashboard&reason=forbidden",
"errors": [],
"warnings": [],
"content_checks": [
"跨角色访问被权限系统拦截"
]
},
"forbidden_admin_school": {
"url": "http://localhost:3000/admin/school",
"route": "/admin/school",
"category": "Cross-Role Access Control",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/parent/dashboard?from=%2Fadmin%2Fschool&reason=forbidden",
"redirect_url": "http://localhost:3000/parent/dashboard?from=%2Fadmin%2Fschool&reason=forbidden",
"errors": [],
"warnings": [],
"content_checks": [
"跨角色访问被权限系统拦截"
]
},
"forbidden_teacher_dashboard": {
"url": "http://localhost:3000/teacher/dashboard",
"route": "/teacher/dashboard",
"category": "Cross-Role Access Control",
"status": "failed",
"http_status": 500,
"final_url": "http://localhost:3000/teacher/dashboard",
"redirect_url": null,
"errors": [
"跨角色访问返回 HTTP 500应被重定向拦截",
"Failed to load resource: the server responded with a status of 500 (Internal Server Error)",
"%o\n\n%s Error: Teacher not found\n at getTeacherIdForMutations (about://React/Server/E:%5CDesktop%5CCICD%5C.next%5Cdev%5Cserver%5Cchunks%5Cssr%5C%5Broot-of-the-server%5D__458f1717._.js?61:9381:27)\n at TeacherDashboardPage (about://React/Server/E:%5CDesktop%5CCICD%5C.next%5Cdev%5Cserver%5Cchunks%5Cssr%5C%5Broot-of-the-server%5D__6e4018f8._.js?62:2019:23)\n at resolveErrorDev (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-server-dom-turbopack_9212ccad._.js:...(已截断)"
],
"warnings": [],
"content_checks": []
},
"forbidden_teacher_exams": {
"url": "http://localhost:3000/teacher/exams",
"route": "/teacher/exams",
"category": "Cross-Role Access Control",
"status": "failed",
"http_status": 200,
"final_url": "http://localhost:3000/teacher/exams/all",
"redirect_url": "http://localhost:3000/teacher/exams/all",
"errors": [
"⚠️ 安全漏洞:家长成功访问了受限页面(最终 URL: http://localhost:3000/teacher/exams/all权限隔离失效",
"%o\n\n%s Error: Failed query: select `exams`.`id`, `exams`.`title`, `exams`.`description`, `exams`.`structure`, `exams`.`creator_id`, `exams`.`subject_id`, `exams`.`grade_id`, `exams`.`start_time`, `exams`.`end_time`, `exams`.`exam_mode`, `exams`.`duration_minutes`, `exams`.`shuffle_questions`, `exams`.`allow_late_start`, `exams`.`late_start_grace_minutes`, `exams`.`anti_cheat_enabled`, `exams`.`status`, `exams`.`created_at`, `exams`.`updated_at`, `exams_subject`.`data` as `subject`, `exams_gradeE...(已截断)"
],
"warnings": [],
"content_checks": []
},
"forbidden_teacher_homework": {
"url": "http://localhost:3000/teacher/homework",
"route": "/teacher/homework",
"category": "Cross-Role Access Control",
"status": "failed",
"http_status": 500,
"final_url": "http://localhost:3000/teacher/homework/assignments",
"redirect_url": "http://localhost:3000/teacher/homework/assignments",
"errors": [
"跨角色访问返回 HTTP 500应被重定向拦截",
"Failed to load resource: the server responded with a status of 500 (Internal Server Error)",
"%o\n\n%s Error: Teacher not found\n at getTeacherIdForMutations (about://React/Server/E:%5CDesktop%5CCICD%5C.next%5Cdev%5Cserver%5Cchunks%5Cssr%5C%5Broot-of-the-server%5D__458f1717._.js?47:9381:27)\n at AssignmentsPage (about://React/Server/E:%5CDesktop%5CCICD%5C.next%5Cdev%5Cserver%5Cchunks%5Cssr%5C%5Broot-of-the-server%5D__8e4de1e6._.js?48:253:23)\n at resolveErrorDev (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-server-dom-turbopack_9212ccad._.js:1882:1...(已截断)"
],
"warnings": [],
"content_checks": []
},
"forbidden_teacher_grades": {
"url": "http://localhost:3000/teacher/grades",
"route": "/teacher/grades",
"category": "Cross-Role Access Control",
"status": "failed",
"http_status": 200,
"final_url": "http://localhost:3000/teacher/grades",
"redirect_url": null,
"errors": [
"⚠️ 安全漏洞:家长成功访问了受限页面(最终 URL: http://localhost:3000/teacher/grades权限隔离失效"
],
"warnings": [],
"content_checks": []
},
"forbidden_teacher_questions": {
"url": "http://localhost:3000/teacher/questions",
"route": "/teacher/questions",
"category": "Cross-Role Access Control",
"status": "failed",
"http_status": 200,
"final_url": "http://localhost:3000/teacher/questions",
"redirect_url": null,
"errors": [
"⚠️ 安全漏洞:家长成功访问了受限页面(最终 URL: http://localhost:3000/teacher/questions权限隔离失效"
],
"warnings": [],
"content_checks": []
},
"forbidden_teacher_classes": {
"url": "http://localhost:3000/teacher/classes",
"route": "/teacher/classes",
"category": "Cross-Role Access Control",
"status": "failed",
"http_status": 200,
"final_url": "http://localhost:3000/teacher/classes/my",
"redirect_url": "http://localhost:3000/teacher/classes/my",
"errors": [
"⚠️ 安全漏洞:家长成功访问了受限页面(最终 URL: http://localhost:3000/teacher/classes/my权限隔离失效"
],
"warnings": [],
"content_checks": []
},
"forbidden_teacher_attendance": {
"url": "http://localhost:3000/teacher/attendance",
"route": "/teacher/attendance",
"category": "Cross-Role Access Control",
"status": "failed",
"http_status": 200,
"final_url": "http://localhost:3000/teacher/attendance",
"redirect_url": null,
"errors": [
"⚠️ 安全漏洞:家长成功访问了受限页面(最终 URL: http://localhost:3000/teacher/attendance权限隔离失效"
],
"warnings": [],
"content_checks": []
},
"forbidden_student_dashboard": {
"url": "http://localhost:3000/student/dashboard",
"route": "/student/dashboard",
"category": "Cross-Role Access Control",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/parent/dashboard?from=%2Fstudent%2Fdashboard&reason=forbidden",
"redirect_url": "http://localhost:3000/parent/dashboard?from=%2Fstudent%2Fdashboard&reason=forbidden",
"errors": [],
"warnings": [],
"content_checks": [
"跨角色访问被权限系统拦截"
]
},
"forbidden_student_learning": {
"url": "http://localhost:3000/student/learning",
"route": "/student/learning",
"category": "Cross-Role Access Control",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/parent/dashboard?from=%2Fstudent%2Flearning&reason=forbidden",
"redirect_url": "http://localhost:3000/parent/dashboard?from=%2Fstudent%2Flearning&reason=forbidden",
"errors": [],
"warnings": [],
"content_checks": [
"跨角色访问被权限系统拦截"
]
},
"forbidden_student_grades": {
"url": "http://localhost:3000/student/grades",
"route": "/student/grades",
"category": "Cross-Role Access Control",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/parent/dashboard?from=%2Fstudent%2Fgrades&reason=forbidden",
"redirect_url": "http://localhost:3000/parent/dashboard?from=%2Fstudent%2Fgrades&reason=forbidden",
"errors": [],
"warnings": [],
"content_checks": [
"跨角色访问被权限系统拦截"
]
},
"forbidden_student_attendance": {
"url": "http://localhost:3000/student/attendance",
"route": "/student/attendance",
"category": "Cross-Role Access Control",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/parent/dashboard?from=%2Fstudent%2Fattendance&reason=forbidden",
"redirect_url": "http://localhost:3000/parent/dashboard?from=%2Fstudent%2Fattendance&reason=forbidden",
"errors": [],
"warnings": [],
"content_checks": [
"跨角色访问被权限系统拦截"
]
},
"forbidden_management_grade_classes": {
"url": "http://localhost:3000/management/grade/classes",
"route": "/management/grade/classes",
"category": "Cross-Role Access Control",
"status": "passed",
"http_status": 200,
"final_url": "http://localhost:3000/parent/dashboard?from=%2Fmanagement%2Fgrade%2Fclasses&reason=forbidden",
"redirect_url": "http://localhost:3000/parent/dashboard?from=%2Fmanagement%2Fgrade%2Fclasses&reason=forbidden",
"errors": [],
"warnings": [],
"content_checks": [
"跨角色访问被权限系统拦截"
]
}
},
"functional_checks": [
{
"name": "返回仪表盘按钮",
"expected": "存在 Back to Dashboard 链接",
"actual": "Found",
"passed": true
},
{
"name": "子女姓名标题",
"expected": "显示子女姓名",
"actual": "小明",
"passed": true
},
{
"name": "邮箱掩码处理",
"expected": "邮箱被掩码为 j***@domain.com",
"actual": "Masked",
"passed": true
},
{
"name": "作业摘要卡片",
"expected": "显示 {childName}'s Homework",
"actual": "Found",
"passed": true
},
{
"name": "作业统计 - Pending",
"expected": "显示 Pending 计数",
"actual": "Found",
"passed": true
},
{
"name": "作业统计 - Submitted",
"expected": "显示 Submitted 计数",
"actual": "Found",
"passed": true
},
{
"name": "作业统计 - Graded",
"expected": "显示 Graded 计数",
"actual": "Found",
"passed": true
},
{
"name": "成绩趋势卡片",
"expected": "显示成绩信息",
"actual": "Found",
"passed": true
},
{
"name": "今日课表卡片",
"expected": "显示 {childName}'s Today Schedule",
"actual": "Found",
"passed": true
},
{
"name": "View all 链接",
"expected": "存在 View all 链接",
"actual": "Found",
"passed": true
},
{
"name": "仪表盘标题",
"expected": "Parent Dashboard",
"actual": "Parent Dashboard",
"passed": true
},
{
"name": "问候语显示",
"expected": "Good morning/afternoon/evening 或 Welcome",
"actual": "Found",
"passed": true
},
{
"name": "Grades 快捷入口",
"expected": "存在",
"actual": "Found",
"passed": true
},
{
"name": "Attendance 快捷入口",
"expected": "存在",
"actual": "Found",
"passed": true
},
{
"name": "Announcements 快捷入口",
"expected": "存在",
"actual": "Found",
"passed": true
},
{
"name": "子女卡片显示",
"expected": "≥1 个子女卡片",
"actual": "1 个",
"passed": true
},
{
"name": "子女卡片 - Pending 统计",
"expected": "显示 Pending 计数",
"actual": "Found",
"passed": true
},
{
"name": "子女卡片 - Overdue 统计",
"expected": "显示 Overdue 计数",
"actual": "Found",
"passed": true
},
{
"name": "子女数量提示",
"expected": "显示 'N child(ren) linked'",
"actual": "Found",
"passed": true
},
{
"name": "侧边栏 - Dashboard",
"expected": "显示 Dashboard 导航项",
"actual": "Found",
"passed": true
},
{
"name": "侧边栏 - Grades",
"expected": "显示 Grades 导航项",
"actual": "Found",
"passed": true
},
{
"name": "侧边栏 - Attendance",
"expected": "显示 Attendance 导航项",
"actual": "Found",
"passed": true
},
{
"name": "侧边栏 - Announcements",
"expected": "显示 Announcements 导航项",
"actual": "Found",
"passed": true
},
{
"name": "侧边栏 - Messages",
"expected": "显示 Messages 导航项",
"actual": "Found",
"passed": true
}
],
"security_checks": [
{
"name": "访问不存在/非关联子女应被拒绝",
"expected": "显示 Access denied 或 404",
"actual": "Access denied",
"passed": true
}
],
"console_errors": [],
"navigation_issues": []
}