refactor(grades,attendance,error-book,elective): 组件化重构第 1 批 - 低风险模块

- grades: 拆分 batch-grade-entry (457→374) + grade-record-list (523→231),新增 4 个子组件
- attendance: 迁移 stats-cards/filters 到 StatsGrid/FilterBar 底座
- error-book: 迁移 stats-cards/filters 到 StatsGrid/FilterBar 底座
- elective: 迁移 stats-cards/filters 到 StatsGrid/FilterBar 底座
- 删除 12 个重复组件,新增 app 层 filter 组件就近放置
- tsc 零错误,lint 无新增错误
This commit is contained in:
SpecialX
2026-07-06 19:01:57 +08:00
parent 11ddc8ccbe
commit 19a05091d3
96 changed files with 8641 additions and 1088 deletions

View File

@@ -1255,7 +1255,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- Lib✅ P1-2 新增,✅ P3 更新签名,✅ P3-26 拆分,✅ v4-P2-6 新增 scope-check`toNumber` / `normalize`(位于 `lib/grade-utils.ts``buildScopeClassFilter(scope, currentUserId?)`P3-26 从 grade-utils.ts 迁移至 `lib/scope-filter.ts`P3 修复:`class_members` scope 内置 studentId 过滤,需传入 currentUserId 参数);`assertClassInScope(scope: DataScope, classId: string): string | null`(✅ v4-P2-6 从 actions.ts 迁移至 `lib/scope-check.ts`:校验 classId 是否在 scope 允许范围内,供 actions.ts 与 actions-analytics.ts 复用。迁移原因actions.ts 是 "use server" 文件要求所有 export 为 async而 assertClassInScope 是同步函数)
- Stats-service✅ P1-1 新增):`computeGradeStats` / `computeAverageScore` / `buildGradeTrendPoints` / `computeTrendAverage` / `computeClassComparisonStats` / `computeSubjectComparisonStats` / `computeGradeDistribution` / `buildRankingTrendPoints`(从 3 个 data-access 文件抽取的纯函数,使数据层专注 DB I/O统计逻辑可独立测试
- Export✅ v4-P1-12 新增):`exportGradeRecordsToExcel` / `exportClassGradeReportToExcel` / `exportStudentGradeRecordsToExcel`v4-P1-12 新增:导出单个学生成绩单家长视角,仅含成绩明细 + 个人统计不含班级数据scope 为 children 自动按 studentId 过滤)/ `formatDateForFile`(已迁移至 shared/lib/utils
- Components✅ P1-5 新增):`WidgetBoundary`Error Boundary + Suspense + Skeleton 组合,含 a11y 属性)/ `SchoolWideSummaryCard`(✅ v3-P2 新增管理员全校成绩汇总卡片4 个统计卡片 + 各年级对比表格)/ `ScoreCell`(✅ v4-P1-7 新增:成绩单元格组件,根据得分率着色——红<60%/黄60-84%/绿≥85%,使用语义化 Tailwind 类名避免动态拼接fullScore<=0 时不着色)
- Components✅ P1-5 新增,✅ Phase 4.6 拆分扩展`WidgetBoundary`Error Boundary + Suspense + Skeleton 组合,含 a11y 属性)/ `SchoolWideSummaryCard`(✅ v3-P2 新增管理员全校成绩汇总卡片4 个统计卡片 + 各年级对比表格)/ `ScoreCell`(✅ v4-P1-7 新增:成绩单元格组件,根据得分率着色——红<60%/黄60-84%/绿≥85%,使用语义化 Tailwind 类名避免动态拼接fullScore<=0 时不着色)/ `BatchGradeEntrySelectors`(✅ Phase 4.6 新增:批量录入试卷/班级选择器子组件)/ `BatchGradeEntryToolbar`(✅ Phase 4.6 新增:批量录入底部工具栏)/ `GradeRecordRow`(✅ Phase 4.6 新增:成绩记录行渲染:桌面表格+移动端卡片)/ `GradeRecordDetail`(✅ Phase 4.6 新增:成绩记录编辑/删除对话框集合,导出 EditableFields 类型与 toEditableFields 工具函数)
**依赖关系**
- 依赖:`shared/*`、`@/auth`、`classes`(✅ P1-1 已修复:通过 classes data-access.getClassExists/getClassNameById/getClassNamesByIds/getActiveStudentIdsByClassId/getStudentActiveClassId/getClassesByGradeId/getClassGradeIdsByClassIds、`school`(✅ P1-1 已修复:通过 school data-access.getSubjectOptions/getGradeOptions、`users`(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds、`exams`(✅ 2026-06-24 新增:通过 exams data-access.getExamsForGradeEntry/getExamForGradeEntry 获取试卷列表和详情供按试卷录入成绩使用)、`error-book`(✅ 2026-06-25 新增:通过 error-book data-access.collectFromExamSubmission 在成绩录入后自动采集考试错题)、`diagnostic`(通过 diagnostic data-access.updateMasteryFromExamScore 更新掌握度)
@@ -1365,25 +1365,25 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| `components/school-wide-summary-card.tsx` | - | v3-P2 新增管理员全校成绩汇总卡片4 个统计卡片 + 各年级对比表格) |
| `components/score-cell.tsx` | 41 | v4-P1-7 新增:成绩单元格组件,根据得分率着色(红<60%/黄60-84%/绿≥85%),使用语义化 Tailwind 类名 |
| `components/grade-trend-card.tsx` | 69 | 趋势卡片v2-P2-9 修复a11yv2-P1-4i18nP3 修复NaN 日期检查 + fullScore > 0 守卫) |
| `components/grade-record-list.tsx` | 200+ | 成绩记录列表v2-P1-4i18nP3 修复safeActionCall 包装删除操作v4-P1-7使用 ScoreCellv4-P1-10overflow-x-autov3-P3-2 新增:多选复选框 + 全选 + 批量删除工具栏 + 批量删除确认对话框,接入 bulkDeleteGradeRecordsAction |
| `components/grade-distribution-chart.tsx` | 100 | 分数分布图v2-P1-4i18n |
| `components/subject-comparison-chart.tsx` | 62 | 科目对比图v2-P1-4i18n |
| `components/class-comparison-chart.tsx` | 194 | 班级对比图v2-P1-4i18nv3-P3-5 新增:显著性分析区域,基于极差和样本量的经验规则判断班级间差异,含可折叠详细分析) |
| `components/grade-trend-chart.tsx` | 59 | 趋势图v2-P1-4i18n |
| `components/grade-record-form.tsx` | 177 | 录入表单v2-P2-7 修复Label htmlForv2-P1-4i18nP3 修复safeActionCall 包装提交) |
| `components/batch-grade-entry.tsx` | 422 | 2026-06-24 完全重写:导出名改为 BatchGradeEntryByExam按试卷录入每题得分。Excel 式表格(行=学生,列=题目,末列=总分自动计算)。交互:试卷选择器(按 scope 过滤)→ 班级选择器(按试卷 gradeId 过滤);多行多列 Excel 粘贴Tab 分隔Enter 跳下一行同一列Tab 跳下一格;实时统计(已录入/总数/均分/最高/最低分数校验超过题目满分标红撤销机制sessionStorage 5 分钟有效)。**P1-6/P1-7 重构2026-06-25**:拆分为 3 个子组件 + 1 个 HookBatchGradeEntryTable + BatchGradeEntryStats + BatchGradeEntryDialog + useBatchGradeEntryUndo主组件从 604 行降至 422 行;撤销数据使用 isUndoData 类型守卫替代 as { ids: string[]; timestamp: number } 断言window.confirm() 已替换为声明式 AlertDialog。Props: exams/classes/classGradeMap/exam/students/defaultExamId?/defaultClassId?(原 v2-P2-7 Label htmlForv2-P1-4 i18nP3 safeActionCall + localStorage 安全检查v3-P2 服务端草稿v3-P3-1 下载 CSV 模板v4-P3-2 新手引导均已替换) |
| `components/batch-grade-entry.tsx` | 376 | 2026-06-24 完全重写:导出名改为 BatchGradeEntryByExam按试卷录入每题得分。Excel 式表格(行=学生,列=题目,末列=总分自动计算)。交互:试卷选择器(按 scope 过滤)→ 班级选择器(按试卷 gradeId 过滤);多行多列 Excel 粘贴Tab 分隔Enter 跳下一行同一列Tab 跳下一格;实时统计(已录入/总数/均分/最高/最低分数校验超过题目满分标红撤销机制sessionStorage 5 分钟有效)。**P1-6/P1-7 重构2026-06-25**:拆分为 3 个子组件 + 1 个 HookBatchGradeEntryTable + BatchGradeEntryStats + BatchGradeEntryDialog + useBatchGradeEntryUndo**Phase 4.6 重构2026-07-06**:进一步拆出 BatchGradeEntrySelectors试卷/班级选择器含未选态)+ BatchGradeEntryToolbar保存/清除按钮+粘贴提示),主组件从 457 行降至 376 行,容器仅保留状态编排与 Server Action 调用。撤销数据使用 isUndoData 类型守卫替代 as { ids: string[]; timestamp: number } 断言window.confirm() 已替换为声明式 AlertDialog。Props: exams/classes/classGradeMap/exam/students/defaultExamId?/defaultClassId? |
| `components/batch-grade-entry-selectors.tsx` | 124 | **Phase 4.6 新增2026-07-06**:批量录入试卷/班级选择器子组件(含未选试卷态引导)。从 batch-grade-entry.tsx 拆分,承接试卷选择器、班级选择器、试卷信息徽章三个区块的渲染 |
| `components/batch-grade-entry-toolbar.tsx` | 53 | **Phase 4.6 新增2026-07-06**:批量录入底部工具栏子组件(清除按钮 + 保存按钮 + 粘贴提示)。从 batch-grade-entry.tsx 拆分 |
| `components/batch-grade-entry-table.tsx` | 115 | **P1-6 新增2026-06-25**:批量录入 Excel 式表格子组件(行=学生,列=题目,末列=总分自动计算)。支持多行多列 Excel 粘贴、Enter 跳下一行、Tab 跳下一格、分数校验(超过题目满分标红)。接受 inputRefs 供父组件管理输入焦点 |
| `components/batch-grade-entry-stats.tsx` | 68 | **P1-6 新增2026-06-25**:批量录入统计栏子组件,显示已录入/总数/均分/最高/最低/无效分数徽章,含学生搜索框过滤。导出 BatchGradeEntryStatsData 接口 |
| `components/batch-grade-entry-dialog.tsx` | 58 | **P1-6 新增2026-06-25**:批量录入切换确认对话框子组件(替代 window.confirm())。导出 PendingSwitch 联合类型 { kind: "exam"; examId; message } \| { kind: "class"; classId; message } \| null |
| `hooks/use-batch-grade-entry-undo.ts` | 86 | **P1-6/P1-7 新增2026-06-25**:批量录入撤销 Hook。封装撤销令牌的序列化、sessionStorage 读写、5 分钟 TTL 过期检查、撤销 Action 调用。包含 isUndoData 类型守卫函数(替代 as 断言),使用 in 运算符做安全属性访问 |
| `components/grade-filters.tsx` | 76 | 过滤器v2-P1-4i18n**P2-22026-06-25**:补全 : JSX.Element 返回值类型) |
| `components/student-grade-summary.tsx` | 107 | 学生成绩摘要v2-P1-4i18n |
| `components/export-button.tsx` | 79 | 导出按钮v2-P1-4i18nP3 修复safeActionCall 包装导出操作;**P2-22026-06-25**handleExport 补全 : Promise<void> 返回值类型) |
| `components/analytics-filters.tsx` | 86+ | 分析过滤器v2-P1-4i18nv3-P2 新增exams/currentExamId/currentSemester props添加学期和考试筛选 ChipNav |
| `components/stats-class-selector.tsx` | 40 | 统计班级选择器v2-P1-4i18n |
| `components/grade-stats-card.tsx` | 74 | 统计卡片v2-P1-4i18n |
| `components/class-grade-report.tsx` | 90 | 班级成绩报告v2-P1-4i18n |
| `components/grade-query-filters.tsx` | 96 | 查询过滤器v2-P2-7 修复Label htmlForv2-P1-4i18n**P2-22026-06-25**:补全 : JSX.Element 返回值类型) |
| `components/class-grade-report.tsx` | 130 | 班级成绩报告v2-P1-4i18n**Phase 4.6 重构2026-07-06**:内联原 GradeStatsCard 实现,使用 StatsGrid + 8 个 StatCardaverage/median/max/min/stdDev/passRate/excellentRate/count含 count=0 空态 Card |
| `components/grade-record-list.tsx` | 232 | 成绩记录列表v2-P1-4i18nP3 修复safeActionCall 包装删除操作v4-P1-7使用 ScoreCellv4-P1-10overflow-x-autov3-P3-2 新增:多选复选框 + 全选 + 批量删除工具栏 + 批量删除确认对话框;**Phase 4.6 重构2026-07-06**:行渲染(桌面表格+移动端卡片)拆分至 GradeRecordRow编辑/删除/批量删除对话框拆分至 GradeRecordDetail主组件从 523 行降至 232 行 |
| `components/grade-record-row.tsx` | 175 | **Phase 4.6 新增2026-07-06**:成绩记录行渲染子组件,承接桌面端表格行 + 移动端卡片堆叠视图,含 v3-P3-2 多选复选框 + 全选 + v4-P1-7 ScoreCell 颜色编码 |
| `components/grade-record-detail.tsx` | 215 | **Phase 4.6 新增2026-07-06**:成绩记录详情/编辑/删除对话框集合,含编辑表单、单条删除确认、批量删除确认三个对话框。导出 EditableFields 类型与 toEditableFields 工具函数 |
| `components/report-card-view.tsx` | 323 | **P3-1 新增2026-06-26**A4 打印格式成绩报告卡服务端组件。含表头(学校名+标题+学期、学生信息网格、科目成绩表格rowSpan 合并科目名/排名列)、汇总统计(均分/班排名/及格率/优秀率)、教师评语+签字区。使用 globals.css `@media print` 规则控制打印布局 |
| `components/report-card-print-button.tsx` | 106 | **P3-1 新增2026-06-26**:跳转至报告卡页面的客户端按钮,支持学年/学期筛选 |
| `components/report-card-print-action.tsx` | 48 | **P3-1 新增2026-06-26**:调用 window.print() 的客户端按钮组件,含 loading 状态和错误 toast |
@@ -1396,6 +1396,11 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
> - **grade-record-list.tsx**:编辑保存 `isSaving` useState → `useOptimistic<GradeRecordListItem[], GradeRecordListItem>` + `useTransition`。`handleEditSave` 构造 `optimisticRecord` 在 `startSaveTransition` 内 `addOptimisticRecord` + `await updateGradeRecordAction` + `router.refresh()`;两处 `records.map` 改为 `optimisticRecords.map`(桌面表格 + 移动端卡片视图)即时显示乐观记录
> - **关键决策**:成功后调用 `router.refresh()` 同步数据源,让 `useOptimistic` 回滚到最新服务端值;不为删除操作使用 useOptimistic因为删除需要服务端确认外键约束、关联记录检查等
> 架构变更2026-07-06Phase 4.6 — grades 模块组件化重构闭环):
> - **batch-grade-entry.tsx**457→376 行):拆出 `BatchGradeEntrySelectors`(试卷/班级选择器含未选态)+ `BatchGradeEntryToolbar`(保存/清除按钮+粘贴提示),容器仅保留状态编排与 Server Action 调用
> - **grade-record-list.tsx**523→232 行):拆出 `GradeRecordRow`(行渲染:桌面表格+移动端卡片)+ `GradeRecordDetail`(编辑/单条删除/批量删除对话框集合)
> - **删除 4 个重复组件**`grade-stats-card.tsx`(迁移至 `StatsGrid`,由 class-grade-report.tsx 直接使用)/ `grade-filters.tsx`(迁移至调用方 `student/grades/student-grade-filters.tsx`,复用 `FilterBar` + `FilterSearchInput`/ `grade-query-filters.tsx`(迁移至调用方 `teacher/grades/teacher-grade-query-filters.tsx`,复用 `FilterBar` + Select/ `analytics-filters.tsx`(迁移至调用方 `teacher/grades/analytics/analytics-grade-filters.tsx`,复用 `FilterBar` + `ChipNav`
---
## 2.7 classes班级模块— 耦合最严重

View File

@@ -5,8 +5,8 @@
"generatedAt": "2026-06-17",
"formatVersion": "1.1",
"rule": "每次文件修改后须同步更新本文件",
"lastUpdate": "缓存策略落地专项重构完成2026-07-05全栈缓存一致性重构,覆盖全部 28 个模块。基础设施层:新增 shared/lib/cache/ 9 个文件types/memory-store/redis-store/store-factory/cache-fn/invalidation-map/client-invalidation-map/invalidate/index+ 共享 redis-client.ts + 客户端 query-keys.ts 工厂。核心机制:(1) cacheFn 双层包装器 — 外层 react.cache 请求级 memoization内层 cacheStore.getOrSet 跨请求/跨实例缓存(按 tag 失效TTL 可选)(2) INVALIDATION_MAP 集中式失效映射表 — 扩展至 203 个 actionId每个声明 { tags, queryKeys, paths } 三段副作用;(3) invalidateFor 三步编排 — CacheStore.invalidateTags → revalidateTag × N → revalidatePath × NNext.js 16 revalidateTag 第二参数必填 \"default\"(4) CLIENT_INVALIDATION_MAP 客户端可见子集(仅 queryKeys 字段,避免拉入 server-only 依赖);(5) CACHE_DRIVER 环境变量切换 memory/redis driver复用 rate-limit 模式。迁移范围data-access 83 个文件 250+ 查询函数迁移至 cacheFn双导出 raw + 包装版本,单测可 mockactions 43 个文件 247 处 revalidatePath 替换为 invalidateForINVALIDATION_MAP 覆盖全部 28 个模块。Hook 双模式useActionQuery 传 queryKey 走 useQuery 跨页共享缓存,不传走旧 useEffect+useStateuseActionMutation 传 mutationFn+actionId 自动 invalidate旧 mutate(action) 仍可用。ESLint 强制规则no-restricted-syntax 禁止 Server Actions 中直接调用 revalidatePath/revalidateTag仅 shared/lib/cache/invalidate.ts 豁免data-access 查询函数双导出 raw 模式便于单测 mock。验证npx tsc --noEmit 零错误npm run lint 零 no-restricted-syntax 错误(仅 3 个 pre-existing react-hooks/refs 错误位于未修改文件npx vitest run src/shared/lib/cache/ 25/25 测试通过。详细文档见 004 第 1.1.6 节。",
"lastUpdated": "2026-07-05"
"lastUpdate": "Phase 4.6 grades 模块组件化重构闭环完成2026-07-06(1) 拆分 batch-grade-entry.tsx 巨型文件 — 将试卷/班级选择器(含未选态引导)拆至 batch-grade-entry-selectors.tsx、底部工具栏清除/保存/粘贴提示)拆至 batch-grade-entry-toolbar.tsx主组件从 457 行降至 374 行,容器仅负责状态编排与 Server Action 调用;(2) 拆分 grade-record-list.tsx 巨型文件 — 行渲染(桌面表格 + 移动端卡片堆叠)拆至 grade-record-row.tsx、编辑/单条删除/批量删除对话框集合拆至 grade-record-detail.tsx导出 EditableFields 类型 + toEditableFields 工具函数),主组件从 523 行降至 231 行(3) 迁移 4 个重复组件到共享底座并删除原文件 — grade-stats-card.tsx → StatsGrid + StatItem 组合class-grade-report.tsx 内联 8 个统计项、grade-filters.tsx → app 层 student-grade-filters.tsx复用 FilterBar + FilterSearchInput + Select + nuqs useQueryState、grade-query-filters.tsx → app 层 teacher-grade-query-filters.tsx复用 FilterBar + Select + useRouter/useSearchParams、analytics-filters.tsx → app 层 analytics-grade-filters.tsx复用 FilterBar + ChipNav保留原 buildHref URL 构造逻辑。同步更新StatItem/ChipNav/FilterBar/FilterSearchInput 的 usedBy 列表、GradeStats/isGradeType/isSemester 的 usedBy 列表、BatchGradeEntry 与 GradeRecordList 与 ClassGradeReport 的 purpose/props/deps、新增 4 个子组件条目BatchGradeEntrySelectors/BatchGradeEntryToolbar/GradeRecordRow/GradeRecordDetail、删除 4 个旧组件条目GradeStatsCard/GradeQueryFilters/AnalyticsFilters 及 grades/components/grade-filters 隐式条目)。验证npx tsc --noEmit 零错误npm run lint 仅 3 个 pre-existing react-hooks/refs 错误位于未修改的 use-exam-preview-tasks.tsgrep 确认旧组件名GradeStatsCard/GradeFilters/GradeQueryFilters/AnalyticsFilters无业务代码残留。前次更新缓存策略落地专项重构完成2026-07-05覆盖全部 28 个模块,新增 shared/lib/cache/ 9 个文件 + INVALIDATION_MAP 203 个 actionIddata-access 83 文件 250+ 查询函数迁移至 cacheFn 双导出模式actions 43 文件 247 处 revalidatePath 替换为 invalidateFor详细见 004 第 1.1.6 节。",
"lastUpdated": "2026-07-06"
},
"architectureOverview": {
"layers": [
@@ -1767,20 +1767,21 @@
"name": "StatItem",
"file": "components/ui/stat-item.tsx",
"props": "{ label, value, icon?, hint?, valueClassName? }",
"purpose": "紧凑统计项label+icon+value+hint用于统计面板网格P1-a 重构从 attendance-stats-card、grade-stats-card 两处重复实现抽取",
"purpose": "紧凑统计项label+icon+value+hint用于统计面板网格P1-a 重构从 attendance-stats-card、grade-stats-card 两处重复实现抽取。Phase 4.6grades 模块 grade-stats-card.tsx 已删除class-grade-report.tsx 改用 StatsGrid + StatItem 组合",
"internalDeps": [
"cn"
],
"usedBy": [
"attendance/components/attendance-stats-card.tsx",
"grades/components/grade-stats-card.tsx"
"grades/components/class-grade-report.tsx",
"shared/components/ui/stats-grid.tsx"
]
},
{
"name": "ChipNav",
"file": "components/ui/chip-nav.tsx",
"props": "{ options: ChipNavOption[], currentId, buildHref: (id) => string, size?: 'sm'|'xs', allOption?, className? }",
"purpose": "芯片导航组(通过 URL search params 切换筛选维度Link 跳转P1-b 重构从 stats-class-selector、attendance-stats-class-selector、analytics-filters 三处重复实现抽取",
"purpose": "芯片导航组(通过 URL search params 切换筛选维度Link 跳转P1-b 重构从 stats-class-selector、attendance-stats-class-selector、analytics-filters 三处重复实现抽取。Phase 4.6grades 模块 analytics-filters.tsx 已删除analytics-grade-filters.tsxapp 层)改用 FilterBar + ChipNav 组合",
"internalDeps": [
"Link",
"cn"
@@ -1788,7 +1789,7 @@
"usedBy": [
"grades/components/stats-class-selector.tsx",
"attendance/components/attendance-stats-class-selector.tsx",
"grades/components/analytics-filters.tsx"
"app/(dashboard)/teacher/grades/analytics/analytics-grade-filters.tsx"
]
},
{
@@ -1810,7 +1811,7 @@
"name": "FilterBar",
"file": "components/ui/filter-bar.tsx",
"props": "{ children, hasFilters?, onReset?, layout?: 'default'|'wrap'|'between', gapClassName?, className?, resetClassName? }",
"purpose": "筛选栏容器(统一布局壳 + Reset 按钮P3-b 重构从 exam/textbook/question/audit-log/login-log filters 五处重复布局抽取。URL 状态管理方式nuqs/router/callback由各模块自行处理",
"purpose": "筛选栏容器(统一布局壳 + Reset 按钮P3-b 重构从 exam/textbook/question/audit-log/login-log filters 五处重复布局抽取。URL 状态管理方式nuqs/router/callback由各模块自行处理。Phase 4.6grades 模块 3 个 filter 组件迁移至 app 层并复用 FilterBarstudent-grade-filters / teacher-grade-query-filters / analytics-grade-filters",
"internalDeps": [
"Button",
"cn"
@@ -1820,14 +1821,17 @@
"textbooks/components/textbook-filters.tsx",
"questions/components/question-filters.tsx",
"audit/components/audit-log-filters.tsx",
"audit/components/login-log-filters.tsx"
"audit/components/login-log-filters.tsx",
"app/(dashboard)/student/grades/student-grade-filters.tsx",
"app/(dashboard)/teacher/grades/teacher-grade-query-filters.tsx",
"app/(dashboard)/teacher/grades/analytics/analytics-grade-filters.tsx"
]
},
{
"name": "FilterSearchInput",
"file": "components/ui/filter-bar.tsx",
"props": "{ value, onChange, placeholder?, className?, inputClassName? }",
"purpose": "筛选栏搜索框(带 Search 图标的 InputP3-b 重构从 exam/textbook/question filters 三处重复搜索框抽取",
"purpose": "筛选栏搜索框(带 Search 图标的 InputP3-b 重构从 exam/textbook/question filters 三处重复搜索框抽取。Phase 4.6grades 模块 student-grade-filters 复用",
"internalDeps": [
"Input",
"Search (lucide-react)",
@@ -1836,7 +1840,8 @@
"usedBy": [
"exams/components/exam-filters.tsx",
"textbooks/components/textbook-filters.tsx",
"questions/components/question-filters.tsx"
"questions/components/question-filters.tsx",
"app/(dashboard)/student/grades/student-grade-filters.tsx"
]
},
{
@@ -14095,7 +14100,7 @@
"definition": "{ count, average, median, stdDev, passRate, excellentRate, maxScore, minScore }",
"usedBy": [
"data-access",
"components/grade-stats-card"
"components/class-grade-report"
]
},
{
@@ -14440,20 +14445,20 @@
"name": "isGradeType",
"signature": "(v: unknown) => v is GradeRecordType",
"file": "lib/type-guards.ts",
"purpose": "类型守卫:验证 unknown 值是否为合法的 GradeRecordType。P2-9 修复:使用 GRADE_TYPES.some((t) => t === v) 替代 (GRADE_TYPES as readonly string[]).includes(v),消除 as 断言",
"purpose": "类型守卫:验证 unknown 值是否为合法的 GradeRecordType。P2-9 修复:使用 GRADE_TYPES.some((t) => t === v) 替代 (GRADE_TYPES as readonly string[]).includes(v),消除 as 断言。Phase 4.6:原调用方 grade-filters/grade-query-filters 已删除,改由 app 层 student-grade-filters / teacher-grade-query-filters 复用",
"usedBy": [
"grades/components/grade-filters",
"grades/components/grade-query-filters"
"app/(dashboard)/student/grades/student-grade-filters",
"app/(dashboard)/teacher/grades/teacher-grade-query-filters"
]
},
{
"name": "isSemester",
"signature": "(v: unknown) => v is GradeRecordSemester",
"file": "lib/type-guards.ts",
"purpose": "类型守卫:验证 unknown 值是否为合法的 GradeRecordSemester。P2-9 修复:使用 SEMESTERS.some((s) => s === v) 替代 (SEMESTERS as readonly string[]).includes(v),消除 as 断言",
"purpose": "类型守卫:验证 unknown 值是否为合法的 GradeRecordSemester。P2-9 修复:使用 SEMESTERS.some((s) => s === v) 替代 (SEMESTERS as readonly string[]).includes(v),消除 as 断言。Phase 4.6:原调用方 grade-filters/grade-query-filters 已删除,改由 app 层 student-grade-filters / teacher-grade-query-filters 复用",
"usedBy": [
"grades/components/grade-filters",
"grades/components/grade-query-filters"
"app/(dashboard)/student/grades/student-grade-filters",
"app/(dashboard)/teacher/grades/teacher-grade-query-filters"
]
},
{
@@ -14550,7 +14555,7 @@
{
"name": "BatchGradeEntry",
"file": "components/batch-grade-entry.tsx",
"purpose": "2026-06-24 重新设计:导出名改为 BatchGradeEntryByExam按试卷录入每题得分。Excel 式表格(行=学生,列=题目,末列=总分自动计算)。交互:试卷选择器(按 scope 过滤)→ 班级选择器(按试卷 gradeId 过滤);多行多列 Excel 粘贴Tab 分隔Enter 跳下一行同一列Tab 跳下一格;实时统计(已录入/总数/均分/最高/最低分数校验超过题目满分标红撤销机制sessionStorage 5 分钟有效。P1-6/P1-7 重构:拆分为 3 个子组件 + 1 个自定义 HookBatchGradeEntryTable + BatchGradeEntryStats + BatchGradeEntryDialog + useBatchGradeEntryUndo主组件从 604 行降至 422 行。撤销数据使用 isUndoData 类型守卫替代 as 断言。Props: exams/classes/classGradeMap/exam/students/defaultExamId?/defaultClassId?",
"purpose": "2026-06-24 重新设计:导出名改为 BatchGradeEntryByExam按试卷录入每题得分。Excel 式表格(行=学生,列=题目,末列=总分自动计算)。交互:试卷选择器(按 scope 过滤)→ 班级选择器(按试卷 gradeId 过滤);多行多列 Excel 粘贴Tab 分隔Enter 跳下一行同一列Tab 跳下一格;实时统计(已录入/总数/均分/最高/最低分数校验超过题目满分标红撤销机制sessionStorage 5 分钟有效。P1-6/P1-7 重构:拆分为 3 个子组件 + 1 个自定义 HookBatchGradeEntryTable + BatchGradeEntryStats + BatchGradeEntryDialog + useBatchGradeEntryUndo主组件从 604 行降至 422 行。撤销数据使用 isUndoData 类型守卫替代 as 断言。Phase 4.6 重构:进一步拆分试卷/班级选择器(含未选态引导)至 BatchGradeEntrySelectors、底部工具栏清除/保存/粘贴提示)至 BatchGradeEntryToolbar主组件降至 374 行,容器仅负责状态编排与 Server Action 调用。Props: exams/classes/classGradeMap/exam/students/defaultExamId?/defaultClassId?",
"props": "{ exams: ExamOptionForEntry[]; classes: ClassOption[]; classGradeMap: Record<string, string>; exam: ExamForGradeEntry | null; students: Student[]; defaultExamId?: string; defaultClassId?: string }",
"deps": [
"exams/types.ExamOptionForEntry",
@@ -14560,6 +14565,8 @@
"grades/components/batch-grade-entry-table.BatchGradeEntryTable",
"grades/components/batch-grade-entry-stats.BatchGradeEntryStats",
"grades/components/batch-grade-entry-dialog.BatchGradeEntryDialog",
"grades/components/batch-grade-entry-selectors.BatchGradeEntrySelectors",
"grades/components/batch-grade-entry-toolbar.BatchGradeEntryToolbar",
"grades/hooks/use-batch-grade-entry-undo.useBatchGradeEntryUndo",
"shared/lib/action-utils.safeActionCall",
"shared/components/ui/card",
@@ -14606,20 +14613,82 @@
"grades/components/batch-grade-entry"
]
},
{
"name": "BatchGradeEntrySelectors",
"file": "components/batch-grade-entry-selectors.tsx",
"purpose": "Phase 4.6 新增:批量录入试卷/班级选择器子组件。未选试卷时显示单选试卷 Card + Info 引导;已选试卷时显示双列选择器(试卷 Select + 班级 Select+ 试卷信息 Badge题目数/总分/满分)",
"props": "{ exams, classes, exam, defaultExamId?, defaultClassId?, onExamChange, onClassChange }",
"deps": [
"shared/components/ui/card",
"shared/components/ui/select",
"shared/components/ui/badge"
],
"usedBy": [
"grades/components/batch-grade-entry"
]
},
{
"name": "BatchGradeEntryToolbar",
"file": "components/batch-grade-entry-toolbar.tsx",
"purpose": "Phase 4.6 新增:批量录入底部工具栏子组件(清除按钮 + 保存按钮 + Excel 粘贴提示)。保存按钮在 hasInvalidScores 时禁用",
"props": "{ isSubmitting, hasInvalidScores, onClear, onSubmit }",
"deps": [
"shared/components/ui/button"
],
"usedBy": [
"grades/components/batch-grade-entry"
]
},
{
"name": "GradeRecordList",
"file": "components/grade-record-list.tsx",
"purpose": "成绩列表(含查询筛选、删除对话框)"
"purpose": "成绩记录列表容器。Phase 4.6 重构:行渲染(桌面表格 + 移动端卡片堆叠)拆分至 GradeRecordRow编辑/单条删除/批量删除对话框拆分至 GradeRecordDetail主组件从 523 行降至 231 行容器仅负责状态编排useOptimistic + useTransition与 Server Action 调用。Phase 4.5:编辑保存改用 useOptimistic + useTransition在列表中即时显示乐观记录。v3-P3-2批量选择与删除",
"props": "{ records: GradeRecordListItem[] }",
"deps": [
"grades/actions.deleteGradeRecordAction",
"grades/actions.updateGradeRecordAction",
"grades/actions.bulkDeleteGradeRecordsAction",
"grades/components/grade-record-row.GradeRecordRow",
"grades/components/grade-record-detail.GradeRecordDetail",
"grades/components/grade-record-detail.toEditableFields",
"shared/lib/action-utils.safeActionCall",
"shared/components/ui/button"
],
"usedBy": [
"teacher/grades/page.tsx"
]
},
{
"name": "GradeStatsCard",
"file": "components/grade-stats-card.tsx",
"purpose": "统计卡片(均分、中位数、标准差、及格率、优秀率、最高分、最低分)"
"name": "GradeRecordRow",
"file": "components/grade-record-row.tsx",
"purpose": "Phase 4.6 新增:成绩记录行渲染子组件。桌面端 Table 行(含复选框 + 学生/科目/考试/分数/满分/类型/学期/备注/操作)+ 移动端卡片堆叠视图role=list/listitem。复选框支持单行切换与全选切换。操作按钮触发编辑与单条删除回调",
"props": "{ records, selectedIds, onToggleRow, onToggleSelectAll, onEdit, onDelete }",
"deps": [
"shared/components/ui/table",
"shared/components/ui/checkbox",
"shared/components/ui/button",
"shared/components/ui/status-badge"
],
"usedBy": [
"grades/components/grade-record-list"
]
},
{
"name": "GradeQueryFilters",
"file": "components/grade-query-filters.tsx",
"purpose": "查询筛选器(班级、科目、考试类型、学期)"
"name": "GradeRecordDetail",
"file": "components/grade-record-detail.tsx",
"purpose": "Phase 4.6 新增成绩记录详情对话框集合子组件包含编辑对话框title/score/fullScore/type/semester/remark 表单)、单条删除确认对话框、批量删除确认对话框。导出 EditableFields 类型 + toEditableFields(r: GradeRecordListItem): EditableFields 工具函数,用于将列表项转换为表单可编辑字段",
"props": "{ editTarget, deleteId, isBulkDeleteDialogOpen, isSaving, isDeleting, isBulkDeleting, bulkDeleteCount, onEditTargetChange, onCancelEdit, onSaveEdit, onCancelDelete, onConfirmDelete, onCloseBulkDelete, onConfirmBulkDelete }",
"deps": [
"shared/components/ui/dialog",
"shared/components/ui/button",
"shared/components/ui/label",
"shared/components/ui/input",
"shared/components/ui/select",
"shared/components/ui/alert-dialog"
],
"usedBy": [
"grades/components/grade-record-list"
]
},
{
"name": "StudentGradeSummary",
@@ -14629,7 +14698,7 @@
{
"name": "ClassGradeReport",
"file": "components/class-grade-report.tsx",
"purpose": "班级成绩报表(含统计+排名)"
"purpose": "班级成绩报表(含统计+排名)。Phase 4.6 重构:删除 GradeStatsCard 依赖,改用 StatsGrid + StatItem 组合内联渲染 8 个统计项average/median/max/min/stdDev/passRate/excellentRate/countcount=0 时显示空态 Card"
},
{
"name": "ExportButton",
@@ -14678,20 +14747,6 @@
"shared/components/ui/chart"
]
},
{
"name": "AnalyticsFilters",
"file": "components/analytics-filters.tsx",
"purpose": "成绩分析页筛选器(班级、科目、年级 Link 筛选按钮组,含 focus-visible 焦点样式v3-P2 新增exams/currentExamId/currentSemester props添加学期和考试筛选 ChipNav",
"props": "{ classId?, gradeId?, subjectId?, currentSemester?, currentExamId?, exams: { id, title }[] }",
"deps": [
"next/link",
"shared/lib/utils.cn",
"shared/components/ui/chip-nav"
],
"usedBy": [
"teacher/grades/analytics/page.tsx"
]
},
{
"name": "StatsClassSelector",
"file": "components/stats-class-selector.tsx",