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:
@@ -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 修复:a11y;v2-P1-4:i18n;P3 修复:NaN 日期检查 + fullScore > 0 守卫) |
|
||||
| `components/grade-record-list.tsx` | 200+ | 成绩记录列表(v2-P1-4:i18n;P3 修复:safeActionCall 包装删除操作;v4-P1-7:使用 ScoreCell;v4-P1-10:overflow-x-auto;v3-P3-2 新增:多选复选框 + 全选 + 批量删除工具栏 + 批量删除确认对话框,接入 bulkDeleteGradeRecordsAction) |
|
||||
| `components/grade-distribution-chart.tsx` | 100 | 分数分布图(v2-P1-4:i18n) |
|
||||
| `components/subject-comparison-chart.tsx` | 62 | 科目对比图(v2-P1-4:i18n) |
|
||||
| `components/class-comparison-chart.tsx` | 194 | 班级对比图(v2-P1-4:i18n;v3-P3-5 新增:显著性分析区域,基于极差和样本量的经验规则判断班级间差异,含可折叠详细分析) |
|
||||
| `components/grade-trend-chart.tsx` | 59 | 趋势图(v2-P1-4:i18n) |
|
||||
| `components/grade-record-form.tsx` | 177 | 录入表单(v2-P2-7 修复:Label htmlFor;v2-P1-4:i18n;P3 修复: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 个 Hook(BatchGradeEntryTable + 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 htmlFor;v2-P1-4 i18n;P3 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 个 Hook(BatchGradeEntryTable + 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-4:i18n;**P2-2(2026-06-25)**:补全 : JSX.Element 返回值类型) |
|
||||
| `components/student-grade-summary.tsx` | 107 | 学生成绩摘要(v2-P1-4:i18n) |
|
||||
| `components/export-button.tsx` | 79 | 导出按钮(v2-P1-4:i18n;P3 修复:safeActionCall 包装导出操作;**P2-2(2026-06-25)**:handleExport 补全 : Promise<void> 返回值类型) |
|
||||
| `components/analytics-filters.tsx` | 86+ | 分析过滤器(v2-P1-4:i18n;v3-P2 新增:exams/currentExamId/currentSemester props,添加学期和考试筛选 ChipNav) |
|
||||
| `components/stats-class-selector.tsx` | 40 | 统计班级选择器(v2-P1-4:i18n) |
|
||||
| `components/grade-stats-card.tsx` | 74 | 统计卡片(v2-P1-4:i18n) |
|
||||
| `components/class-grade-report.tsx` | 90 | 班级成绩报告(v2-P1-4:i18n) |
|
||||
| `components/grade-query-filters.tsx` | 96 | 查询过滤器(v2-P2-7 修复:Label htmlFor;v2-P1-4:i18n;**P2-2(2026-06-25)**:补全 : JSX.Element 返回值类型) |
|
||||
| `components/class-grade-report.tsx` | 130 | 班级成绩报告(v2-P1-4:i18n;**Phase 4.6 重构(2026-07-06)**:内联原 GradeStatsCard 实现,使用 StatsGrid + 8 个 StatCard(average/median/max/min/stdDev/passRate/excellentRate/count),含 count=0 空态 Card) |
|
||||
| `components/grade-record-list.tsx` | 232 | 成绩记录列表(v2-P1-4:i18n;P3 修复:safeActionCall 包装删除操作;v4-P1-7:使用 ScoreCell;v4-P1-10:overflow-x-auto;v3-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-06,Phase 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(班级模块)— 耦合最严重
|
||||
|
||||
@@ -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 × N(Next.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 + 包装版本,单测可 mock);actions 43 个文件 247 处 revalidatePath 替换为 invalidateFor;INVALIDATION_MAP 覆盖全部 28 个模块。Hook 双模式:useActionQuery 传 queryKey 走 useQuery 跨页共享缓存,不传走旧 useEffect+useState;useActionMutation 传 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.ts);grep 确认旧组件名(GradeStatsCard/GradeFilters/GradeQueryFilters/AnalyticsFilters)无业务代码残留。前次更新:缓存策略落地专项重构完成(2026-07-05),覆盖全部 28 个模块,新增 shared/lib/cache/ 9 个文件 + INVALIDATION_MAP 203 个 actionId,data-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.6:grades 模块 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.6:grades 模块 analytics-filters.tsx 已删除,analytics-grade-filters.tsx(app 层)改用 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.6:grades 模块 3 个 filter 组件迁移至 app 层并复用 FilterBar(student-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 图标的 Input),P3-b 重构从 exam/textbook/question filters 三处重复搜索框抽取",
|
||||
"purpose": "筛选栏搜索框(带 Search 图标的 Input),P3-b 重构从 exam/textbook/question filters 三处重复搜索框抽取。Phase 4.6:grades 模块 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 个自定义 Hook(BatchGradeEntryTable + 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 个自定义 Hook(BatchGradeEntryTable + 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/count),count=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",
|
||||
|
||||
Reference in New Issue
Block a user