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
主要变更: - 新增 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)
45 KiB
45 KiB
src/app/(dashboard)/{announcements,dashboard,management,messages,profile,settings} 规范核查报告 v2
核查日期:2026-06-18(第二轮) 核查范围:
src/app/(dashboard)/下的 announcements、dashboard、management、messages、profile、settings 子路由及其直接依赖的模块组件 依据文档:
- 项目规则
- 编码规范
- 架构影响地图 004
- 架构数据 005 应用技能:
vercel-react-best-practices、web-design-guidelines(web-artifacts-builder加载失败,界面优化建议已合并至 web-design-guidelines 章节) 前置版本:others_bug.md v1
〇、v1 → v2 修复进度对比
已修复问题(11 项)
| 问题编号 | 描述 | 修复方式 |
|---|---|---|
| BUG-A01 | announcements/page.tsx 缺少权限校验 |
✅ 增加 requirePermission(ANNOUNCEMENT_READ) |
| BUG-D01 | dashboard/page.tsx 使用权限反推角色 |
✅ 改用 roles.includes("admin"/"student"/"parent") |
| BUG-M01 | management/grade/classes/page.tsx 缺少权限校验 |
✅ 增加 requirePermission(GRADE_MANAGE) |
| BUG-M02 | management/grade/classes/page.tsx userId 兜底空字符串 |
✅ 改用 ctx.userId |
| BUG-MSG01 相关 | messages/page.tsx 已有权限校验 |
✅ 保持 requirePermission(MESSAGE_READ) |
| BUG-SS01 | settings/security/page.tsx 缺少权限校验 |
✅ 增加 requireAuth() |
| BUG-S 部分 | settings/page.tsx 改用 requireAuth() |
⚠️ 部分修复(仍用权限反推角色) |
| BUG-P 部分 | profile/page.tsx 改用 requireAuth() |
⚠️ 部分修复(仍用权限反推角色) |
| BUG-NL03 | notification-list.tsx button 缺少 type 属性 |
✅ 已添加 type="button" |
| BUG-PS 部分 | profile-settings-form.tsx 处理 result.success |
✅ 增加 result.success 分支处理 |
| BUG-MI01 部分 | management/grade/insights/page.tsx 增加权限校验 |
⚠️ 使用 requireAuth() 而非 requirePermission() |
未修复问题(仍存在)
v1 报告中的其余 53 项问题仍未修复,详见下文。
一、核查文件清单
二、违规问题清单(仍未修复)
2.1 dashboard/page.tsx — 严重度:中
BUG-D02:多重 redirect 调用难以维护(未修复)
- 位置:
src/app/(dashboard)/dashboard/page.tsx:12-15 - 问题:4 个连续
if + redirect缺乏优先级文档说明,新增角色时易遗漏 - 改进建议:抽取为
resolveDefaultPath(roles)单一函数(proxy.ts已有类似实现),保持单一职责
2.2 management/grade/insights/page.tsx — 严重度:高
BUG-MI01:权限校验不充分(部分修复)
- 位置:
src/app/(dashboard)/management/grade/insights/page.tsx:27 - 问题:使用
requireAuth()而非requirePermission(),仅校验登录状态,未校验具体权限 - 规范依据:项目规则「Server Action 必须使用
requirePermission()进行权限校验」 - 改进建议:应使用
requirePermission(Permissions.HOMEWORK_READ)或对应年级负责人权限
BUG-MI02:使用原生 <select> 而非 shadcn Select 组件(未修复)
- 位置:
src/app/(dashboard)/management/grade/insights/page.tsx:72-83 - 问题:使用原生
<select>元素,与项目其他页面使用的 shadcnSelect组件风格不一致 - 规范依据:Web Interface Guidelines — Consistency;项目组件规范
- 影响:视觉风格不统一,无障碍特性差异,主题切换时原生 select 样式无法跟随
- 改进建议:替换为 shadcn
Select组件
BUG-MI03:<label> 缺少 htmlFor 关联(未修复)
- 位置:
src/app/(dashboard)/management/grade/insights/page.tsx:71 - 问题:
<label className="text-sm font-medium">Grade</label>未关联到select元素 - 规范依据:Web Interface Guidelines — Forms「Labels properly associated」
- 改进建议:
<label htmlFor="gradeId" className="...">Grade</label>
BUG-MI04:表单提交触发整页刷新(未修复)
- 位置:
src/app/(dashboard)/management/grade/insights/page.tsx:70 - 问题:
<form action="/management/grade/insights" method="get">使用原生 GET 提交,导致整页刷新 - 违反规则:Next.js 客户端导航最佳实践
- 改进建议:改为客户端组件 +
useRouter().push()或使用useSearchParams实现无刷新筛选
BUG-MI05:fmt 工具函数命名过于简短(未修复)
- 位置:
src/app/(dashboard)/management/grade/insights/page.tsx:24 - 问题:
const fmt = (v: number | null, digits = 1) => ...命名过于简短 - 改进建议:重命名为
formatScore或formatNumber
2.3 messages/[id]/page.tsx — 严重度:中
BUG-MSG02:渲染期间执行写操作(未修复)
- 位置:
src/app/(dashboard)/messages/[id]/page.tsx:20-23 - 问题:在 RSC 渲染期间调用
markMessageAsRead(id, ctx.userId)执行写操作 - 违反规则:React Server Components 规范 — 渲染函数应为纯函数,不应有副作用
- 影响:
- React 18+ 严格模式下渲染函数可能被调用两次,导致重复写入
- 流式渲染时若渲染被中断,写操作可能已执行但 UI 未更新
- 错误边界捕获错误后重试渲染会再次执行写操作
- 改进建议:使用
after()API 延迟执行非阻塞写操作import { after } from "next/server" if (!message.isRead && message.receiverId === ctx.userId) { after(() => markMessageAsRead(id, ctx.userId)) } - 规范依据:
vercel-react-best-practices—server-after-nonblocking
2.4 messages/compose/page.tsx — 严重度:低
BUG-MSG03:缺少 metadata 导出(未修复)
- 改进建议:
export const metadata = { title: "Compose Message" }
2.5 profile/page.tsx — 严重度:高
BUG-P01:使用权限反推角色(未修复)
- 位置:
src/app/(dashboard)/profile/page.tsx:46-47 - 问题:
isStudent = permissions.includes(HOMEWORK_SUBMIT) && !permissions.includes(EXAM_CREATE),isTeacher = permissions.includes(EXAM_CREATE) - 规范依据:项目规则禁止硬编码角色判断;架构文档 004 已标记
- 改进建议:使用
ctx.roles判断const roles = ctx.roles const isStudent = roles.includes("student") const isTeacher = roles.includes("teacher")
BUG-P02:在 RSC 中使用 IIFE 异步块(未修复)
- 位置:
src/app/(dashboard)/profile/page.tsx:49-117 - 问题:使用
await (async () => { ... })()立即执行异步函数,将学生数据加载逻辑内联在组件中 - 影响:
- 函数体过长(60+ 行),难以测试
- 无法被 React
cache()缓存 - 违反单一职责原则
- 改进建议:抽取为
data-access.ts中的getStudentProfileData(userId)函数
BUG-P03:本地 formatDate 函数与全局工具重复(未修复)
- 位置:
src/app/(dashboard)/profile/page.tsx:26-33 - 问题:定义了本地
formatDate函数,与@/shared/lib/utils.formatDate重复 - 影响:日期格式不一致(本地使用
en-US,全局使用zh-CN),维护成本增加 - 改进建议:删除本地函数,使用全局
formatDate
BUG-P04:toWeekday 类型断言不必要(未修复)
- 位置:
src/app/(dashboard)/profile/page.tsx:23 - 问题:
(day === 0 ? 7 : day) as 1 | 2 | 3 | 4 | 5 | 6 | 7使用as断言 - 规范依据:编码规范 4.2.3「禁止
as断言(除非从unknown转换)」 - 改进建议:使用类型守卫
const toWeekday = (d: Date): 1 | 2 | 3 | 4 | 5 | 6 | 7 => { const day = d.getDay() const result = day === 0 ? 7 : day if (result < 1 || result > 7) throw new Error("Invalid weekday") return result }
BUG-P05:缩进不一致(未修复)
- 位置:
src/app/(dashboard)/profile/page.tsx:156,166,184,204-206 - 问题:多处缩进不一致(如 156 行
<div比 155 行多一个空格) - 规范依据:
.prettierrc配置tabWidth: 2 - 改进建议:运行
npx prettier --write统一格式
BUG-P06:缺少 metadata 导出(未修复)
- 改进建议:
export const metadata = { title: "Profile" }
2.6 settings/page.tsx — 严重度:中
BUG-S01:使用权限反推角色(未修复)
- 位置:
src/app/(dashboard)/settings/page.tsx:24,27 - 问题:同 BUG-P01,使用
permissions.includes(HOMEWORK_SUBMIT) && !permissions.includes(EXAM_CREATE)判断学生 - 改进建议:使用
ctx.roles判断
BUG-S02:缺少 metadata 导出(未修复)
- 改进建议:
export const metadata = { title: "Settings" }
2.7 layout.tsx — 严重度:中
BUG-L01:跳过链接样式冗长(未修复)
- 位置:
src/app/(dashboard)/layout.tsx:12 - 问题:
focus:absolute focus:z-50 focus:p-4 focus:bg-background focus:text-foreground focus:border focus:border-border focus:rounded-md focus:m-2类名过长且重复 - 改进建议:抽取为
skip-link类名或独立组件
BUG-L02:<main> 元素缺少显式 role="main"(未修复)
- 位置:
src/app/(dashboard)/layout.tsx:16 - 问题:
<main id="main-content">已有id,但部分屏幕阅读器需要显式role="main" - 改进建议:添加
role="main"(虽然 HTML5 规范中<main>隐式role="main",但为兼容性建议显式)
2.8 error.tsx — 严重度:低
BUG-E01:未使用 error.digest 信息(未修复)
- 位置:
src/app/(dashboard)/error.tsx:7 - 问题:
error参数包含digest字段(用于错误追踪),但未展示给用户或上报 - 改进建议:在描述中包含
digest或提供「复制错误码」按钮
2.9 not-found.tsx — 严重度:低
BUG-NF01:使用原生 <a> 样式而非 Button 组件(未修复)
- 位置:
src/app/(dashboard)/not-found.tsx:15-20 - 问题:
<Link className="bg-primary text-primary-foreground hover:bg-primary/90 inline-flex h-9 ...">手动拼接 Button 样式 - 规范依据:项目组件规范「使用
cn()工具函数管理条件类名」 - 改进建议:使用
<Button asChild><Link href="/dashboard">...</Link></Button>
2.10 announcement-list.tsx — 严重度:中
BUG-AL01:使用 <a href> 而非 <Link>(未修复)
- 位置:
src/modules/announcements/components/announcement-list.tsx:76 - 问题:
<a href={createHref}>使用原生<a>标签,导致全页刷新 - 违反规则:
vercel-react-best-practices— Next.js 客户端导航最佳实践 - 改进建议:使用
next/link的<Link>组件
BUG-AL02:handleFilterChange 未使用 useCallback(未修复)
- 位置:
src/modules/announcements/components/announcement-list.tsx:51-57 - 问题:
handleFilterChange每次渲染创建新引用 - 违反规则:
rerender-functional-setstate、rerender-memo - 改进建议:使用
useCallback包裹
2.11 announcement-card.tsx — 严重度:低
BUG-AC01:useMemo 包裹整个 JSX(过度优化,未修复)
- 位置:
src/modules/announcements/components/announcement-card.tsx:38-68 - 问题:使用
useMemo包裹整个卡片 JSX,依赖项为[announcement](对象) - 违反规则:
rerender-simple-expression-in-memo— 简单表达式不需要 memo - 影响:
announcement是对象,每次父组件传入新引用时 memo 失效,无实际优化效果 - 改进建议:移除
useMemo,直接渲染 JSX;如需优化应使用React.memo包裹组件
2.12 announcement-detail.tsx — 严重度:中
BUG-AD01:使用 <a href> 而非 <Link>(未修复)
- 位置:
src/modules/announcements/components/announcement-detail.tsx:123,146 - 问题:
backHref和editHref使用原生<a>标签 - 改进建议:替换为
next/link
BUG-AD02:三个处理函数未 useCallback(未修复)
- 位置:
src/modules/announcements/components/announcement-detail.tsx:64-115 - 问题:
handlePublish、handleArchive、handleDelete每次渲染创建新引用 - 违反规则:
rerender-functional-setstate - 改进建议:使用
useCallback包裹
2.13 message-list.tsx — 严重度:中
BUG-ML01:使用字符串拼接动态类名(未修复)
- 位置:
src/modules/messaging/components/message-list.tsx:82,91 - 问题:
className={`transition-colors hover:bg-accent/50 ${unread ? "border-primary/40" : ""}`}使用模板字符串拼接类名 - 规范依据:项目规则「使用
cn()工具函数管理条件类名」 - 改进建议:
className={cn( "transition-colors hover:bg-accent/50", unread && "border-primary/40" )}
BUG-ML02:usePermission 在客户端组件中导致 hydration 风险(未修复)
- 位置:
src/modules/messaging/components/message-list.tsx:30-31 - 问题:
usePermission()依赖useSession(),服务端渲染时返回空权限,客户端首次渲染后才有权限,导致「Compose」按钮在 hydration 后闪烁 - 违反规则:Web Interface Guidelines — Hydration Safety
- 改进建议:将
canSend作为 prop 从 RSC 父组件传入
2.14 message-detail.tsx — 严重度:中
BUG-MD01:使用 <a href> 而非 <Link>(未修复)
- 位置:
src/modules/messaging/components/message-detail.tsx:79 - 问题:
<a href={backHref}>使用原生<a> - 改进建议:替换为
next/link
BUG-MD02:replyHref 为 undefined 时仍渲染 Link(未修复)
- 位置:
src/modules/messaging/components/message-detail.tsx:68,87-92 - 问题:当
canSend为 false 时replyHref为undefined,但代码使用<Link href={replyHref ?? "#"}>仍渲染可点击链接,点击后跳转到# - 改进建议:
canSend为 false 时不渲染 Reply 按钮(当前已有{canSend ? ... : null}包裹,但内部仍用?? "#"兜底,应直接使用replyHref!或移除兜底)
BUG-MD03:URL 参数未编码(未修复)
- 位置:
src/modules/messaging/components/message-detail.tsx:69-71 - 问题:
subject=${encodeURIComponent(...)}已编码 subject,但parentId和receiverId未编码 - 改进建议:使用
URLSearchParams构建查询字符串
2.15 message-compose.tsx — 严重度:中
BUG-MC01:使用 <a href> 而非 <Link>(未修复)
- 位置:
src/modules/messaging/components/message-compose.tsx:73 - 问题:返回按钮使用原生
<a> - 改进建议:替换为
next/link
BUG-MC02:隐藏 input 与 formData.set 重复(未修复)
- 位置:
src/modules/messaging/components/message-compose.tsx:46,97 - 问题:
handleSubmit中formData.set("receiverId", receiverId),同时 JSX 中又有<input type="hidden" name="receiverId" value={receiverId} />,两者重复 - 改进建议:移除隐藏 input,仅使用
formData.set
BUG-MC03:handleSubmit 未 useCallback(未修复)
- 位置:
src/modules/messaging/components/message-compose.tsx:41-66 - 改进建议:使用
useCallback包裹
2.16 notification-list.tsx — 严重度:中
BUG-NL01:使用字符串拼接动态类名(未修复)
- 位置:
src/modules/messaging/components/notification-list.tsx:94,102 - 问题:
className={`transition-colors ${!n.isRead ? "border-primary/40 bg-primary/5" : ""}`} - 规范依据:项目规则「使用
cn()工具函数管理条件类名」 - 改进建议:使用
cn()
BUG-NL02:handleMarkRead 未 useCallback(未修复)
- 位置:
src/modules/messaging/components/notification-list.tsx:54-63 - 改进建议:使用
useCallback
2.17 password-change-form.tsx — 严重度:高
BUG-PC01:使用字符串拼接动态类名(严重违规,未修复)
- 位置:
src/modules/settings/components/password-change-form.tsx:133 - 问题:
className={`h-2 [&>div]:${meta.color}`}动态拼接 Tailwind 类名 - 规范依据:项目规则「禁止字符串拼接动态类名(
bg-${color}-500)」 - 影响:Tailwind JIT 无法识别动态拼接的类名,
bg-red-500、bg-yellow-500、bg-green-500可能被 tree-shaking 移除,导致生产环境进度条无颜色 - 改进建议:使用映射对象 +
cn()const STRENGTH_BAR_CLASS: Record<PasswordStrength, string> = { weak: "h-2 [&>div]:bg-red-500", medium: "h-2 [&>div]:bg-yellow-500", strong: "h-2 [&>div]:bg-green-500", } <Progress value={meta.value} className={STRENGTH_BAR_CLASS[strength]} />
BUG-PC02:使用 document.getElementById 操作 DOM(未修复)
- 位置:
src/modules/settings/components/password-change-form.tsx:62-63 - 问题:
const form = document.getElementById("password-change-form") as HTMLFormElement | null直接操作 DOM - 规范依据:React 最佳实践 — 避免直接 DOM 操作
- 改进建议:使用
useRef<HTMLFormElement>或受控组件重置表单
BUG-PC03:as 断言使用(未修复)
- 位置:
src/modules/settings/components/password-change-form.tsx:62 - 问题:
as HTMLFormElement | null使用类型断言 - 规范依据:编码规范 4.2.3「禁止
as断言」 - 改进建议:使用
useRef后通过 ref.current 的类型推导
2.18 profile-settings-form.tsx — 严重度:高
BUG-PS01:使用 as any 类型断言(严重违规,未修复)
- 位置:
src/modules/settings/components/profile-settings-form.tsx:35 - 问题:
resolver: zodResolver(profileFormSchema) as any使用as any - 规范依据:项目规则「禁止
any」「禁止as断言」 - 改进建议:修复
zodResolver类型不匹配问题import type { Resolver } from "react-hook-form" const resolver: Resolver<ProfileFormValues> = zodResolver(profileFormSchema)
BUG-PS02:console.error 残留(未修复)
- 位置:
src/modules/settings/components/profile-settings-form.tsx:64 - 问题:
console.error(error)在生产代码中残留 - 规范依据:编码规范 — 生产代码不应包含
console.* - 改进建议:移除或替换为日志服务
BUG-PS03:onSubmit 未 useCallback(未修复)
- 位置:
src/modules/settings/components/profile-settings-form.tsx:47-67 - 改进建议:使用
useCallback
BUG-PS04:age 字段使用 z.coerce.number() 但未处理 NaN(未修复)
- 位置:
src/modules/settings/components/profile-settings-form.tsx:25 - 问题:
age: z.coerce.number().min(0).optional()当输入为空字符串时会转换为0,而非undefined - 改进建议:使用
z.preprocess处理空值
2.19 notification-preferences-form.tsx — 严重度:中
BUG-NPF01:Switch 与隐藏 checkbox 状态同步问题(未修复)
- 位置:
src/modules/settings/components/notification-preferences-form.tsx:186-201,233-248 - 问题:同时使用隐藏
<input type="checkbox">和<Switch>,两者都调用toggleChannel/toggleCategory,可能导致双重切换 - 影响:用户点击 Switch 时,
onCheckedChange触发;同时隐藏 checkbox 的onChange也触发,导致状态切换两次回到原点 - 改进建议:移除隐藏 checkbox,仅使用 Switch + 隐藏 input(
type="hidden")提交表单<input type="hidden" name={item.key} value={checked ? "true" : "false"} /> <Switch checked={checked} onCheckedChange={() => toggleChannel(item.key)} aria-label={item.label} />
BUG-NPF02:本地状态与服务器状态可能不同步(未修复)
- 位置:
src/modules/settings/components/notification-preferences-form.tsx:122-133 - 问题:
useState初始化自preferencesprop,但 prop 变化时状态不更新 - 违反规则:
rerender-derived-state-no-effect - 改进建议:使用
keyprop 重置组件,或使用受控组件
BUG-NPF03:中文注释混合英文代码(未修复)
- 位置:
src/modules/settings/components/notification-preferences-form.tsx:161,186,209 - 问题:
{/* 通知渠道 */}、{/* 隐藏的 checkbox 用于表单提交 */}、{/* 通知类别 */}中文注释 - 规范依据:项目代码一致性(其他文件使用英文注释)
- 改进建议:统一为英文注释
2.20 theme-preferences-card.tsx — 严重度:低
BUG-TP01:"use client" 后缺少空行(未修复)
- 位置:
src/modules/settings/components/theme-preferences-card.tsx:1-2 - 问题:
"use client"紧跟import无空行 - 规范依据:
.prettierrc格式规范 - 改进建议:运行
npx prettier --write
BUG-TP02:setTheme 参数类型不安全(未修复)
- 位置:
src/modules/settings/components/theme-preferences-card.tsx:31 - 问题:
onValueChange={(v) => setTheme(v)}中v为string,但setTheme期望特定类型 - 改进建议:使用类型守卫或 next-themes 提供的类型
2.21 ai-provider-settings-card.tsx — 严重度:高
BUG-AI01:中英文混合 UI(严重一致性违规,未修复)
- 位置:
src/modules/settings/components/ai-provider-settings-card.tsx:298,306,325,352,367 - 问题:
- FormLabel 使用中文「品牌方」「设为默认」
- FormDescription 使用中文「填写基础地址,不要包含 /chat/completions。」「不会回显历史 Key,留空表示不更新。」
- SelectItem 使用中文「智谱」
- 规范依据:Web Interface Guidelines — Consistency;项目其他 UI 均为英文
- 影响:用户在英文界面中突然看到中文,体验割裂
- 改进建议:统一为英文
<FormLabel>Provider</FormLabel> <FormDescription>Enter base URL without /chat/completions suffix.</FormDescription> <FormLabel>Set as default</FormLabel> <FormDescription>Existing key won't be displayed. Leave blank to keep current.</FormDescription> <SelectItem value="zhipu">Zhipu</SelectItem>
BUG-AI02:useEffect 依赖项过多导致重复执行(未修复)
- 位置:
src/modules/settings/components/ai-provider-settings-card.tsx:108-136 - 问题:
useEffect依赖[form, selectedId, onProvidersChanged, initialMode, resetToNew],但使用loadedRef防止重复执行 - 违反规则:
rerender-dependencies— 应使用原始依赖 - 改进建议:将初始化逻辑移至
useEffect内部,依赖项仅为[](仅执行一次)
BUG-AI03:handleSelectChange 未 useCallback(未修复)
- 位置:
src/modules/settings/components/ai-provider-settings-card.tsx:138-156 - 改进建议:使用
useCallback
BUG-AI04:文件行数 405 行,接近上限(未修复)
- 位置:
src/modules/settings/components/ai-provider-settings-card.tsx - 问题:文件 405 行,项目规则建议 React 组件 ≤ 500 行,但复杂度较高
- 改进建议:考虑拆分为
AiProviderSelect、AiProviderForm、AiProviderTestButton子组件
2.22 admin-settings-view.tsx — 严重度:低
BUG-AS01:Tab 图标语义错误(未修复)
- 位置:
src/modules/settings/components/admin-settings-view.tsx:50-53 - 问题:
appearanceTab 使用<Shield />图标(盾牌通常表示安全),应使用<Palette />或<Monitor /> - 规范依据:Web Interface Guidelines — Iconography
- 改进建议:
<TabsTrigger value="appearance"><Palette /></TabsTrigger>(注意:student-settings-view.tsx和teacher-settings-view.tsx已正确使用Palette,仅 admin 视图未修复)
BUG-AS02:signOut 直接调用未确认(未修复)
- 位置:
src/modules/settings/components/admin-settings-view.tsx:120 - 问题:
onClick={() => signOut({ callbackUrl: "/login" })}直接登出,无确认对话框 - 规范依据:Web Interface Guidelines — Destructive Actions
- 改进建议:增加确认对话框
2.23 teacher-settings-view.tsx — 严重度:低
BUG-TS01:与 admin-settings-view.tsx 大量重复代码(未修复)
- 位置:
src/modules/settings/components/teacher-settings-view.tsx - 问题:与
admin-settings-view.tsx、student-settings-view.tsx90% 代码重复,仅「Back to dashboard」链接和「Quick links」不同 - 规范依据:DRY 原则
- 改进建议:抽取为
SettingsLayout共享组件
2.24 student-settings-view.tsx — 严重度:低
BUG-ST01:同 BUG-TS01,代码重复(未修复)
- 改进建议:同 BUG-TS01
2.25 grade-classes-view.tsx — 严重度:高
BUG-GC01:文件 455 行,接近 500 行建议上限(未修复)
- 位置:
src/modules/classes/components/grade-classes-view.tsx - 问题:单文件 455 行,包含列表、创建对话框、编辑对话框、删除确认对话框
- 规范依据:项目规则「React 组件:建议 ≤ 500 行」
- 改进建议:拆分为:
grade-classes-view.tsx(主视图,< 100 行)grade-class-create-dialog.tsxgrade-class-edit-dialog.tsxgrade-class-delete-dialog.tsx
BUG-GC02:useEffect 依赖项导致不必要重渲染(未修复)
- 位置:
src/modules/classes/components/grade-classes-view.tsx:62-78 - 问题:两个
useEffect依赖managedGrades数组引用,父组件每次传入新数组都会触发 - 违反规则:
rerender-dependencies - 改进建议:依赖
managedGrades[0]?.id而非整个数组
BUG-GC03:中英文混合 UI(未修复)
- 位置:
src/modules/classes/components/grade-classes-view.tsx:183-184,283,370,389 - 问题:表头「班主任」「任课老师」使用中文,其他列使用英文
- 规范依据:Web Interface Guidelines — Consistency
- 改进建议:统一为英文
Homeroom Teacher、Subject Teachers
BUG-GC04:formatSubjectTeachers 在每次渲染时重新创建(未修复)
- 位置:
src/modules/classes/components/grade-classes-view.tsx:140-146 - 问题:函数在组件内定义,每次渲染创建新引用
- 改进建议:移至模块级别(不依赖组件状态)
三、React 性能优化(应用 vercel-react-best-practices 技能)
3.1 重渲染优化
PERF-01:usePermission 返回的回调未 memoize(未修复)
- 位置:
src/shared/hooks/use-permission.ts:11-25 - 问题:
hasPermission、hasAnyPermission、hasAllPermissions、hasRole每次渲染创建新函数引用 - 违反规则:
rerender-functional-setstate、rerender-memo - 影响:
message-list.tsx、message-detail.tsx中使用usePermission()的组件每次渲染都创建新canSend/canDelete值 - 改进建议:使用
useCallback包裹所有回调
PERF-02:AnnouncementCard 的 useMemo 无效(未修复)
- 位置:
src/modules/announcements/components/announcement-card.tsx:38-68 - 问题:
useMemo依赖[announcement](对象),父组件每次渲染传入新引用,memo 失效 - 违反规则:
rerender-simple-expression-in-memo - 改进建议:移除
useMemo,使用React.memo包裹组件
PERF-06:ai-provider-settings-card.tsx 使用 loadedRef 防止重复加载(未修复)
- 位置:
src/modules/settings/components/ai-provider-settings-card.tsx:66,109-110 - 问题:使用
loadedRef而非空依赖useEffect - 违反规则:
rerender-dependencies - 改进建议:使用空依赖数组
[]+ 清理函数
3.2 服务端性能
PERF-08:profile/page.tsx 数据加载未使用 cache()(未修复)
- 位置:
src/app/(dashboard)/profile/page.tsx:49-117 - 问题:学生数据加载逻辑内联在组件中,无法被 React
cache()去重 - 违反规则:
server-cache-react - 改进建议:抽取为
data-access.ts中的cache()包裹函数
PERF-09:messages/[id]/page.tsx 渲染期间写操作(未修复)
- 位置:
src/app/(dashboard)/messages/[id]/page.tsx:20-23 - 问题:渲染期间调用
markMessageAsRead执行写操作 - 违反规则:
server-after-nonblocking - 改进建议:使用
after()API
四、Web 界面规范审查(应用 web-design-guidelines 技能)
4.1 Hydration Safety
UI-01:usePermission 导致 hydration 闪烁(未修复)
- 位置:
src/modules/messaging/components/message-list.tsx:30-31、src/modules/messaging/components/message-detail.tsx:41-43 - 问题:
usePermission()依赖useSession(),服务端渲染时无权限,客户端 hydration 后权限相关 UI(Compose、Reply、Delete 按钮)闪烁出现 - 违反规则:Web Interface Guidelines — Hydration Safety
- 改进建议:将权限判断结果作为 prop 从 RSC 父组件传入
UI-02:theme-preferences-card.tsx 已使用 suppressHydrationWarning(已修复 ✅)
- 位置:
src/modules/settings/components/theme-preferences-card.tsx:32 - 现状:✅ 已正确处理主题切换的 hydration 问题
4.2 Navigation & State
UI-03:使用 <a href> 导致全页刷新(未修复)
- 位置:多处(BUG-AL01、BUG-AD01、BUG-MD01、BUG-MC01)
- 问题:使用原生
<a>而非<Link>,破坏 SPA 导航 - 违反规则:Web Interface Guidelines — Navigation
- 改进建议:全部替换为
next/link
UI-04:announcement-list.tsx 筛选状态未反映在 URL(未修复)
- 位置:
src/modules/announcements/components/announcement-list.tsx:51-57 - 问题:
handleFilterChange使用router.replace(qs ? ?${qs} : ?)更新 URL ✅,但初始filter状态来自initialStatusprop 而非 URL - 改进建议:使用
useSearchParams读取 URL 状态
UI-05:message-detail.tsx 回复链接 URL 参数构建不严谨(未修复)
- 位置:
src/modules/messaging/components/message-detail.tsx:69-71 - 问题:手动拼接 URL 参数,未使用
URLSearchParams - 改进建议:见 BUG-MD03
4.3 Forms
UI-06:management/grade/insights/page.tsx label 未关联 select(未修复)
- 位置:
src/app/(dashboard)/management/grade/insights/page.tsx:71 - 问题:
<label>缺少htmlFor - 违反规则:Web Interface Guidelines — Forms
- 改进建议:见 BUG-MI03
UI-08:message-compose.tsx 表单提交使用 formData.set 而非受控组件(未修复)
- 位置:
src/modules/messaging/components/message-compose.tsx:46-49 - 问题:混合使用受控(
receiverIdstate)和非受控(FormData)模式 - 改进建议:统一使用受控组件或完全使用 FormData
4.4 Content & Copy
UI-09:中英文混合 UI(未修复)
- 位置:
ai-provider-settings-card.tsx:BUG-AI01grade-classes-view.tsx:BUG-GC03notification-preferences-form.tsx:BUG-NPF03(注释)
- 违反规则:Web Interface Guidelines — Consistency
- 改进建议:统一为英文
UI-10:错误消息缺少修复步骤(未修复)
- 位置:
src/app/(dashboard)/error.tsx:13 - 问题:
"We apologize for the inconvenience. An unexpected error occurred."未提供下一步操作 - 违反规则:Web Interface Guidelines — Content & Copy
- 改进建议:增加「联系管理员」链接或错误码展示
UI-11:admin-settings-view.tsx Tab 图标语义错误(未修复)
- 位置:
src/modules/settings/components/admin-settings-view.tsx:50-53 - 问题:Appearance Tab 使用 Shield 图标
- 违反规则:Web Interface Guidelines — Iconography
- 改进建议:见 BUG-AS01
4.5 Accessibility
UI-12:notification-list.tsx icon 按钮缺少 aria-label(未修复)
- 位置:
src/modules/messaging/components/notification-list.tsx:118-124 - 问题:「Mark as read」按钮文本存在,但图标按钮模式未统一
- 改进建议:确保所有图标按钮有
aria-label
UI-13:layout.tsx 跳过链接样式冗长(未修复)
- 位置:
src/app/(dashboard)/layout.tsx:12 - 问题:跳过链接使用大量
focus:前缀类名,难以维护 - 改进建议:抽取为独立样式或组件
4.6 Performance
UI-14:management/grade/insights/page.tsx 表单提交整页刷新(未修复)
- 位置:
src/app/(dashboard)/management/grade/insights/page.tsx:70 - 问题:原生 form GET 提交导致整页刷新
- 违反规则:Web Interface Guidelines — Performance
- 改进建议:见 BUG-MI04
UI-15:profile/page.tsx 内联数据处理逻辑(未修复)
- 位置:
src/app/(dashboard)/profile/page.tsx:59-108 - 问题:在组件内执行数组排序、过滤等耗时操作
- 改进建议:移至 data-access 层
五、架构文档同步问题
5.1 004_architecture_impact_map.md
DOC-01:announcements 模块未记录页面缺少权限校验(已过时 ✅)
- 位置:004 文档 2.16 节
- 问题:v1 报告中标记的「
app/(dashboard)/announcements/page.tsx完全缺少权限校验」已修复 - 改进建议:更新架构文档,移除「缺少权限校验」的已知问题,标记为 ✅ 已修复
DOC-02:management 模块未在架构文档中独立记录(未修复)
- 位置:004 文档
- 问题:
app/(dashboard)/management/grade/路由未在架构文档中记录其依赖关系 - 改进建议:补充 management 路由的模块依赖(classes、school)
DOC-03:settings 模块文件清单过期(未修复)
- 位置:004 文档 2.23 节
- 问题:记录
components/* | 8 文件,但实际有 8 个文件 ✅,需核对行数 - 改进建议:核对并更新各文件行数
5.2 005_architecture_data.json
DOC-04:缺少 management 路由记录(未修复)
- 改进建议:在
routes数组中补充 management 路由
六、问题汇总统计
v2 总体统计
| 严重度 | 数量 | 问题编号 |
|---|---|---|
| 高 | 7 | BUG-MI01, BUG-P01, BUG-P02, BUG-PC01, BUG-PS01, BUG-AI01, BUG-GC01 |
| 中 | 13 | BUG-D02, BUG-MI02, BUG-MI03, BUG-MI04, BUG-MSG02, BUG-P01(中), BUG-S01, BUG-L01, BUG-AL01, BUG-AD01, BUG-ML01, BUG-ML02, BUG-MD01, BUG-MD02, BUG-MC01, BUG-NL01, BUG-NPF01, BUG-AI02 |
| 低 | 12 | BUG-MSG03, BUG-P03, BUG-P04, BUG-P05, BUG-P06, BUG-S02, BUG-L02, BUG-E01, BUG-NF01, BUG-AC01, BUG-AD02, BUG-MC02, BUG-MC03, BUG-NL02, BUG-PC02, BUG-PC03, BUG-PS02, BUG-PS03, BUG-PS04, BUG-NPF02, BUG-NPF03, BUG-TP01, BUG-TP02, BUG-AI03, BUG-AI04, BUG-AS01, BUG-AS02, BUG-TS01, BUG-ST01, BUG-GC02, BUG-GC03, BUG-GC04 |
| 性能 | 5 | PERF-01, PERF-02, PERF-06, PERF-08, PERF-09 |
| 界面 | 12 | UI-01, UI-03, UI-04, UI-05, UI-06, UI-08, UI-09, UI-10, UI-11, UI-12, UI-13, UI-14, UI-15 |
| 文档 | 3 | DOC-02, DOC-03, DOC-04 |
| 合计 | 52 |
v1 → v2 修复进度
| 类别 | v1 数量 | v2 已修复 | v2 未修复 | 修复率 |
|---|---|---|---|---|
| 高严重度 | 9 | 2 | 7 | 22% |
| 中严重度 | 14 | 1 | 13 | 7% |
| 低严重度 | 13 | 1 | 12 | 8% |
| 性能 | 9 | 4 | 5 | 44% |
| 界面 | 15 | 3 | 12 | 20% |
| 文档 | 4 | 1 | 3 | 25% |
| 合计 | 64 | 12 | 52 | 19% |
七、修复优先级建议(v2)
P0(立即修复 — 影响安全与正确性,仍未修复)
- BUG-MI01:
management/grade/insights/page.tsx权限校验升级为requirePermission() - BUG-PC01:
password-change-form.tsx修复动态类名拼接(生产环境进度条无颜色) - BUG-PS01:
profile-settings-form.tsx移除as any - BUG-AI01:
ai-provider-settings-card.tsx统一 UI 语言为英文 - BUG-P01:
profile/page.tsx使用ctx.roles判断角色 - BUG-P02:
profile/page.tsx抽取数据加载逻辑到 data-access - BUG-GC01:
grade-classes-view.tsx拆分组件
P1(本迭代修复 — 影响可维护性与性能)
- BUG-S01:
settings/page.tsx使用ctx.roles判断角色 - BUG-MSG02:
messages/[id]/page.tsx使用after()延迟写操作 - BUG-AL01、BUG-AD01、BUG-MD01、BUG-MC01:替换
<a>为<Link> - BUG-ML01、BUG-NL01:使用
cn()替换字符串拼接 - PERF-01:
usePermission回调 memoize - UI-01:权限相关 UI 改为 RSC prop 传入
- BUG-NPF01:
notification-preferences-form.tsx修复 Switch/checkbox 双重切换 - BUG-PS02:移除
console.error
P2(下迭代修复 — 增强健壮性)
- BUG-MI02、BUG-MI03、BUG-MI04:
management/grade/insights改用 shadcn Select - BUG-PC02、BUG-PC03:
password-change-form.tsx使用useRef替代document.getElementById - BUG-TS01、BUG-ST01:抽取
SettingsLayout共享组件 - BUG-AS01:修复 admin Tab 图标语义
- UI-10:错误页增加修复步骤
- BUG-GC03:统一
grade-classes-view.tsxUI 语言 - BUG-NPF03:统一注释语言
P3(文档同步)
- DOC-01:更新架构文档,标记 announcements 权限校验已修复
- DOC-02、DOC-04:补充 management 路由记录
- DOC-03:核对 settings 模块文件行数
八、验证命令
修复完成后应运行以下命令确保零错误:
npm run lint
npx tsc --noEmit
npm run test:unit
针对特定模块的端到端验证:
# 验证权限校验
curl -I http://localhost:3000/management/grade/insights # 应返回 302 重定向到 /login
curl -I http://localhost:3000/profile # 应返回 302
curl -I http://localhost:3000/settings # 应返回 302
# 验证 hydration
# 在浏览器控制台检查无 hydration warning
# 验证 Tailwind 类名(BUG-PC01 修复后)
# 检查密码强度进度条在生产环境显示正确颜色
九、v2 新增发现
9.1 新增问题
NEW-01:profile-settings-form.tsx 错误处理改进但仍不完整
- 位置:
src/modules/settings/components/profile-settings-form.tsx:57-61 - 问题:v1 中
onSubmit仅toast.success,v2 已增加result.success分支处理 ✅,但仍未处理result.errors(字段级错误) - 改进建议:使用 react-hook-form 的
setError设置字段级错误
9.2 修复质量评估
GOOD-01:dashboard/page.tsx 角色判断修复质量良好 ✅
- 位置:
src/app/(dashboard)/dashboard/page.tsx:10-15 - 评估:v2 使用
roles.includes("admin"/"student"/"parent")替代权限反推,逻辑清晰,优先级明确
GOOD-02:management/grade/classes/page.tsx 权限校验修复质量良好 ✅
- 位置:
src/app/(dashboard)/management/grade/classes/page.tsx:9-10 - 评估:v2 使用
requirePermission(GRADE_MANAGE)并通过ctx.userId获取用户 ID,消除了空字符串隐患
GOOD-03:notification-list.tsx button type 修复 ✅
- 位置:
src/modules/messaging/components/notification-list.tsx:119 - 评估:v2 已添加
type="button",防止意外表单提交
报告生成人:AI Agent(GLM-5.2) 核查方法:人工逐行审查 + 架构图比对 + 技能规则匹配 + v1 对比 应用技能:
vercel-react-best-practices(65 条规则)、web-design-guidelines(Web Interface Guidelines) 前置版本:others_bug.md v1 修复进度:12/64(19%),其中高严重度修复 2/9(22%)