Files
NextEdu/bugs/admin_bug_v2.md
SpecialX 978d9a8309
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
feat: 新增备课模块并修复全模块 P0/P1/P2 缺陷
主要变更:

- 新增 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)
2026-06-22 01:06:16 +08:00

533 lines
27 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Admin 前端文件规范核查报告 v2
> 版本v2基于 v1 报告的二次复查)
> 核查范围:`src/app/(dashboard)/admin/` 下全部 26 个 `page.tsx` 文件
> 核查依据:
> - `.trae/rules/project_rules.md`(项目规则)
> - `docs/standards/coding-standards.md`(编码规范 v1.0
> - `docs/architecture/004_architecture_impact_map.md`(架构影响地图)
> - React / Next.js 16 最佳实践
> - Web 界面设计规范WCAG 2.2 AA
> 核查日期2026-06-18v2
> 上次核查2026-06-18v1
---
## 、v1 → v2 修复状态追踪
**重要说明**:本次复查发现,自 v1 报告(`bugs/admin_bug.md`)输出后,`src/app/(dashboard)/admin/` 下全部 26 个 `page.tsx` 文件**内容均未发生任何修改**`src/shared/lib/utils.ts` 也未新增共享工具函数。v1 报告提出的所有问题**全部未修复**。
### v1 问题修复状态对照表
| v1 编号 | 问题 | 严重级别 | v2 状态 | 备注 |
|---------|------|---------|---------|------|
| P0-1 | 全部 26 个页面缺少 `error.tsx` / `loading.tsx` | P0 | ❌ 未修复 | 仍无任何 error/loading 边界文件 |
| P0-2 | `attendance/page.tsx` 缺少权限校验 | P0 | ❌ 未修复 | 第 26 行仍为 `getAuthContext()`,未加 `requirePermission` |
| P1-1 | 全部 26 个页面组件缺少返回类型标注 | P1 | ❌ 未修复 | 全部页面函数仍无 `: Promise<JSX.Element>` |
| P1-2 | `getParam` 工具函数在 27 个文件中重复 | P1 | ❌ 未修复 | `shared/lib/utils.ts` 未新增 `getSearchParam` |
| P1-3 | 4 个文件使用 `as` 类型断言 | P1 | ❌ 未修复 | `audit-logs/*``attendance` 仍用 `as` |
| P1-4 | UI 文案中英文混用 | P1 | ❌ 未修复 | 仅 `users/import` 为中文,其余仍英文 |
| P2-1 | `school/grades/insights` 使用原生 `<select>` | P2 | ❌ 未修复 | 第 57-68 行仍为原生 `<select>` |
| P2-2 | `users/import` 使用原生 `<table>` | P2 | ❌ 未修复 | 第 93-128 行仍为原生 `<table>` |
| P2-3 | Tailwind 任意值违规 | P2 | ❌ 未修复 | `md:w-[360px]``h-[360px]` 仍存在 |
| P2-4 | `users/import` 硬编码颜色 `text-amber-500` | P2 | ❌ 未修复 | 第 67 行未变 |
| P2-5 | `school/grades/insights` 导入顺序违规 | P2 | ❌ 未修复 | `lucide-react` 仍在最后 |
| P2-6 | `course-plans/[id]/edit` 同模块重复导入 | P2 | ❌ 未修复 | 第 3-4 行仍分两行 |
| P2-7 | `scheduling/*``actions` 取数 | P2 | ❌ 未修复 | 仍从 `@/modules/scheduling/actions` 导入 |
**结论**v1 提出的 **2 个 P0 + 4 个 P1 + 7 个 P2 = 13 个问题0 个已修复**
---
## 一、v2 新增发现v1 遗漏的问题)
本次复查在 v1 基础上深度审查,新发现 **10 个问题**
### P1 重要问题v2 新增)
#### P1-5v2 新增)`attendance/page.tsx` 第 39 行违反 Prettier `printWidth: 100`
**文件**[src/app/(dashboard)/admin/attendance/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/attendance/page.tsx#L39)
**违反规范**
- `.prettierrc` 配置 `"printWidth": 100`
- 编码规范 §十五「Prettier 自动保证格式一致」
**现状**:第 39 行单行长度约 115 字符,超出 100 字符限制:
```tsx
status: status && status !== "all" ? (status as "present" | "absent" | "late" | "early_leave" | "excused") : undefined,
```
**说明**:项目 `.prettierrc` 已配置 `printWidth: 100`,但此行未触发格式化,可能是因为该文件未经过 `prettier --write` 处理,或 ESLint 未强制 Prettier 规则。
**修复建议**:抽取状态类型守卫后自然换行(同时解决 P1-3 的 `as` 断言问题):
```tsx
const isValidAttendanceStatus = (v?: string): v is AttendanceStatus =>
v === "present" || v === "absent" || v === "late" || v === "early_leave" || v === "excused"
// 在组件内
status: status && status !== "all" && isValidAttendanceStatus(status) ? status : undefined,
```
---
#### P1-6v2 新增)`school/grades/insights/page.tsx` 的 `getParam` 实现与其他文件不一致
**文件**[src/app/(dashboard)/admin/school/grades/insights/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx#L17-L22)
**现状**:该文件的 `getParam` 实现与其他 8 个 admin 页面**逻辑等价但写法不同**
```tsx
// school/grades/insights/page.tsx第 17-22 行)—— 三分支写法
const getParam = (params: SearchParams, key: string) => {
const v = params[key]
if (typeof v === "string") return v
if (Array.isArray(v)) return v[0]
return undefined
}
// 其他 8 个 admin 页面 —— 三元写法
const getParam = (params: SearchParams, key: string) => {
const v = params[key]
return Array.isArray(v) ? v[0] : v
}
```
**影响**:加剧 P1-2 的 DRY 问题,两种实现并存增加维护成本,且 `v[0]``noUncheckedIndexedAccess` 开启后返回 `string | undefined`,两种写法的类型推导行为可能不同。
**修复建议**:与 P1-2 一并解决,抽取到 `shared/lib/utils.ts` 统一实现。
---
#### P1-7v2 新增)`attendance/page.tsx` 第 39 行使用内联字面量类型而非 `AttendanceStatus` 类型
**文件**[src/app/(dashboard)/admin/attendance/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/attendance/page.tsx#L39)
**违反规范**
- 编码规范 §4.2:「优先 `interface` 描述对象形状,`type` 用于联合、交叉、映射类型」
- DRY 原则
**现状**:第 39 行内联了 5 个字面量类型,而非引用 `AttendanceStatus` 类型:
```tsx
status as "present" | "absent" | "late" | "early_leave" | "excused"
```
**说明**`@/modules/attendance/types` 应已定义 `AttendanceStatus` 类型(其他模块如 `announcements``scheduling``course-plans``elective` 均有对应 status 类型导出)。内联字面量导致类型定义重复,若枚举值变更需多处修改。
**修复建议**
```tsx
import type { AttendanceStatus } from "@/modules/attendance/types"
const isValidAttendanceStatus = (v?: string): v is AttendanceStatus =>
v === "present" || v === "absent" || v === "late" || v === "early_leave" || v === "excused"
```
---
### P2 一般问题v2 新增)
#### P2-8v2 新增)`school/grades/insights/page.tsx` 第 24 行 `fmt` 工具函数内联定义
**文件**[src/app/(dashboard)/admin/school/grades/insights/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx#L24)
**违反规范**
- 编码规范 §一:「单一职责」
- 编码规范 §5.3:「工具函数 ≤ 40 行」(此函数 1 行,但属于通用工具应抽取)
**现状**:第 24 行内联定义数字格式化函数:
```tsx
const fmt = (v: number | null, digits = 1) => (typeof v === "number" && Number.isFinite(v) ? v.toFixed(digits) : "-")
```
**影响**:该函数为通用数字格式化工具,可能在其他统计页面(如 `teacher/grades/stats``management/grade/insights`)重复出现。
**修复建议**:抽取到 `shared/lib/utils.ts`
```tsx
export function formatNumber(v: number | null | undefined, digits = 1): string {
if (typeof v !== "number" || !Number.isFinite(v)) return "-"
return v.toFixed(digits)
}
```
---
#### P2-9v2 新增)`school/grades/insights/page.tsx` 第 137 行可用可选链简化
**文件**[src/app/(dashboard)/admin/school/grades/insights/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx#L137)
**现状**:第 137 行使用三元表达式而非可选链:
```tsx
<div className="text-xs text-muted-foreground">{insights.latest ? insights.latest.title : "-"}</div>
```
**修复建议**:使用可选链 + 空值合并:
```tsx
<div className="text-xs text-muted-foreground">{insights.latest?.title ?? "-"}</div>
```
**说明**:同文件第 136 行已使用 `insights.latest?.scoreStats.avg ?? null`,写法不一致。
---
#### P2-10v2 新增)`school/page.tsx` 缺少 `export const dynamic` 声明
**文件**[src/app/(dashboard)/admin/school/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/page.tsx)
**现状**:该文件仅 5 行,使用 `redirect()` 跳转,但**未声明** `export const dynamic = "force-dynamic"`
```tsx
import { redirect } from "next/navigation"
export default function AdminSchoolPage() {
redirect("/admin/school/classes")
}
```
**对比**admin 目录下其他 25 个页面均声明了 `export const dynamic = "force-dynamic"`,仅此文件缺失。
**影响**Next.js 可能在构建时尝试静态生成此页面,`redirect()` 在静态生成阶段的行为与运行时不同,可能导致构建警告或行为不一致。
**修复建议**:补充声明:
```tsx
import { redirect } from "next/navigation"
export const dynamic = "force-dynamic"
export default function AdminSchoolPage(): never {
redirect("/admin/school/classes")
}
```
**注**`redirect()` 抛出异常永不返回,返回类型应标注为 `never`
---
#### P2-11v2 新增)`users/import/page.tsx` 是同步函数但无 `dynamic` 导出,与其他页面不一致
**文件**[src/app/(dashboard)/admin/users/import/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/users/import/page.tsx#L14)
**现状**:第 14 行为同步函数组件,且无 `export const dynamic` 声明:
```tsx
export default function UserImportPage() {
return ( /* ... */ )
}
```
**对比**admin 目录下其他 24 个数据获取页面均声明 `export const dynamic = "force-dynamic"`,仅此文件与 `school/page.tsx` 缺失。
**说明**:该页面为纯静态内容(无数据获取),理论上可静态生成,但与 admin 路由组整体策略不一致。需明确决策:
- 若 admin 路由组统一 `force-dynamic`(因权限校验需运行时),则此页面应补充声明
- 若允许静态页面,则应在架构文档中说明例外
**修复建议**:为保持一致性,补充 `export const dynamic = "force-dynamic"`,或显式注释说明为何例外。
---
#### P2-12v2 新增)多个编辑页缺少返回上一页的导航
**违反规范**
- Web 界面设计规范:「焦点管理必须合理」
- 用户体验最佳实践:「始终提供返回路径」
**现状**:以下编辑/创建页面**未提供返回按钮**,用户只能通过浏览器后退或侧边栏导航:
| 文件 | 是否有返回按钮 |
|------|--------------|
| `announcements/[id]/page.tsx` | ❌ 无 |
| `course-plans/create/page.tsx` | ❌ 无(仅 `CoursePlanForm``backHref` prop |
| `course-plans/[id]/page.tsx` | ❌ 无(仅 `CoursePlanDetail``backHref` prop |
| `course-plans/[id]/edit/page.tsx` | ❌ 无(仅 `CoursePlanForm``backHref` prop |
| `elective/create/page.tsx` | ❌ 无(仅 `ElectiveCourseForm``backHref` prop |
| `elective/[id]/edit/page.tsx` | ❌ 无(仅 `ElectiveCourseForm``backHref` prop |
| `users/import/page.tsx` | ✅ 有(第 20-25 行 `ArrowLeft` 返回按钮) |
**说明**`users/import/page.tsx` 在页面顶部提供了显式的返回按钮(`<Button asChild variant="ghost"><Link href="/admin/dashboard"><ArrowLeft /> 返回</Link></Button>`),是正确的做法。其他编辑页虽通过子组件的 `backHref` prop 传递了返回路径,但返回入口依赖子组件内部实现,页面层未统一控制。
**修复建议**:在所有编辑/创建页面顶部统一添加返回按钮,与 `users/import/page.tsx` 保持一致;或将返回按钮抽取为共享组件 `PageBackButton`
---
#### P2-13v2 新增)大部分页面缺少 `metadata` 导出
**违反规范**
- Next.js 16 最佳实践:「页面应导出 `metadata` 用于 SEO 与标签页标题」
- 编码规范 §十四:「文档与交付物」
**现状**
| 文件 | 是否导出 `metadata` |
|------|-------------------|
| `users/import/page.tsx` | ✅ 有(第 9-12 行) |
| 其余 25 个页面 | ❌ 无 |
**影响**:浏览器标签页标题默认显示全局标题,无法区分当前所在 admin 子页面,影响用户体验(多个标签页难以区分)。
**修复建议**:为每个页面补充 `metadata` 导出:
```tsx
import type { Metadata } from "next"
export const metadata: Metadata = {
title: "审计日志 - Next_Edu",
description: "查看系统所有用户操作记录",
}
```
---
#### P2-14v2 新增)`school/grades/insights/page.tsx` 使用原生 `<form method="get">` 导致整页刷新
**文件**[src/app/(dashboard)/admin/school/grades/insights/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx#L55-L72)
**现状**:第 55-72 行使用原生 HTML `<form action="/admin/school/grades/insights" method="get">` 提交筛选器,会导致**整页刷新**,丢失当前滚动位置与页面状态。
**违反规范**
- 编码规范 §7.3「URL 状态:使用 `nuqs`(已集成)」
- React 最佳实践:「避免不必要的整页刷新」
**影响**
- 用户体验差:每次筛选都触发整页白屏加载(叠加 P0-1 缺少 `loading.tsx` 问题更严重)
- 与项目已集成的 `nuqs` URL 状态管理方案不一致
- 其他筛选页(`audit-logs/*``attendance`)使用子组件内的客户端筛选,此页面是唯一使用原生 form 提交的
**修复建议**
1. **方案 A推荐**:将筛选器提取为客户端组件,使用 `nuqs``useQueryState` 管理 `gradeId` 参数,实现无刷新筛选
2. **方案 B最小改动**:保持服务端筛选,但补充 `loading.tsx` 缓解白屏问题
---
## 二、v2 核查概览(含 v1 + v2 全部问题)
| 维度 | 文件数 | 通过 | 待改进 | v2 新增 |
|------|--------|------|--------|---------|
| 架构分层 | 26 | 24 | 2 | 0 |
| TypeScript 规范 | 26 | 4 | 22 | +3 |
| 安全与权限 | 26 | 3 | 23 | 0 |
| UI 一致性与设计令牌 | 26 | 18 | 8 | +1 |
| 错误与加载边界 | 26 | 0 | 26 | 0 |
| 代码复用DRY | 26 | 0 | 26 | +2 |
| 格式化Prettier | 26 | 25 | 1 | +1 |
| 导航与 UX | 26 | 1 | 25 | +2 |
| SEOmetadata | 26 | 1 | 25 | +1 |
**累计问题数**v1 的 13 个 + v2 新增 10 个 = **23 个问题**,全部未修复。
---
## 三、v2 问题清单汇总(按严重程度排序)
### P0 严重(必须立即修复)
| 编号 | 问题 | v1/v2 | 文件 |
|------|------|-------|------|
| P0-1 | 全部 26 个页面缺少 `error.tsx` / `loading.tsx` | v1 | 全部 |
| P0-2 | `attendance/page.tsx` 缺少 `requirePermission` 权限校验 | v1 | `attendance/page.tsx` |
### P1 重要(应尽快修复)
| 编号 | 问题 | v1/v2 | 文件 |
|------|------|-------|------|
| P1-1 | 全部 26 个页面缺少返回类型 `Promise<JSX.Element>` | v1 | 全部 |
| P1-2 | `getParam` 在 27 个文件重复定义 | v1 | 9 个 admin 文件 |
| P1-3 | 4 个文件使用 `as` 类型断言 | v1 | `audit-logs/*``attendance` |
| P1-4 | UI 文案中英文混用 | v1 | ~20 个文件 |
| P1-5 | `attendance` 第 39 行超 `printWidth: 100` | **v2** | `attendance/page.tsx` |
| P1-6 | `school/grades/insights``getParam` 实现不一致 | **v2** | `school/grades/insights/page.tsx` |
| P1-7 | `attendance` 使用内联字面量而非 `AttendanceStatus` 类型 | **v2** | `attendance/page.tsx` |
### P2 一般(建议修复)
| 编号 | 问题 | v1/v2 | 文件 |
|------|------|-------|------|
| P2-1 | `school/grades/insights` 使用原生 `<select>` | v1 | `school/grades/insights/page.tsx` |
| P2-2 | `users/import` 使用原生 `<table>` | v1 | `users/import/page.tsx` |
| P2-3 | Tailwind 任意值 `w-[360px]``h-[360px]` | v1 | `school/grades/insights/page.tsx` |
| P2-4 | `users/import` 硬编码颜色 `text-amber-500` | v1 | `users/import/page.tsx` |
| P2-5 | `school/grades/insights` 导入顺序违规 | v1 | `school/grades/insights/page.tsx` |
| P2-6 | `course-plans/[id]/edit` 同模块重复导入 | v1 | `course-plans/[id]/edit/page.tsx` |
| P2-7 | `scheduling/*``actions` 取数 | v1 | `scheduling/*` |
| P2-8 | `fmt` 工具函数内联定义 | **v2** | `school/grades/insights/page.tsx` |
| P2-9 | 第 137 行可用可选链简化 | **v2** | `school/grades/insights/page.tsx` |
| P2-10 | `school/page.tsx` 缺少 `export const dynamic` | **v2** | `school/page.tsx` |
| P2-11 | `users/import` 缺少 `dynamic` 声明(不一致) | **v2** | `users/import/page.tsx` |
| P2-12 | 多个编辑页缺少返回按钮 | **v2** | 6 个编辑/创建页 |
| P2-13 | 25 个页面缺少 `metadata` 导出 | **v2** | 25 个文件 |
| P2-14 | 原生 `<form method="get">` 整页刷新 | **v2** | `school/grades/insights/page.tsx` |
---
## 四、React 性能优化建议v2 更新)
### R1 利用 Suspense 流式渲染v1 提出,未实施)
**现状**:所有页面使用 `export const dynamic = "force-dynamic"` 整页动态渲染。
**建议**:对数据量大的页面(`audit-logs/*``school/grades/insights``attendance`)拆分 Suspense 边界。详见 v1 报告 R1。
### R2 `school/grades/insights/page.tsx` 串行查询可并行v1 提出,未实施)
**现状**:第 30-33 行 `getGrades()``getGradeHomeworkInsights()` 串行执行,但两者无数据依赖。
**建议**:改为 `Promise.all` 并行。详见 v1 报告 R2。
### R3 列表页 `classOptions` 映射可下沉至 data-accessv1 提出,未实施)
详见 v1 报告 R3。
### R4v2 新增)`school/grades/insights/page.tsx` 表格未虚拟化,大数据量下性能风险
**文件**[src/app/(dashboard)/admin/school/grades/insights/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx#L164-L180)
**现状**:第 164-180 行与第 208-220 行使用 `insights.assignments.map()``insights.classes.map()` 直接渲染整张表格,无分页或虚拟化。
**说明**`getGradeHomeworkInsights({ limit: 50 })` 限制为 50 条,但 `insights.classes` 无限制,大型学校(如 50+ 班级的年级)可能渲染数百行 DOM 节点。
**修复建议**
- 短期:在 data-access 层对 `classes` 也加 `limit`
- 长期:引入 `@tanstack/react-virtual` 虚拟化长列表
---
## 五、Web 界面设计规范建议v2 更新)
### W1-W5v1 提出,未实施)
详见 v1 报告第四部分:`<label>` 关联、表格 `<caption>`、标题层级、`aria-live``EmptyState` 图标语义。
### W6v2 新增)`school/grades/insights/page.tsx` 原生 `<select>` 缺少 ARIA 属性
**文件**[src/app/(dashboard)/admin/school/grades/insights/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx#L57-L68)
**现状**:第 57-68 行原生 `<select>` 缺少 `aria-label``aria-labelledby`,且 `<label>` 未通过 `htmlFor` 关联v1 W1 已记录)。
**违反**WCAG 2.2 SC 4.1.2(名称、角色、值)。
**补充建议**:除 v1 建议的 `htmlFor`/`id` 关联外,建议直接替换为 shadcn `Select` 组件P2-1该组件已内置 ARIA 支持。
### W7v2 新增)`attendance/page.tsx` 筛选器无 `aria-live` 反馈
**文件**[src/app/(dashboard)/admin/attendance/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/attendance/page.tsx#L58-L68)
**现状**`AttendanceFilters`(客户端组件)提交后,`AttendanceRecordList` 数据刷新,但屏幕阅读器用户无法感知。
**说明**:此问题与 v1 W4 相同,但 v1 仅提及 `school/grades/insights``attendance``audit-logs/*`,未明确 `attendance` 的具体位置。
**修复建议**:在 `AttendanceRecordList` 容器添加 `aria-live="polite"`,或使用 `useAriaLive` Hook 通知「已加载 N 条记录」。
---
## 六、优秀实践(已符合规范,应保持)
> 与 v1 报告第五部分一致,本次复查确认以下优秀实践仍然成立:
1. **服务端组件默认化**:全部 26 个页面均为 async 服务端组件,未滥用 `"use client"`
2. **并行数据获取**:多个页面使用 `Promise.all` 并行查询。
3. **类型守卫正确使用**`announcements``scheduling/changes``course-plans``elective` 使用 `isValidStatus` 类型守卫。
4. **404 处理**:动态路由页面使用 `notFound()`
5. **权限校验到位**`audit-logs/*``files/page.tsx` 正确调用 `requirePermission()`
6. **模块化组合**页面仅负责数据获取与组合UI 逻辑下沉至 `modules/*/components/`
7. **`force-dynamic` 标注**24/26 个页面显式声明(`school/page.tsx``users/import` 除外,见 P2-10、P2-11
8. **`metadata` 导出**`users/import/page.tsx` 正确导出(见 P2-13建议推广
9. **ESLint 通过**:本次复查运行 `npx eslint "src/app/(dashboard)/admin/**/*.tsx"``npx tsc --noEmit` 均通过,无编译错误。
---
## 七、v2 修复优先级与建议执行顺序
| 优先级 | 问题编号 | 建议执行顺序 | 影响范围 | v1/v2 |
|--------|---------|-------------|---------|-------|
| **P0** | P0-2 | 立即修复 attendance 权限 | 1 文件 | v1 |
| **P0** | P0-1 | 补充 error.tsx / loading.tsx | 新增 ~6 文件 | v1 |
| **P1** | P1-1 | 补充返回类型标注 | 26 文件 | v1 |
| **P1** | P1-2 + P1-6 | 抽取共享 `getSearchParam`(统一两种实现) | 27 文件 | v1+v2 |
| **P1** | P1-3 + P1-5 + P1-7 | `attendance` 类型守卫重构(一并解决 3 个问题) | 1 文件 | v1+v2 |
| **P1** | P1-4 | 统一 UI 文案语言 | ~20 文件 | v1 |
| **P2** | P2-5 + P2-6 | 修复导入顺序与重复导入 | 2 文件 | v1 |
| **P2** | P2-10 + P2-11 | 补充 `dynamic` 声明 | 2 文件 | v2 |
| **P2** | P2-1 + P2-14 + W6 | `school/grades/insights` 筛选器重构(一并解决) | 1 文件 | v1+v2 |
| **P2** | P2-2 + P2-4 | `users/import` 表格与颜色修复 | 1 文件 | v1 |
| **P2** | P2-3 + P2-8 + P2-9 | `school/grades/insights` 工具函数与任意值 | 1 文件 | v1+v2 |
| **P2** | P2-7 | `scheduling/*` data-access 迁移 | 3 文件 | v1 |
| **P2** | P2-12 | 编辑页返回按钮统一 | 6 文件 | v2 |
| **P2** | P2-13 | 补充 `metadata` 导出 | 25 文件 | v2 |
| **R** | R1-R4 | 性能优化Suspense、并行、虚拟化 | 关键页面 | v1+v2 |
| **W** | W1-W7 | 可访问性优化 | 关键页面 | v1+v2 |
---
## 八、附v2 文件清单与合规状态
| 文件 | P0 | P1 | P2 | v2 新增 | 备注 |
|------|----|----|----|---------|------|
| `dashboard/page.tsx` | - | 缺返回类型 | 缺 metadata | - | 整体合规 |
| `announcements/page.tsx` | - | 缺返回类型、getParam 重复 | 缺 metadata | - | 类型守卫正确 |
| `announcements/[id]/page.tsx` | - | 缺返回类型、英文文案 | 缺返回按钮、缺 metadata | P2-12 | - |
| `users/import/page.tsx` | - | 缺返回类型 | 原生 table、硬编码颜色、缺 dynamic | P2-11 | 文案为中文(正确)、有返回按钮、有 metadata |
| `school/page.tsx` | - | 缺返回类型 | 缺 dynamic、缺 metadata | P2-10 | 仅 redirect |
| `school/schools/page.tsx` | - | 缺返回类型、英文文案 | 缺 metadata | - | - |
| `school/classes/page.tsx` | - | 缺返回类型、英文文案 | 缺 metadata | - | - |
| `school/grades/page.tsx` | - | 缺返回类型、英文文案 | 缺 metadata | - | - |
| `school/grades/insights/page.tsx` | - | 缺返回类型、英文文案、getParam 不一致 | 原生 select、任意值、导入顺序、label 未关联、fmt 内联、可选链、原生 form | P1-6, P2-8, P2-9, P2-14 | **问题最多8 个)** |
| `school/academic-year/page.tsx` | - | 缺返回类型、英文文案 | 缺 metadata | - | - |
| `school/departments/page.tsx` | - | 缺返回类型、英文文案 | 缺 metadata | - | - |
| `audit-logs/page.tsx` | - | 缺返回类型、as 断言、英文文案、getParam 重复 | 缺 metadata | - | 权限校验正确 |
| `audit-logs/login-logs/page.tsx` | - | 缺返回类型、as 断言、英文文案、getParam 重复 | 缺 metadata | - | 权限校验正确 |
| `audit-logs/data-changes/page.tsx` | - | 缺返回类型、as 断言、英文文案、getParam 重复 | 缺 metadata | - | 权限校验正确 |
| `scheduling/auto/page.tsx` | - | 缺返回类型、英文文案 | 从 actions 取数、缺 metadata | - | - |
| `scheduling/changes/page.tsx` | - | 缺返回类型、英文文案、getParam 重复 | 从 actions 取数、缺 metadata | - | 类型守卫正确 |
| `scheduling/rules/page.tsx` | - | 缺返回类型、英文文案 | 从 actions 取数、缺 metadata | - | - |
| `course-plans/page.tsx` | - | 缺返回类型、英文文案、getParam 重复 | 缺 metadata | - | 类型守卫正确 |
| `course-plans/create/page.tsx` | - | 缺返回类型、英文文案 | 缺返回按钮、缺 metadata | P2-12 | - |
| `course-plans/[id]/page.tsx` | - | 缺返回类型 | 缺返回按钮、缺 metadata | P2-12 | - |
| `course-plans/[id]/edit/page.tsx` | - | 缺返回类型、英文文案 | 重复导入、缺返回按钮、缺 metadata | P2-12 | - |
| `elective/page.tsx` | - | 缺返回类型、英文文案、getParam 重复 | 缺 metadata | - | 类型守卫正确 |
| `elective/create/page.tsx` | - | 缺返回类型、英文文案 | 缺返回按钮、缺 metadata | P2-12 | - |
| `elective/[id]/edit/page.tsx` | - | 缺返回类型、英文文案 | 缺返回按钮、缺 metadata | P2-12 | - |
| `attendance/page.tsx` | **缺权限校验** | 缺返回类型、as 断言、英文文案、getParam 重复、超 printWidth、内联字面量 | 缺 metadata | P1-5, P1-7 | **最高优先级6 个问题)** |
| `files/page.tsx` | - | 缺返回类型 | 缺 metadata | - | 权限校验正确、整体合规 |
---
## 九、v2 总结与建议
### 当前状态
- **v1 提出的 13 个问题0 个已修复**
- **v2 新增 10 个问题**
- **累计 23 个问题待处理**
- **ESLint 与 tsc 检查通过**(说明现有问题多为规范层面,非编译错误)
### 核心问题集中在三类
1. **系统性缺失**(影响全部 26 个文件):
-`error.tsx` / `loading.tsx`P0-1
- 缺返回类型标注P1-1
-`metadata` 导出P2-13
2. **代码复用问题**(影响 27 个文件):
- `getParam` 重复定义且实现不一致P1-2 + P1-6
3. **`attendance/page.tsx``school/grades/insights/page.tsx` 问题集中**
- `attendance`6 个问题(含 P0 权限缺失)
- `school/grades/insights`8 个问题v2 问题最密集的文件)
### 建议执行策略
1. **第一优先级**:立即修复 `attendance/page.tsx` 的权限校验P0-2这是唯一的安全漏洞
2. **第二优先级**:补充 `error.tsx` / `loading.tsx`P0-1改善所有页面的错误处理与加载体验
3. **第三优先级**:抽取 `shared/lib/utils.ts``getSearchParam`P1-2一次性解决 27 个文件的 DRY 问题
4. **第四优先级**:重构 `attendance/page.tsx`P1-3 + P1-5 + P1-7 一并解决)与 `school/grades/insights/page.tsx`P2-1 + P2-3 + P2-5 + P2-8 + P2-9 + P2-14 + W6 一并解决)
5. **第五优先级**批量补充返回类型P1-1`metadata`P2-13可通过脚本辅助
6. **最后**:统一 UI 文案语言P1-4需产品确认中文/英文/i18n 方案
### 验证要求
每完成一批次修复后,必须运行:
```bash
npm run lint
npx tsc --noEmit
```
确保零错误,并同步更新架构文档 `004_architecture_impact_map.md``005_architecture_data.json`
---
> v2 报告生成完毕。**关键提醒v1 报告提出的问题均未修复,请优先处理 P0 级别的权限校验缺失与错误边界缺失问题。**