Files
NextEdu/bugs/admin_bug.md
SpecialX 49291fcc31 refactor: fix all P0/P1/P2 bugs and architecture issues
Bug fixes (from bugs/ directory):

- Fix cross-module DB queries in 9 modules (homework, grades, parent, diagnostic, elective, proctoring, notifications, scheduling, classes) by routing through data-access functions

- Fix shared/lib <-> auth circular dependency via new session.ts module

- Fix divide-by-zero guard in grades data-access

- Fix audit export data truncation (paginated fetch for full datasets)

- Fix missing transactions in homework grading and elective lottery

- Fix missing revalidatePath in course-plans actions

- Fix frontend permission checks using requirePermission instead of requireAuth

- Fix dashboard role routing using session.user.roles

- Fix student auth pattern (migrate getDemoStudentUser to users module)

- Fix ActionState return type handling in components

Code quality fixes:

- Remove 60+ as type assertions (replace with type guards)

- Remove non-null assertions (use optional chaining or explicit checks)

- Convert dynamic imports to static imports (grades, diagnostic)

- Add React.cache() wrapping for read functions

- Parallelize independent queries with Promise.all

- Add explicit return types to 30+ arrow functions

- Replace any with unknown + type guards

- Fix import type for type-only imports

- Add Zod validation schemas for classes and diagnostic modules

- Extract duplicate code (normalizeRoleName, normalizeBcryptHash, logger IP extraction)

- Add console.error to silent catch blocks

- Fix permission naming consistency (exam:proctor_read -> exam:proctor:read)

Architecture doc sync:

- Update 004_architecture_impact_map.md and 005_architecture_data.json

- Update management-modules-audit.md for P0-7 cross-module fix

Moved deleted proctoring event route to deletes/ folder.
2026-06-19 05:13:34 +08:00

