# 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-18（v2）
> 上次核查：2026-06-18（v1）

---

## 〇、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-5（v2 新增）`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-6（v2 新增）`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-7（v2 新增）`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-8（v2 新增）`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-9（v2 新增）`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-10（v2 新增）`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-11（v2 新增）`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-12（v2 新增）多个编辑页缺少返回上一页的导航

**违反规范**：
- 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-13（v2 新增）大部分页面缺少 `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-14（v2 新增）`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 |
| SEO（metadata） | 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-access（v1 提出，未实施）

详见 v1 报告 R3。

### R4（v2 新增）`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-W5（v1 提出，未实施）

详见 v1 报告第四部分：`<label>` 关联、表格 `<caption>`、标题层级、`aria-live`、`EmptyState` 图标语义。

### W6（v2 新增）`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 支持。

### W7（v2 新增）`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 级别的权限校验缺失与错误边界缺失问题。**
