Files
NextEdu/src/modules/grades/README.md

12 KiB

grades 成绩模块

成绩全生命周期管理:录入(单条/批量/按试卷)/导入导出/分析统计/草稿锁定/申诉处理/成绩单生成 代码规模: 60 文件 · 360 符号 · 120,932 行(arch.db) 模块路径: src/modules/grades/


架构图

graph TB
    subgraph Client["客户端组件"]
        direction TB
        BatchEntry["batch-grade-entry<br/>(376 行) 批量录入"]
        ExcelDlg["excel-import-dialog<br/>(519 行) Excel 导入"]
        RecordList["grade-record-list<br/>(232 行) 成绩列表"]
        RecordForm["grade-record-form<br/>(196 行) 录入表单"]
        ReportCard["report-card-view<br/>(324 行) 成绩单"]
        Charts["分析图表<br/>distribution/comparison/<br/>trend/mastery/growth"]
        DraftBanner["draft-lock-banner<br/>草稿锁定横幅"]
    end

    subgraph Hooks["Hooks"]
        UseUndo["use-batch-grade-entry-undo<br/>(101 行) 批量撤销"]
        UseLock["use-draft-lock<br/>(185 行) 锁定状态"]
    end

    subgraph Server["服务端 Actions(6 文件)"]
        Actions["actions.ts<br/>(745 行) 核心 CRUD"]
        ActAnalytics["actions-analytics<br/>(353 行) 分析"]
        ActImport["actions-import<br/>(295 行) 导入"]
        ActAppeal["actions-appeal<br/>(215 行) 申诉"]
        ActLock["actions-lock<br/>(209 行) 锁定"]
        ActDraft["actions-draft<br/>(123 行) 草稿"]
    end

    subgraph DA["Data Access"]
        DA1["data-access.ts<br/>(635 行) 核心 CRUD"]
        DADraft["data-access-drafts<br/>(410 行) 草稿"]
        DAAppeal["data-access-appeals<br/>(273 行) 申诉"]
        DARank["data-access-ranking<br/>(199 行) 排名"]
        DAExam["data-access-exam-entry<br/>(190 行) 按试卷录入"]
        DAAnalytics["analytics 4 维度<br/>class(390)/overview(136)<br/>student(262)/trend(135)"]
    end

    subgraph Lib["纯函数库"]
        Stats["stats-service.ts<br/>(696 行) 统计纯函数"]
        StatsTest["stats-service.test.ts<br/>(665 行) 单测"]
        Scope["lib/scope-check.ts<br/>assertClassInScope"]
        ScopeFilter["lib/scope-filter.ts"]
        Report["lib/report-card.ts<br/>(385 行) 成绩单生成"]
        Export["export.ts + import-export.ts<br/>导出/导入"]
        Notify["lib/notify.ts<br/>(188 行) 通知"]
        Guards["lib/type-guards.ts"]
        GradeUtils["lib/grade-utils.ts"]
    end

    subgraph DB["数据库表"]
        GR[(grade_records)]
        GRD[(grade_record_drafts)]
        GA[(grade_appeals)]
        GL[(grade_locks)]
    end

    subgraph Cross["跨模块联动"]
        Diagnostic["diagnostic<br/>updateMasteryFromExamScore"]
        Exams["exams<br/>getExamForGradeEntry"]
        ErrorBook["error-book<br/>collectFromExamSubmission"]
    end

    BatchEntry --> UseUndo
    BatchEntry --> Actions
    ExcelDlg --> ActImport
    RecordList --> Actions
    RecordForm --> Actions
    ReportCard --> Report
    Charts --> ActAnalytics
    DraftBanner --> UseLock
    UseLock --> ActLock

    Actions --> DA1
    ActAnalytics --> DAAnalytics
    ActAnalytics --> Stats
    ActImport --> Export
    ActAppeal --> DAAppeal
    ActLock --> GL
    ActDraft --> DADraft
    DAExam --> DAExam2["data-access-exam-entry"]
    DA1 --> GR
    DADraft --> GRD
    DAAppeal --> GA
    DA1 --> Scope
    DA1 --> Notify

    Actions --> Diagnostic
    DAExam --> Exams
    Actions --> ErrorBook

核心流程图

流程 1: 按试卷批量录入成绩(跨模块联动)