549 lines
25 KiB
Markdown
Raw 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 前端文件规范核查报告
> 核查范围:`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
---
## 一、核查概览
| 维度 | 文件数 | 通过 | 待改进 |
|------|--------|------|--------|
| 架构分层 | 26 | 24 | 2 |
| TypeScript 规范 | 26 | 4 | 22 |
| 安全与权限 | 26 | 3 | 23 |
| UI 一致性与设计令牌 | 26 | 18 | 8 |
| 错误与加载边界 | 26 | 0 | 26 |
| 代码复用DRY | 26 | 0 | 26 |
**结论**:整体架构清晰、服务端组件使用规范、并行数据获取到位,但在**返回类型标注、权限校验一致性、加载/错误边界、代码复用、UI 文案一致性**方面存在系统性问题,需统一整改。
---
## 二、问题清单(按严重程度排序)
### P0 严重问题(必须立即修复)
#### P0-1 全部 26 个页面缺少 `error.tsx` 与 `loading.tsx`
**违反规范**
- 编码规范 §2.3:「每个路由段应提供 `loading.tsx`(骨架屏)和 `error.tsx`(错误边界)」
- 编码规范 §2.4:「每个路由段都必须提供 `error.tsx`,不得出现未捕获异常导致白屏」
- 编码规范 §2.4:「`loading.tsx` 必须提供骨架屏或最小可感知的加载状态,不得使用全局 spin 遮罩」
**现状**`src/app/(dashboard)/admin/` 及其所有子路由(`dashboard/``announcements/``school/*``audit-logs/*``scheduling/*``course-plans/*``elective/*``attendance/``files/``users/import/`)均**未提供** `loading.tsx``error.tsx`
对比:`teacher/``student/` 路由组在关键页面已提供 `loading.tsx`(如 `teacher/exams/all/loading.tsx``student/dashboard/loading.tsx`admin 路由组完全缺失。
**影响**
- 数据获取失败时整页白屏,用户体验差
- 无加载态感知,用户误以为页面卡死
- 不符合 Next.js 16 App Router 最佳实践Suspense 流式渲染)
**修复建议**
1.`src/app/(dashboard)/admin/` 根目录新增 `error.tsx`(具名导出,客户端组件,含重试按钮)
2.`src/app/(dashboard)/admin/` 根目录新增 `loading.tsx`(骨架屏,匹配各页面布局)
3. 对数据量大的页面(`audit-logs/*``school/grades/insights``attendance`)单独提供 `loading.tsx`
4. 对动态路由(`[id]/page.tsx`)单独提供 `error.tsx` 处理 `notFound` 以外的异常
---
#### P0-2 `attendance/page.tsx` 缺少权限校验
**文件**[src/app/(dashboard)/admin/attendance/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/attendance/page.tsx)
**违反规范**
- 项目规则「Server Action 必须使用 `requirePermission()` 进行权限校验」
- 编码规范 §8.3「权限校验Server Action 必须使用 `requirePermission()`
**现状**:第 26 行仅调用 `getAuthContext()` 获取上下文,**未调用 `requirePermission()`** 验证用户是否有考勤查看权限。
```tsx
// 当前代码(第 26 行)
const ctx = await getAuthContext()
```
**对比**:同类 admin 页面均做了权限校验:
- `audit-logs/page.tsx` 第 22 行:`await requirePermission(Permissions.AUDIT_LOG_READ)`
- `audit-logs/login-logs/page.tsx` 第 22 行:`await requirePermission(Permissions.AUDIT_LOG_READ)`
- `audit-logs/data-changes/page.tsx` 第 26 行:`await requirePermission(Permissions.AUDIT_LOG_READ)`
- `files/page.tsx` 第 12 行:`await requirePermission(Permissions.FILE_READ)`
**影响**:越权风险——无考勤查看权限的用户可直接访问 `/admin/attendance` 查看全校考勤数据。
**修复建议**:在 `getAuthContext()` 前增加权限校验:
```tsx
await requirePermission(Permissions.ATTENDANCE_READ)
const ctx = await getAuthContext()
```
---
### P1 重要问题(应尽快修复)
#### P1-1 全部 26 个页面组件缺少返回类型标注
**违反规范**
- 编码规范 §4.2:「函数返回值必须显式标注,特别是 `Promise<T>`
- 项目规则:「函数返回值必须显式标注」
**现状**:所有 `page.tsx` 的默认导出函数均未标注返回类型,例如:
```tsx
// dashboard/page.tsx
export default async function AdminDashboardPage() { // ❌ 缺少 : Promise<JSX.Element>
const data = await getAdminDashboardData()
return <AdminDashboardView data={data} />
}
```
**影响**26 个文件全部不合规,类型推导依赖 TS 隐式推断,不利于代码审查与维护。
**修复建议**:统一补充返回类型:
```tsx
export default async function AdminDashboardPage(): Promise<JSX.Element> {
// ...
}
```
涉及文件admin 目录下全部 26 个 `page.tsx`
---
#### P1-2 `getParam` 工具函数在 27 个文件中重复定义
**违反规范**
- 编码规范 §一:「单一职责」「工具函数 ≤ 40 行」
- DRY 原则
**现状**:以下 admin 文件各自重复定义了相同的 `getParam` / `SearchParams` 类型与函数:
| 文件 | 行号 |
|------|------|
| `announcements/page.tsx` | 8-13 |
| `audit-logs/page.tsx` | 10-15 |
| `audit-logs/login-logs/page.tsx` | 10-15 |
| `audit-logs/data-changes/page.tsx` | 14-19 |
| `scheduling/changes/page.tsx` | 16-21 |
| `course-plans/page.tsx` | 7-12 |
| `elective/page.tsx` | 7-12 |
| `attendance/page.tsx` | 13-18 |
| `school/grades/insights/page.tsx` | 15-22 |
全项目共 27 个文件重复(含 teacher / student / management 路由组)。
**影响**:维护成本高,任何一处逻辑变更需同步修改 27 处。
**修复建议**
1.`src/shared/lib/utils.ts` 新增共享工具:
```tsx
export type SearchParams = { [key: string]: string | string[] | undefined }
export function getSearchParam(params: SearchParams, key: string): string | undefined {
const v = params[key]
return Array.isArray(v) ? v[0] : v
}
```
2. 全部页面改为 `import { getSearchParam, type SearchParams } from "@/shared/lib/utils"`
3. 同步更新架构文档 004 / 005
---
#### P1-3 多个页面使用 `as` 类型断言违反 TypeScript 规范
**违反规范**
- 编码规范 §4.2:「不使用 `as` 断言,除非从 `unknown` 强制转换或在测试中(需注释原因)」
- 项目规则:「禁止 `as` 断言(除非从 `unknown` 转换或测试中,需注释原因)」
**现状**:以下文件使用 `as` 进行类型断言而非类型守卫:
| 文件 | 行号 | 问题代码 |
|------|------|---------|
| `audit-logs/page.tsx` | 28 | `(getParam(params, "status") as AuditLogStatus \| undefined)` |
| `audit-logs/login-logs/page.tsx` | 26-27 | `as LoginLogAction \| undefined``as LoginLogStatus \| undefined` |
| `audit-logs/data-changes/page.tsx` | 31 | `as DataChangeAction \| undefined` |
| `attendance/page.tsx` | 39 | `as "present" \| "absent" \| "late" \| "early_leave" \| "excused"` |
**对比(正确示例)**:以下文件已使用类型守卫,应作为模板推广:
- `announcements/page.tsx` 第 15-16 行:`isValidStatus` 类型守卫
- `scheduling/changes/page.tsx` 第 23-24 行:`isValidStatus` 类型守卫
- `course-plans/page.tsx` 第 14-15 行:`isValidStatus` 类型守卫
- `elective/page.tsx` 第 14-15 行:`isValidStatus` 类型守卫
**影响**:运行时无法捕获非法枚举值,类型安全被绕过。
**修复建议**:为每个枚举类型补充类型守卫,替换 `as` 断言:
```tsx
const isValidAuditLogStatus = (v?: string): v is AuditLogStatus =>
v === "success" || v === "failure" || v === "pending"
const status = isValidAuditLogStatus(statusParam) ? statusParam : undefined
```
---
#### P1-4 UI 文案语言不统一(中英文混用)
**违反规范**
- 编码规范 §一:「可读性优先」
- 项目定位为「Next_Edu K12 智慧教务系统」(中文用户)
**现状**
| 文件 | 文案语言 |
|------|---------|
| `users/import/page.tsx` | 中文("批量导入用户"、"返回" |
| `announcements/[id]/page.tsx` | 英文("Edit Announcement" |
| `school/schools/page.tsx` | 英文("Schools"、"Manage schools..." |
| `school/classes/page.tsx` | 英文("Classes"、"Manage classes..." |
| `school/grades/page.tsx` | 英文("Grades"、"Manage grades..." |
| `school/grades/insights/page.tsx` | 英文("Grade Insights"、"Filters" |
| `school/academic-year/page.tsx` | 英文("Academic Year" |
| `school/departments/page.tsx` | 英文("Departments" |
| `audit-logs/page.tsx` | 英文("Audit Logs" |
| `audit-logs/login-logs/page.tsx` | 英文("Login Logs" |
| `audit-logs/data-changes/page.tsx` | 英文("Data Change Logs" |
| `scheduling/auto/page.tsx` | 英文("Auto Schedule" |
| `scheduling/changes/page.tsx` | 英文("Schedule Change Requests" |
| `scheduling/rules/page.tsx` | 英文("Scheduling Rules" |
| `course-plans/page.tsx` | 英文("Course Plans" |
| `course-plans/create/page.tsx` | 英文("New Course Plan" |
| `course-plans/[id]/edit/page.tsx` | 英文("Edit Course Plan" |
| `elective/page.tsx` | 英文("Elective Courses" |
| `elective/create/page.tsx` | 英文("New Elective Course" |
| `elective/[id]/edit/page.tsx` | 英文("Edit Elective Course" |
| `attendance/page.tsx` | 英文("Attendance Overview" |
**影响**用户体验割裂admin 区仅 `users/import` 为中文,其余全英文,与系统定位不符。
**修复建议**:统一为中文(与 `users/import/page.tsx` 保持一致),或引入 i18n 方案统一管理。建议优先统一为中文。
---
### P2 一般问题(建议修复)
#### P2-1 `school/grades/insights/page.tsx` 使用原生 `<select>` 而非共享组件
**文件**[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>` 元素,而项目已提供 `@/shared/components/ui/select.tsx`shadcn Select
```tsx
<select
name="gradeId"
defaultValue={selected || "all"}
className="h-10 w-full rounded-md border bg-background px-3 text-sm md:w-[360px]"
>
```
**影响**
- UI 风格与其他页面不一致(其他页面使用 shadcn Select
- 原生 `<select>` 样式难以跨浏览器统一
- 可访问性较弱(缺少 ARIA 属性)
**修复建议**:替换为 `@/shared/components/ui/select.tsx``Select` / `SelectTrigger` / `SelectContent` / `SelectItem` 组合。
---
#### P2-2 `users/import/page.tsx` 使用原生 `<table>` 而非共享组件
**文件**[src/app/(dashboard)/admin/users/import/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/users/import/page.tsx#L93-L128)
**现状**:第 93-128 行使用原生 `<table>` 元素手写表格,而项目已提供 `@/shared/components/ui/table.tsx`shadcn Table
**影响**:与 `school/grades/insights/page.tsx` 等使用 shadcn Table 的页面风格不一致。
**修复建议**:替换为 `Table` / `TableHeader` / `TableBody` / `TableRow` / `TableHead` / `TableCell` 组合。
---
#### P2-3 Tailwind 任意值违规
**违反规范**
- 编码规范 §6.2:「禁止使用任意值(`w-[137px]`),除非有充分理由并注释说明」
- 项目规则:「禁止使用任意值(`w-[137px]`),除非有充分理由并注释」
**现状**
| 文件 | 行号 | 问题类名 |
|------|------|---------|
| `school/grades/insights/page.tsx` | 60 | `md:w-[360px]` |
| `school/grades/insights/page.tsx` | 82, 89, 96 | `h-[360px]` |
| `users/import/page.tsx` | 16 | `h-full flex-1 flex-col``flex-1` 合理,但整体布局类应复用) |
**修复建议**
- `md:w-[360px]` → 使用设计令牌宽度类(如 `md:w-72``md:w-80`)或在 globals.css 定义 `--filter-width` 变量
- `h-[360px]` → 使用 `h-80`320px`h-96`384px等标准档位
---
#### P2-4 `users/import/page.tsx` 使用硬编码颜色
**违反规范**
- 编码规范 §6.3:「所有视觉设计决策(颜色、字号、间距)必须体现在设计令牌中,组件中不使用硬编码值」
**文件**[src/app/(dashboard)/admin/users/import/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/users/import/page.tsx#L67)
**现状**:第 67 行使用 `text-amber-500` 硬编码颜色:
```tsx
<Info className="h-5 w-5 text-amber-500" />
```
**修复建议**:使用设计令牌颜色,如 `text-warning`(若存在)或在 globals.css 定义 `--warning` 变量。如暂无 warning 令牌,可使用 `text-primary``text-muted-foreground` 保持一致。
---
#### P2-5 `school/grades/insights/page.tsx` 导入顺序违规
**违反规范**
- 编码规范 §4.3「导入顺序React → 第三方 → 内部绝对路径 → 相对路径 → 类型导入」
- 项目规则引用的 ESLint `import/order` 规则
**文件**[src/app/(dashboard)/admin/school/grades/insights/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx#L1-L11)
**现状**:第 1-11 行导入顺序混乱,`lucide-react`(第三方库)被放在所有 `@/` 内部导入之后:
```tsx
import Link from "next/link" // next外部
import { getGrades } from "@/modules/school/data-access" // 内部
import { getGradeHomeworkInsights } from "@/modules/classes/data-access"
import { EmptyState } from "@/shared/components/ui/empty-state"
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
import { Badge } from "@/shared/components/ui/badge"
import { Button } from "@/shared/components/ui/button"
import { Table, TableBody, ... } from "@/shared/components/ui/table"
import { formatDate } from "@/shared/lib/utils"
import { BarChart3 } from "lucide-react" // ❌ 第三方应在前
```
**修复建议**:调整为 `next``lucide-react``@/` 内部导入,分组间空一行:
```tsx
import Link from "next/link"
import { BarChart3 } from "lucide-react"
import { getGrades } from "@/modules/school/data-access"
// ...
```
---
#### P2-6 `course-plans/[id]/edit/page.tsx` 同模块重复导入
**文件**[src/app/(dashboard)/admin/course-plans/[id]/edit/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/course-plans/[id]/edit/page.tsx#L3-L4)
**现状**:第 3-4 行从同一模块 `@/modules/course-plans/data-access` 分两行导入:
```tsx
import { getCoursePlanById } from "@/modules/course-plans/data-access"
import { getSubjectOptions } from "@/modules/course-plans/data-access"
```
**修复建议**:合并为单行:
```tsx
import { getCoursePlanById, getSubjectOptions } from "@/modules/course-plans/data-access"
```
---
#### P2-7 `scheduling/*` 页面从 `actions` 而非 `data-access` 获取数据
**违反规范**
- 编码规范 §7.1:「服务端数据获取通过模块的 `data-access.ts` 函数」
- 架构影响地图「actions.ts编排层权限 + 调用 data-access + revalidate
**现状**:以下页面从 `@/modules/scheduling/actions` 导入数据查询函数:
| 文件 | 导入函数 |
|------|---------|
| `scheduling/auto/page.tsx` | `getAdminClassesForScheduling` |
| `scheduling/changes/page.tsx` | `getAdminClassesForScheduling``getScheduleChanges` |
| `scheduling/rules/page.tsx` | `getAdminClassesForScheduling``getSchedulingRules` |
**说明**:规范允许 `app/` 调用 Server Actions但 Server Actions 的职责是「编排:权限 + 调用 data-access + revalidate」主要用于**变更操作**。纯读取操作应通过 `data-access.ts` 暴露,避免在 Server Component 中触发不必要的 `revalidate` 逻辑。
**修复建议**:将 `getAdminClassesForScheduling``getScheduleChanges``getSchedulingRules` 等纯查询函数迁移到 `scheduling/data-access.ts`,或在 actions 中明确标注其为只读封装。需同步更新架构文档 004 / 005。
---
## 三、React 性能优化建议(基于最佳实践)
### R1 利用 Suspense 流式渲染提升首屏感知性能
**现状**:所有页面使用 `export const dynamic = "force-dynamic"` 整页动态渲染,数据获取完成前无任何内容呈现。
**建议**:对数据量大的页面(`audit-logs/*``school/grades/insights``attendance`)拆分为多个 Suspense 边界,优先渲染页面骨架,慢查询部分流式注入:
```tsx
import { Suspense } from "react"
export default async function AuditLogsPage(): Promise<JSX.Element> {
return (
<div className="flex h-full flex-col space-y-8 p-8">
<Header />
<Suspense fallback={<FilterSkeleton />}>
<Filters />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<AuditTable />
</Suspense>
</div>
)
}
```
### R2 `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#L30-L33)
**现状**:第 30-33 行先 `await getGrades()` 再条件 `await getGradeHomeworkInsights()`,两次串行查询:
```tsx
const grades = await getGrades()
const selected = gradeId && gradeId !== "all" ? gradeId : ""
const insights = selected ? await getGradeHomeworkInsights({ gradeId: selected, limit: 50 }) : null
```
**说明**`insights` 依赖 `selected`(来自 URL 参数,非 `grades` 结果),两者无数据依赖,可并行:
```tsx
const selected = gradeId && gradeId !== "all" ? gradeId : ""
const [grades, insights] = await Promise.all([
getGrades(),
selected ? getGradeHomeworkInsights({ gradeId: selected, limit: 50 }) : Promise.resolve(null),
])
```
### R3 列表页 `classOptions` 映射可下沉至 data-access
**现状**`scheduling/auto``scheduling/changes``scheduling/rules``attendance``course-plans/create``course-plans/[id]/edit``elective/create``elective/[id]/edit` 等页面均在组件内 `.map()` 转换数据形状:
```tsx
const classOptions = classes.map((c) => ({ id: c.id, name: c.name, grade: c.grade }))
```
**建议**:在对应 `data-access.ts` 提供 `getClassOptions()``getStaffOptions()` 等轻量查询函数,仅返回 `{ id, name }` 形状,减少传输数据量与组件层转换逻辑。
---
## 四、Web 界面设计规范建议(基于 WCAG 2.2 AA
### W1 表单 `<label>` 与控件关联不规范
**文件**[src/app/(dashboard)/admin/school/grades/insights/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx#L56-L57)
**现状**:第 56-57 行 `<label>``<select>` 未通过 `htmlFor` / `id` 关联:
```tsx
<label className="text-sm font-medium">Grade</label>
<select name="gradeId" ...>
```
**违反**WCAG 2.2 SC 1.3.1信息与关系、SC 3.3.2(标签或指令)。
**修复建议**
```tsx
<label htmlFor="grade-filter" className="text-sm font-medium">Grade</label>
<select id="grade-filter" name="gradeId" ...>
```
### W2 `users/import/page.tsx` 表格缺少 `<caption>` 与语义化标注
**文件**[src/app/(dashboard)/admin/users/import/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/users/import/page.tsx#L93-L128)
**现状**:原生 `<table>` 缺少 `<caption>` 描述表格用途,屏幕阅读器无法快速理解表格主题。
**修复建议**:增加 `<caption className="sr-only">模板字段说明</caption>`,或替换为 shadcn Table 后通过 `aria-label` 补充。
### W3 页面标题层级不统一
**现状**
- 部分页面使用 `<h2>` 作为页面主标题(如 `school/schools``audit-logs`
- `users/import/page.tsx` 也使用 `<h2>`
- 但页面布局中未见统一的 `<h1>` 主标题层级
**建议**:确认 `(dashboard)/layout.tsx` 是否提供 `<h1>` 或页面 `<main>` 的 accessible name若无建议各页面统一使用 `<h1>` 作为页面主标题,`<h2>` 用于区块标题,保持标题层级连贯。
### W4 交互式筛选器缺少 `aria-live` 反馈
**文件**`school/grades/insights/page.tsx``attendance/page.tsx``audit-logs/*`
**现状**:筛选器提交后表格数据刷新,但屏幕阅读器用户无法感知数据已更新。
**违反**WCAG 2.2 SC 4.1.3(状态消息)。
**建议**:在表格容器添加 `aria-live="polite"` 或使用项目已有的 `useAriaLive` Hook 通知「已加载 N 条记录」。
### W5 `EmptyState` 组件使用一致但图标语义可优化
**现状**`scheduling/*``attendance``school/grades/insights` 均使用 `EmptyState` 组件,图标统一使用 `ClipboardList` / `BarChart3`,体验一致(优点)。
**建议**`BarChart3` 用于「无数据」与「选择年级」两种语义略显混淆,建议「等待操作」类空状态使用 `MousePointerClick``Filter` 图标区分。
---
## 五、优秀实践(已符合规范,应保持)
1. **服务端组件默认化**:全部 26 个页面均为 async 服务端组件,未滥用 `"use client"`,符合 §5.2。
2. **并行数据获取**`announcements/page.tsx``audit-logs/*``course-plans/create``course-plans/[id]/edit``elective/create``elective/[id]/edit``school/grades``scheduling/rules` 等均使用 `Promise.all` 并行查询,性能良好。
3. **类型守卫正确使用**`announcements/page.tsx``scheduling/changes/page.tsx``course-plans/page.tsx``elective/page.tsx` 使用 `isValidStatus` 类型守卫,是 `as` 断言的正确替代方案。
4. **404 处理**`announcements/[id]/page.tsx``course-plans/[id]/page.tsx``course-plans/[id]/edit/page.tsx``elective/[id]/edit/page.tsx` 使用 `notFound()` 处理资源不存在场景。
5. **权限校验到位**`audit-logs/*`3 个文件)、`files/page.tsx` 正确调用 `requirePermission()`
6. **模块化组合**页面仅负责数据获取与组合UI 逻辑下沉至 `modules/*/components/`,符合三层架构。
7. **`force-dynamic` 标注**:需要实时数据的页面均显式声明 `export const dynamic = "force-dynamic"`
8. **`metadata` 导出**`users/import/page.tsx` 正确导出 `metadata` 用于 SEO建议其他页面补充
---
## 六、修复优先级与建议执行顺序
| 优先级 | 问题编号 | 建议执行顺序 | 影响范围 |
|--------|---------|-------------|---------|
| P0 | P0-2 | 立即修复 attendance 权限 | 1 文件 |
| P0 | P0-1 | 补充 error.tsx / loading.tsx | 新增 ~6 文件 |
| P1 | P1-1 | 补充返回类型标注 | 26 文件 |
| P1 | P1-2 | 抽取共享 getSearchParam | 27 文件 |
| P1 | P1-3 | 替换 as 断言为类型守卫 | 4 文件 |
| P1 | P1-4 | 统一 UI 文案语言 | ~20 文件 |
| P2 | P2-1 ~ P2-7 | 逐步整改 | 单文件级 |
| R1 ~ R3 | 性能优化 | 迭代优化 | 关键页面 |
| W1 ~ W5 | 可访问性 | 迭代优化 | 关键页面 |
---
## 七、附:文件清单与合规状态
| 文件 | P0 | P1 | P2 | 备注 |
|------|----|----|----|----|
| `dashboard/page.tsx` | - | 缺返回类型 | - | 整体合规 |
| `announcements/page.tsx` | - | 缺返回类型、getParam 重复 | - | 类型守卫正确 |
| `announcements/[id]/page.tsx` | - | 缺返回类型、英文文案 | - | - |
| `users/import/page.tsx` | - | 缺返回类型 | 原生 table、硬编码颜色 | 文案为中文(正确) |
| `school/page.tsx` | - | 缺返回类型 | - | 仅 redirect |
| `school/schools/page.tsx` | - | 缺返回类型、英文文案 | - | - |
| `school/classes/page.tsx` | - | 缺返回类型、英文文案 | - | - |
| `school/grades/page.tsx` | - | 缺返回类型、英文文案 | - | - |
| `school/grades/insights/page.tsx` | - | 缺返回类型、英文文案 | 原生 select、任意值、导入顺序、label 未关联 | 问题最多 |
| `school/academic-year/page.tsx` | - | 缺返回类型、英文文案 | - | - |
| `school/departments/page.tsx` | - | 缺返回类型、英文文案 | - | - |
| `audit-logs/page.tsx` | - | 缺返回类型、as 断言、英文文案、getParam 重复 | - | 权限校验正确 |
| `audit-logs/login-logs/page.tsx` | - | 缺返回类型、as 断言、英文文案、getParam 重复 | - | 权限校验正确 |
| `audit-logs/data-changes/page.tsx` | - | 缺返回类型、as 断言、英文文案、getParam 重复 | - | 权限校验正确 |
| `scheduling/auto/page.tsx` | - | 缺返回类型、英文文案 | 从 actions 取数 | - |
| `scheduling/changes/page.tsx` | - | 缺返回类型、英文文案、getParam 重复 | 从 actions 取数 | 类型守卫正确 |
| `scheduling/rules/page.tsx` | - | 缺返回类型、英文文案 | 从 actions 取数 | - |
| `course-plans/page.tsx` | - | 缺返回类型、英文文案、getParam 重复 | - | 类型守卫正确 |
| `course-plans/create/page.tsx` | - | 缺返回类型、英文文案 | - | - |
| `course-plans/[id]/page.tsx` | - | 缺返回类型 | - | - |
| `course-plans/[id]/edit/page.tsx` | - | 缺返回类型、英文文案 | 重复导入 | - |
| `elective/page.tsx` | - | 缺返回类型、英文文案、getParam 重复 | - | 类型守卫正确 |
| `elective/create/page.tsx` | - | 缺返回类型、英文文案 | - | - |
| `elective/[id]/edit/page.tsx` | - | 缺返回类型、英文文案 | - | - |
| `attendance/page.tsx` | **缺权限校验** | 缺返回类型、as 断言、英文文案、getParam 重复 | - | 最高优先级 |
| `files/page.tsx` | - | 缺返回类型 | - | 权限校验正确、整体合规 |
---
> 报告生成完毕。建议按「六、修复优先级」顺序整改,每完成一批次后运行 `npm run lint` 与 `npx tsc --noEmit` 验证,并同步更新架构文档 004 / 005。