sequenceDiagram
    participant U as 教师
    participant BE as batch-grade-entry
    participant Act as actions.ts
    participant DAExam as data-access-exam-entry
    participant Ex as exams data-access
    participant Diag as diagnostic
    participant EB as error-book
    participant DB as MySQL

    U->>BE: 选择试卷 + 班级
    BE->>Act: batchCreateByExamAction
    Act->>Act: requirePermission(GRADE_RECORD_MANAGE)
    Act->>Act: assertClassInScope(班级范围校验)
    Act->>Ex: getExamForGradeEntry(题目分值)
    Act->>DAExam: batchCreateGradeRecordsByExam
    DAExam->>DB: INSERT grade_records(批量)
    DAExam-->>Act: 成功

    Act->>Diag: updateMasteryFromExamScore<br/>更新知识点掌握度
    Act->>EB: collectFromExamSubmission<br/>采集错题
    Act->>Act: notifyGradeEntered + notifyScoreAnomaly
    Act-->>BE: ActionState
    BE->>U: 支持撤销(undoBatchCreate)

流程 2: 成绩分析(纯函数统计)

flowchart LR
    A[选择班级/学生/学期] --> B[actions-analytics]
    B --> C{分析维度}
    C -->|班级统计| D[data-access-analytics-class<br/>390 行]
    C -->|全校概览| E[data-access-analytics-overview<br/>136 行]
    C -->|学生汇总| F[data-access-analytics-student<br/>262 行]
    C -->|趋势分析| G[data-access-analytics-trend<br/>135 行]
    D --> H[stats-service.ts<br/>696 行纯函数]
    E --> H
    F --> H
    G --> H
    H --> I[computeGradeStats<br/>average/median/stdDev<br/>passRate/excellentRate]
    I --> J[charts 渲染<br/>distribution/comparison/trend]

流程 3: 草稿保存与成绩锁定

sequenceDiagram
    participant U as 教师
    participant BE as batch-grade-entry
    participant Lock as use-draft-lock
    participant ActD as actions-draft
    participant ActL as actions-lock
    participant DAD as data-access-drafts
    participant DB as MySQL

    U->>BE: 录入中(未提交)
    BE->>ActD: saveDraftAction
    ActD->>DAD: upsertDraft
    DAD->>DB: UPSERT grade_record_drafts
    Note over U,DB: 草稿不写入 grade_records<br/>支持断点续录

    U->>BE: 提交成绩
    BE->>ActD: commitDraftAction
    ActD->>DAD: 读取草稿 → 批量插入 grade_records
    ActD->>DAD: 删除草稿
    ActD-->>BE: 成功

    U->>Lock: 锁定成绩(防篡改)
    Lock->>ActL: lockGradesAction
    ActL->>ActL: requirePermission(GRADE_LOCK)
    ActL->>DB: INSERT grade_locks
    ActL-->>Lock: 锁定后成绩只读

技术栈

类别 技术 用途
数据库 Drizzle ORM grade_records / drafts / appeals / locks 表 CRUD
缓存 cacheFn data-access 函数请求级去重(86 处)
Server Action requirePermission GRADE_RECORD_MANAGE / GRADE_LOCK 等权限点
URL 状态 nuqs 分析页筛选条件 URL 同步
表单验证 Zod schema.ts(238 行) 输入校验
数据可视化 recharts 分布/对比/趋势/掌握度/成长档案图表
Excel 导入导出 自定义 export/import-export 成绩导出 + Excel 模板导入
统计计算 stats-service 纯函数 average/median/stdDev/passRate/excellentRate
权限范围 scope-check assertClassInScope 防越权访问他班成绩

文件结构

src/modules/grades/
├─ actions.ts                  # 核心 CRUD Action (745 行): 成绩录入/更新/删除/查询
├─ actions-analytics.ts        # 分析 Action (353 行)
├─ actions-import.ts           # 导入 Action (295 行)
├─ actions-appeal.ts           # 申诉 Action (215 行)
├─ actions-lock.ts             # 锁定 Action (209 行)
├─ actions-draft.ts            # 草稿 Action (123 行)
├─ data-access.ts              # 核心 CRUD (635 行): grade_records
├─ data-access-drafts.ts       # 草稿数据访问 (410 行)
├─ data-access-appeals.ts      # 申诉数据访问 (273 行)
├─ data-access-ranking.ts      # 排名查询 (199 行)
├─ data-access-exam-entry.ts   # 按试卷录入 (190 行)
├─ data-access-analytics.ts    # 分析入口 (58 行)
├─ data-access-analytics-{class,overview,student,trend}.ts # 四维度 (390/136/262/135)
├─ stats-service.ts            # 统计纯函数 (696 行)
├─ stats-service.test.ts       # 统计单测 (665 行)
├─ export.ts                   # 导出 (291 行)
├─ import-export.ts            # 导入 (278 行)
├─ schema.ts                   # Zod 验证 (238 行)
├─ types.ts                    # 类型定义 (387 行)
├─ lib/
│  ├─ report-card.ts           # 成绩单生成 (385 行)
│  ├─ notify.ts                # 通知 (188 行)
│  ├─ scope-check.ts           # 班级范围校验 (37 行)
│  ├─ scope-filter.ts          # 范围过滤 (52 行)
│  ├─ scope-check.test.ts      # 范围校验单测 (74 行)
│  ├─ grade-utils.ts           # 工具函数 (21 行)
│  └─ type-guards.ts           # 类型守卫 (30 行)
├─ hooks/
│  ├─ use-draft-lock.ts        # 草稿/锁定状态 (185 行)
│  └─ use-batch-grade-entry-undo.ts # 批量撤销 (101 行)
└─ components/                 # 28 个组件
   ├─ excel-import-dialog.tsx  # Excel 导入 (519 行)
   ├─ batch-grade-entry.tsx    # 批量录入 (376 行)
   ├─ report-card-view.tsx     # 成绩单 (324 行)
   ├─ grade-record-detail.tsx  # 详情 (239 行)
   ├─ grade-record-list.tsx    # 列表 (232 行)
   ├─ grade-record-row.tsx     # 行 (200 行)
   ├─ grade-record-form.tsx    # 表单 (196 行)
   ├─ knowledge-point-mastery-chart.tsx # 知识点掌握度 (237 行)
   ├─ class-comparison-chart.tsx # 班级对比 (260 行)
   ├─ grade-distribution-chart.tsx # 分布 (210 行)
   └─ growth-archive-chart.tsx / grade-trend-card.tsx / ranking-trend-card.tsx / ...

依赖关系

依赖(本模块 → 他模块)

模块 依赖类型 用途
exams import getExamForGradeEntry 按试卷录入
diagnostic data-access-call + import updateMasteryFromExamScore 联动掌握度
error-book data-access-call + import collectFromExamSubmission 采集错题
classes data-access-call + import 班级信息 + 范围校验
school data-access-call + import 学科/年级/学年过滤
users data-access-call + import 学生/教师身份
notifications data-access-call + import 成绩录入/异常通知
parent data-access-call + import 成绩单推送家长
attendance data-access-call 考勤关联成绩分析
shared import DB / cache / auth-guard / 工具函数

被依赖(他模块 → 本模块)

模块 依赖类型 用途
attendance data-access-call + import 考勤模块引用成绩分析
diagnostic import 诊断引用成绩数据
parent data-access-call + import 家长端查看成绩
school import 学校维度成绩统计

关键约束

  • actions 按职责拆 6 文件: actions.ts(745 行)近上限,按 CRUD/分析/导入/申诉/锁定/草稿拆分,避免超 1000 行
  • data-access 按职责 + 维度拆分: 主 CRUD + 草稿/申诉/排名/试卷录入 + analytics 四维度(class/overview/student/trend)
  • scope-check 统一范围校验: assertClassInScope 确保班级在用户权限范围内,防止越权访问他班成绩
  • type-guards 替代 as 断言: 遵循禁止 as 断言规范,类型收窄通过类型守卫
  • 录入联动诊断与错题: 成绩录入后调用 updateMasteryFromExamScore 更新掌握度 + collectFromExamSubmission 采集错题
  • stats-service 纯函数 + 单测: 696 行统计逻辑独立 + 665 行单测,保证计算正确且可独立测试

架构决策

决策 原因
actions 按职责拆 6 文件 成绩场景多(CRUD/分析/导入/申诉/锁定/草稿),拆分避免单文件超 1000 行硬上限
stats-service 独立纯函数 统计逻辑与 DB I/O 解耦,696 行纯函数 + 665 行单测,保证计算正确可复用
analytics 按维度拆 4 文件 班级/概览/学生/趋势四维度独立,按场景加载,避免单文件臃肿
scope-check 集中范围校验 防越权访问他班成绩,权限校验收口单一入口,assertClassInScope 返回错误码
type-guards 替代 as 断言 遵循编码规范禁止 as,类型收窄通过类型守卫实现
录入联动 diagnostic/error-book 成绩是掌握度计算与错题采集的源头,录入时同步触发,保证数据一致