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.
30 KiB
30 KiB
src/app/(dashboard)/parent 前端规范核查报告
核查日期:2026-06-18 核查范围:
src/app/(dashboard)/parent/下所有前端文件 +src/modules/parent/配套组件与 data-access 依据文档:项目规则、编码规范docs/standards/coding-standards.md、架构影响地图 004、架构数据 005 应用技能:vercel-react-best-practices、web-artifacts-builder、web-design-guidelines
一、核查文件清单
1.1 路由页面文件(src/app/(dashboard)/parent/)
| 文件 | 行数 | 类型 | 用途 |
|---|---|---|---|
| dashboard/page.tsx | 16 | Server Component | 家长仪表盘入口页 |
| attendance/page.tsx | 61 | Server Component | 子女考勤聚合页 |
| grades/page.tsx | 61 | Server Component | 子女成绩聚合页 |
| children/[studentId]/page.tsx | 71 | Server Component | 单个子女详情页 |
1.2 模块组件文件(src/modules/parent/components/)
| 文件 | 行数 | 类型 | 用途 |
|---|---|---|---|
| parent-dashboard.tsx | 68 | Server Component | 仪表盘主组件 |
| child-card.tsx | 89 | Server Component | 子女卡片 |
| child-detail-header.tsx | 49 | Server Component | 详情页头部 |
| child-detail-panel.tsx | 27 | Server Component | 详情页面板 |
| child-grade-summary.tsx | 163 | Client Component | 成绩趋势图 |
| child-homework-summary.tsx | 131 | Server Component | 作业摘要 |
| child-schedule-card.tsx | 67 | Server Component | 今日课表 |
1.3 数据访问与类型(src/modules/parent/)
| 文件 | 行数 | 类型 | 用途 |
|---|---|---|---|
| data-access.ts | 234 | server-only | 家长-子女数据聚合 |
| types.ts | 57 | 类型定义 | 模块类型 |
二、违规问题清单
2.1 children/[studentId]/page.tsx — 严重度:高(架构违规)
BUG-P001:app 层直接访问 DB,违反三层架构
- 位置:
src/app/(dashboard)/parent/children/[studentId]/page.tsx:2-6, 24-31 - 问题:页面直接
import { db }和parentStudentRelationsschema,并执行db.select().from(parentStudentRelations)查询 - 规范依据:项目规则「架构分层规则」明确「
app/只能调用modules/的 Server Actions 和 data-access,不直接访问 DB」 - 现状:
import { db } from "@/shared/db" import { parentStudentRelations } from "@/shared/db/schema" // ... const [relation] = await db .select({ id: parentStudentRelations.id, relation: parentStudentRelations.relation }) .from(parentStudentRelations) .where(eq(parentStudentRelations.studentId, studentId)) .limit(1) - 改进建议:在
parent/data-access.ts新增verifyParentChildRelation(studentId, parentId)函数,页面调用该函数
BUG-P002:权限校验存在信息泄露风险
- 位置:
src/app/(dashboard)/parent/children/[studentId]/page.tsx:24-31 - 问题:第一次查询 relation 时仅按
studentId过滤,未加parentId = ctx.userId条件。任何登录用户都能探测任意 studentId 是否存在 parent 关系 - 影响:信息泄露(可枚举 studentId 探测家庭关系)
- 改进建议:查询条件加
and(eq(parentStudentRelations.studentId, studentId), eq(parentStudentRelations.parentId, ctx.userId))
BUG-P003:两个 "Access denied" 分支重复
- 位置:
src/app/(dashboard)/parent/children/[studentId]/page.tsx:33-58 - 问题:relation 不存在与 dataScope 不包含两个分支返回完全相同的 UI,代码重复
- 改进建议:合并为单一校验路径,或抽取
AccessDenied组件
BUG-P004:requireAuth() 未做角色校验
- 位置:
src/app/(dashboard)/parent/children/[studentId]/page.tsx:21 - 问题:使用
requireAuth()而非requirePermission(),未校验当前用户是否为 parent 角色。teacher/admin 也能访问该页面(虽然 dataScope 校验会拦截,但应前置失败) - 改进建议:使用
requirePermission(PARENT_VIEW)或在 auth-guard 中增加角色校验
2.2 attendance/page.tsx 与 grades/page.tsx — 严重度:高(代码重复)
BUG-P005:两个页面文件几乎完全重复
-
位置:
src/app/(dashboard)/parent/attendance/page.tsx与src/app/(dashboard)/parent/grades/page.tsx -
问题:两个文件结构 95% 相同,仅模块名(attendance vs grades)、图标(CalendarCheck vs GraduationCap)、标题文案不同
-
违反规则:DRY 原则,编码规范「工具函数 ≤ 40 行」隐含的复用精神
-
改进建议:抽取共享组件
ParentChildrenDataPage,通过 props 传入fetcher、icon、title、emptyTitle、renderItem// 抽取后的共享组件 function ParentChildrenDataPage<T>({ title, description, icon, fetcher, renderItem, emptyTitle, emptyDescription, }: ParentChildrenDataPageProps<T>) { /* ... */ }
BUG-P006:Promise.all 内部异常未处理
- 位置:
src/app/(dashboard)/parent/attendance/page.tsx:29-31、grades/page.tsx:29-31 - 问题:
ctx.dataScope.childrenIds.map((id) => getStudentAttendanceSummary(id))若任一查询抛错,整个页面 500。未做 try-catch 或 Promise.allSettled - 改进建议:使用
Promise.allSettled并过滤 rejected,或对单个子女查询失败显示局部错误状态
2.3 dashboard/page.tsx — 严重度:中
BUG-P007:缺少 dataScope 空状态处理
- 位置:
src/app/(dashboard)/parent/dashboard/page.tsx:7-15 - 问题:未检查
ctx.dataScope.type === "children"或childrenIds.length === 0,直接调用getParentDashboardData(ctx.userId)。虽然 data-access 会返回空数组,但与 attendance/grades 页面的处理方式不一致 - 改进建议:与 attendance/grades 页面统一,前置检查 dataScope
2.4 parent-dashboard.tsx — 严重度:中
BUG-P008:使用 <a href> 而非 <Link>,丢失客户端导航
- 位置:
src/modules/parent/components/parent-dashboard.tsx:30, 36 - 问题:
<a href="/parent/grades">和<a href="/announcements">使用原生<a>标签,导致整页刷新,丢失 Next.js 客户端路由优化 - 违反规则:Web Interface Guidelines — Navigation & State「Links use
<a>/<Link>(Cmd/Ctrl+click, middle-click support)」;Next.js 最佳实践 - 改进建议:
import Link from "next/link",使用<Link href="/parent/grades">
BUG-P009:问候语使用 new Date().getHours() 存在时区风险
- 位置:
src/modules/parent/components/parent-dashboard.tsx:12-16 - 问题:服务端渲染时使用服务器时区计算问候语,与用户实际时区可能不符(例如部署在 UTC 服务器,北京时间早 8 点用户看到 "Good afternoon")
- 违反规则:Web Interface Guidelines — Locale & i18n「Dates/times: use
Intl.DateTimeFormatnot hardcoded formats」 - 改进建议:使用
Intl.DateTimeFormat(undefined, { hour: "numeric", timeZone: ctx.timezone })或将问候语计算移至客户端组件
BUG-P010:标题层级与间距与其他页面不一致
- 位置:
src/modules/parent/components/parent-dashboard.tsx:22vsattendance/page.tsx:16、grades/page.tsx::16 - 问题:dashboard 使用
text-3xl+space-y-6,attendance/grades 使用text-2xl+space-y-8 - 违反规则:web-artifacts-builder 设计一致性原则
- 改进建议:统一标题字号(建议
text-2xl)和间距(建议space-y-6)
2.5 child-card.tsx — 严重度:中
BUG-P011:getInitials 函数重复定义
- 位置:
src/modules/parent/components/child-card.tsx:9-12与src/modules/parent/components/child-detail-header.tsx:9-12 - 问题:两个文件定义了完全相同的
getInitials函数 - 违反规则:DRY 原则
- 改进建议:抽取到
src/modules/parent/lib/utils.ts或src/shared/lib/utils.ts
BUG-P012:使用字符串拼接动态类名,违反 Tailwind 规范
- 位置:
src/modules/parent/components/child-card.tsx:57-60 - 问题:
使用模板字符串拼接类名,违反编码规范「Tailwind 规范:使用
className={`text-lg font-semibold tabular-nums ${ homeworkSummary.overdueCount > 0 ? "text-destructive" : "" }`}cn()工具函数管理条件类名」 - 对比:同模块
child-homework-summary.tsx:72-75正确使用了cn() - 改进建议:
className={cn( "text-lg font-semibold tabular-nums", homeworkSummary.overdueCount > 0 && "text-destructive", )}
BUG-P013:手动截断标题,应使用 Tailwind truncate/line-clamp
- 位置:
src/modules/parent/components/child-card.tsx:81-83 - 问题:
手动 slice + "..." 截断,违反 Web Interface Guidelines — Typography「
({latestGrade.assignmentTitle.slice(0, 20)} {latestGrade.assignmentTitle.length > 20 ? "..." : ""})…not...」 - 改进建议:使用
<span className="truncate inline-block max-w-[200px]">{latestGrade.assignmentTitle}</span>
BUG-P014:cursor-pointer 在 Link 上冗余
- 位置:
src/modules/parent/components/child-card.tsx:21 - 问题:
<Card className="hover:bg-muted/50 transition-colors cursor-pointer h-full">中cursor-pointer冗余(外层<Link>默认 pointer) - 违反规则:Web Interface Guidelines — Anti-patterns
- 改进建议:移除
cursor-pointer
BUG-P015:整个 Card 作为 Link 缺少可访问性描述
- 位置:
src/modules/parent/components/child-card.tsx:20-87 - 问题:
<Link>包裹整个 Card,屏幕阅读器会读出所有内部文本(姓名、班级、数字、最新成绩),缺少简洁的 aria-label - 违反规则:Web Interface Guidelines — Accessibility「Icon-only buttons need
aria-label」延伸到卡片导航 - 改进建议:
<Link href={...} aria-label={查看 ${basicInfo.name ?? "子女"} 的详情}>
BUG-P016:Link 缺少 focus-visible:ring 样式
- 位置:
src/modules/parent/components/child-card.tsx:20-21 - 问题:
<Link>包裹 Card,但 Card 没有focus-visible:ring-*样式,键盘导航时无可见焦点 - 违反规则:Web Interface Guidelines — Focus States「Interactive elements need visible focus」
- 改进建议:添加
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2
2.6 child-detail-header.tsx — 严重度:低
BUG-P017:getInitials 重复(同 BUG-P011)
- 位置:
src/modules/parent/components/child-detail-header.tsx:9-12 - 改进建议:见 BUG-P011
BUG-P018:邮箱直接展示未做防爬处理
- 位置:
src/modules/parent/components/child-detail-header.tsx:43 - 问题:
<span>· {basicInfo.email}</span>直接展示子女邮箱,无防爬/掩码处理 - 改进建议:考虑隐私场景下掩码处理(如
j***@example.com),或仅对家长本人可见时展示完整
2.7 child-grade-summary.tsx — 严重度:中
BUG-P019:"use client" 必要性可优化
- 位置:
src/modules/parent/components/child-grade-summary.tsx:1 - 问题:组件标记为
"use client",但实际仅recharts需要客户端。整个组件(包括数据预处理chartData计算)都被打包到客户端 bundle - 违反规则:
vercel-react-best-practices—bundle-dynamic-imports「Use next/dynamic for heavy components」 - 改进建议:将图表部分抽取为独立的客户端组件
GradeTrendChart,父组件保持服务端组件,通过next/dynamic懒加载图表
BUG-P020:latestGrade 取数组末尾,语义不明确
- 位置:
src/modules/parent/components/child-grade-summary.tsx:32 - 问题:
const latestGrade = grades.trend[grades.trend.length - 1]假设 trend 是按时间升序排列,但类型定义StudentDashboardGradeProps未明确顺序 - 对比:
child-card.tsx:17使用gradeTrend.recent[0]取最新(假设 recent 是降序) - 改进建议:在
homework/types.ts的StudentDashboardGradeProps中补充 JSDoc 说明trend和recent的排序语义
BUG-P021:chartData 在每次渲染时重新计算
- 位置:
src/modules/parent/components/child-grade-summary.tsx:34-41 - 问题:
const chartData = grades.trend.map(...)每次渲染都重新 map,未 memoize - 违反规则:
vercel-react-best-practices—rerender-memo「Extract expensive work into memoized components」 - 改进建议:
const chartData = useMemo(() => grades.trend.map(...), [grades.trend])
BUG-P022:tickFormatter 内联函数每次渲染创建新引用
- 位置:
src/modules/parent/components/child-grade-summary.tsx:99-101 - 问题:
tickFormatter={(value) => value.slice(0, 8) + (value.length > 8 ? "..." : "")}内联箭头函数 - 违反规则:Web Interface Guidelines — Typography「
…not...」;vercel-react-best-practices—rerender-functional-setstate - 改进建议:抽取为模块级纯函数
const formatTick = (v: string) => v.slice(0, 8) + (v.length > 8 ? "…" : "")
BUG-P023:使用 "..." 应为 …
- 位置:
src/modules/parent/components/child-grade-summary.tsx:100 - 问题:
value.length > 8 ? "..." : ""使用三个英文句号,应为省略号字符… - 违反规则:Web Interface Guidelines — Typography「
…not...」
2.8 child-homework-summary.tsx — 严重度:低
BUG-P024:状态字符串硬编码,应使用枚举/常量
- 位置:
src/modules/parent/components/child-homework-summary.tsx:10-21 - 问题:
getStatusVariant和getStatusLabel使用硬编码字符串"graded"、"submitted"、"in_progress"比较 - 改进建议:从
homework/types.ts导入状态常量或联合类型,使用 switch + exhaustive check
BUG-P025:getDueUrgency 在渲染期调用 new Date()
- 位置:
src/modules/parent/components/child-homework-summary.tsx:23-31, 95 - 问题:每次
map迭代都调用new Date()创建新日期对象,虽然性能影响小,但语义上应在外层计算一次now - 改进建议:在组件顶部
const now = new Date()一次,传入getDueUrgency(a.dueAt, now)
2.9 child-schedule-card.tsx — 严重度:低
BUG-P026:空状态高度与其他组件不一致
- 位置:
src/modules/parent/components/child-schedule-card.tsx:31 - 问题:
className="border-none h-60",而child-grade-summary.tsx:57使用h-60,child-homework-summary.tsx:87使用h-40 - 改进建议:统一空状态高度(建议
h-48)
2.10 data-access.ts — 严重度:中
BUG-P027:toWeekday 使用 as 类型断言
- 位置:
src/modules/parent/data-access.ts:28-31 - 问题:
使用
const toWeekday = (d: Date): 1 | 2 | 3 | 4 | 5 | 6 | 7 => { const day = d.getDay() return (day === 0 ? 7 : day) as 1 | 2 | 3 | 4 | 5 | 6 | 7 }as类型断言,违反编码规范「禁止as断言(除非从unknown转换)」 - 改进建议:使用类型守卫
const toWeekday = (d: Date): 1 | 2 | 3 | 4 | 5 | 6 | 7 => { const day = d.getDay() const weekday = day === 0 ? 7 : day if (weekday < 1 || weekday > 7) throw new Error(`Invalid weekday: ${weekday}`) return weekday }
BUG-P028:getChildBasicInfo 串行查询瀑布(架构图已标注 P2)
- 位置:
src/modules/parent/data-access.ts:58-117 - 问题:4 个串行 DB 查询:users → grades → classEnrollments → classes。其中 grades 和 classEnrollments 互相独立,可并行
- 违反规则:
vercel-react-best-practices—async-parallel「Use Promise.all() for independent operations」 - 架构图标注:004 文档 2.19 节「⚠️ P2:
getChildBasicInfo多次串行查询,可优化为 join」 - 改进建议:
或重构为单次 JOIN 查询
// grades 和 classEnrollments 并行 const [gradeRow, enrollment] = await Promise.all([ student.gradeId ? db.select({ name: grades.name }).from(grades).where(eq(grades.id, student.gradeId)).limit(1) : Promise.resolve([]), db.select({ classId: classEnrollments.classId, status: classEnrollments.status }) .from(classEnrollments) .where(and(eq(classEnrollments.studentId, studentId), eq(classEnrollments.status, "active"))) .orderBy(asc(classEnrollments.createdAt)) .limit(1), ])
BUG-P029:getChildBasicInfo 返回类型未显式标注
- 位置:
src/modules/parent/data-access.ts:58 - 问题:
export const getChildBasicInfo = cache(async (studentId: string, relation: string | null = null) => {未显式标注返回类型 - 违反规则:编码规范「函数返回值必须显式标注,特别是
Promise<T>」 - 改进建议:定义
ChildBasicInfo返回类型并显式标注(types.ts中已有ChildBasicInfo类型,可直接使用)
BUG-P030:buildHomeworkSummary 中 [...assignments].sort() 不必要的拷贝
- 位置:
src/modules/parent/data-access.ts:150-156 - 问题:
[...assignments].sort(...)创建数组副本再排序。assignments来自getStudentHomeworkAssignments返回的新数组,无需再拷贝 - 改进建议:直接
assignments.sort(...)或使用toSorted()(ES2023)
2.11 types.ts — 严重度:低
BUG-P031:类型缺少 JSDoc 文档注释
- 位置:
src/modules/parent/types.ts全文件 - 问题:所有类型(
ParentChildRelation、ChildBasicInfo、ChildScheduleItem、ChildHomeworkSummary、ChildDashboardData、ParentDashboardData)均无 JSDoc - 违反规则:编码规范 5.4「必须编写 JSDoc」
- 改进建议:为每个类型补充 JSDoc,说明用途、字段语义
BUG-P032:ChildHomeworkSummary 与组件同名类型冲突风险
- 位置:
src/modules/parent/types.ts:37与child-homework-summary.tsx:33 - 问题:类型名
ChildHomeworkSummary与组件名ChildHomeworkSummary完全相同,在child-homework-summary.tsx中同时 import 两者会造成命名冲突当前依赖 TypeScript 类型与值的命名空间分离才不冲突,但可读性差import type { ChildHomeworkSummary } from "@/modules/parent/types" // 类型 export function ChildHomeworkSummary({ summary }: { summary: ChildHomeworkSummary }) // 组件 - 改进建议:类型重命名为
ChildHomeworkSummaryData或组件重命名为ChildHomeworkSummaryCard
三、React 性能优化(应用 vercel-react-best-practices 技能)
PERF-P01:getChildBasicInfo 串行查询瀑布(同 BUG-P028)
- 违反规则:
async-parallel— Use Promise.all() for independent operations - 改进建议:见 BUG-P028
PERF-P02:chartData 未 memoize(同 BUG-P021)
- 违反规则:
rerender-memo— Extract expensive work into memoized components - 改进建议:见 BUG-P021
PERF-P03:child-grade-summary.tsx 整体客户端化,bundle 体积大
- 位置:
src/modules/parent/components/child-grade-summary.tsx:1 - 问题:
"use client"导致 recharts(~100KB)整体进入客户端 bundle,但组件大部分逻辑(数据预处理、布局)可在服务端完成 - 违反规则:
bundle-dynamic-imports— Use next/dynamic for heavy components - 改进建议:
// child-grade-summary.tsx (Server Component) import dynamic from "next/dynamic" const GradeTrendChart = dynamic(() => import("./grade-trend-chart").then(m => m.GradeTrendChart)) // 仅图表部分客户端化
PERF-P04:getParentDashboardData 内部 Promise.all 已正确并行化
- 位置:
src/modules/parent/data-access.ts:225-227 - 说明:✅ 已正确使用
Promise.all并行获取所有子女数据,符合async-parallel规范
PERF-P05:getChildDashboardData 内部 Promise.all 已正确并行化
- 位置:
src/modules/parent/data-access.ts:190-196 - 说明:✅ 已正确使用
Promise.all并行获取 enrolledClasses/schedule/assignments/gradeTrend/gradeSummary
PERF-P06:cache() 已正确包裹 data-access 函数
- 位置:
src/modules/parent/data-access.ts:33, 58, 185, 209 - 说明:✅ 所有 data-access 函数均使用 React
cache()包裹,符合server-cache-react规范,实现单次请求内去重
PERF-P07:parent-dashboard.tsx 中 new Date() 在服务端执行无 hydration 风险
- 位置:
src/modules/parent/components/parent-dashboard.tsx:12 - 说明:✅ 组件为 Server Component,
new Date()仅在服务端执行一次,无 hydration mismatch 风险(但有时区问题,见 BUG-P009)
四、Web 界面规范审查(应用 web-design-guidelines 技能)
UI-P01:parent-dashboard.tsx
src/modules/parent/components/parent-dashboard.tsx:30 - <a href> → use <Link> for client-side nav
src/modules/parent/components/parent-dashboard.tsx:36 - <a href> → use <Link> for client-side nav
src/modules/parent/components/parent-dashboard.tsx:12 - new Date() server-side, timezone mismatch risk
src/modules/parent/components/parent-dashboard.tsx:22 - title size inconsistent (text-3xl vs text-2xl in other pages)
UI-P02:child-card.tsx
src/modules/parent/components/child-card.tsx:20 - Link wrapping Card lacks aria-label
src/modules/parent/components/child-card.tsx:21 - cursor-pointer redundant on Link
src/modules/parent/components/child-card.tsx:21 - missing focus-visible:ring-* for keyboard nav
src/modules/parent/components/child-card.tsx:57 - string concatenation for className → use cn()
src/modules/parent/components/child-card.tsx:82 - "..." → "…"
src/modules/parent/components/child-card.tsx:82 - manual slice truncation → use truncate/line-clamp
UI-P03:child-grade-summary.tsx
src/modules/parent/components/child-grade-summary.tsx:100 - "..." → "…"
src/modules/parent/components/child-grade-summary.tsx:99 - inline tickFormatter → hoist to module scope
src/modules/parent/components/child-grade-summary.tsx:142 - Link lacks query param for tab deep-linking
UI-P04:child-homework-summary.tsx
src/modules/parent/components/child-homework-summary.tsx:98 - Link lacks query param for tab deep-linking
src/modules/parent/components/child-homework-summary.tsx:25 - new Date() in each map iteration → hoist to component scope
UI-P05:child-detail-header.tsx
src/modules/parent/components/child-detail-header.tsx:43 - email displayed without masking (privacy)
UI-P06:attendance/page.tsx & grades/page.tsx
src/app/(dashboard)/parent/attendance/page.tsx:14 - h-full flex-1 flex-col space-y-8 p-8 md:flex → inconsistent with dashboard/page.tsx (p-6 md:p-8)
src/app/(dashboard)/parent/grades/page.tsx:14 - same inconsistency as above
UI-P07:空状态一致性
src/modules/parent/components/child-schedule-card.tsx:31 - EmptyState h-60
src/modules/parent/components/child-grade-summary.tsx:57 - EmptyState h-60
src/modules/parent/components/child-homework-summary.tsx:87 - EmptyState h-40
src/app/(dashboard)/parent/attendance/page.tsx:24 - EmptyState border-none shadow-none (no height)
→ unify EmptyState height and className
五、界面优化建议(应用 web-artifacts-builder 技能)
UIX-P01:子女卡片网格响应式断点不足
- 位置:
src/modules/parent/components/parent-dashboard.tsx:59 - 问题:
grid-cols-1 md:grid-cols-2 lg:grid-cols-3在 sm 屏幕下强制单列,2 列布局在 sm(640px)下更合适 - 改进建议:
grid-cols-1 sm:grid-cols-2 lg:grid-cols-3
UIX-P02:详情页布局中等屏幕下右侧栏过窄
- 位置:
src/modules/parent/components/child-detail-panel.tsx:12 - 问题:
grid-cols-1 lg:grid-cols-3在 md(768-1024px)下为单列,但lg:col-span-2在 lg 下才生效,md 下左侧内容占满,右侧课表也在下方 - 改进建议:增加 md 断点
grid-cols-1 md:grid-cols-2 lg:grid-cols-3,左侧md:col-span-1 lg:col-span-2
UIX-P03:卡片内嵌套卡片视觉层级混乱
- 位置:
src/modules/parent/components/child-card.tsx:43-73 - 问题:Card 内部 CardContent 中又使用
rounded-md border bg-card p-2创建 3 个小卡片,与外层 Card 视觉层级冲突 - 改进建议:内部小卡片改用
bg-muted/50或移除 border,弱化层级
UIX-P04:作业摘要卡片缺少"查看全部"链接
- 位置:
src/modules/parent/components/child-homework-summary.tsx:90-126 - 问题:仅展示
recentAssignments(最多 5 条),无"查看全部作业"入口 - 改进建议:底部添加
<Link href="/parent/children/{childId}?tab=homework">View all</Link>
UIX-P05:成绩趋势图 X 轴标签截断后信息丢失
- 位置:
src/modules/parent/components/child-grade-summary.tsx:94-102 - 问题:
tickFormatter截断为 8 字符 + "…",多个作业标题前 8 字符相同时无法区分 - 改进建议:X 轴改为日期(
formatDate(submittedAt)),标题在 tooltip 中完整展示
UIX-P06:仪表盘快捷入口仅 2 个,可扩展
- 位置:
src/modules/parent/components/parent-dashboard.tsx:28-41 - 问题:仅有 Grades 和 Announcements 两个快捷按钮,缺少 Attendance、Schedule 等常用入口
- 改进建议:增加 Attendance 快捷入口,或改为下拉菜单
六、架构文档同步问题
DOC-P01:004 文档 parent 模块行数记录过期
- 位置:
docs/architecture/004_architecture_impact_map.md:924 - 问题:记录
data-access.ts | 234 | 子女关系 + 仪表盘数据聚合,实际 234 行 ✅ 一致;但components/* | 7 文件实际为 7 个组件文件 ✅ 一致 - 说明:本节核查后无需更新(行数与文件数均一致)
DOC-P02:004 文档未记录 children/[studentId]/page.tsx 的架构违规
- 位置:
docs/architecture/004_architecture_impact_map.md2.19 节 - 问题:未在 parent 模块「已知问题」中记录
app/(dashboard)/parent/children/[studentId]/page.tsx直接访问 DB 的违规(BUG-P001) - 改进建议:在 004 文档 2.19 节「已知问题」中补充:
- ❌ P1:`app/(dashboard)/parent/children/[studentId]/page.tsx` 直接访问 DB(违反三层架构)
DOC-P03:005 JSON 中 parent 模块的 routes 节点需补充
- 问题:若修复 BUG-P005(抽取共享组件),路由结构不变,但需在 005 JSON 中记录 attendance/grades 页面的 fetcher 依赖关系
- 改进建议:在
005_architecture_data.json的modules.parent.dependencies中补充attendance、grades模块依赖
七、问题汇总统计
| 严重度 | 数量 | 问题编号 |
|---|---|---|
| 高(架构违规/安全) | 6 | BUG-P001, BUG-P002, BUG-P004, BUG-P005, BUG-P006, BUG-P028 |
| 中(规范违规/性能) | 12 | BUG-P003, BUG-P007, BUG-P008, BUG-P009, BUG-P010, BUG-P011, BUG-P012, BUG-P019, BUG-P020, BUG-P021, BUG-P027, BUG-P029 |
| 低(代码质量/UX) | 13 | BUG-P013, BUG-P014, BUG-P015, BUG-P016, BUG-P017, BUG-P018, BUG-P022, BUG-P023, BUG-P024, BUG-P025, BUG-P026, BUG-P030, BUG-P031, BUG-P032 |
| 合计 | 31 | — |
按技能分类统计
| 技能 | 发现问题数 | 主要问题类型 |
|---|---|---|
| 项目规范核查 | 18 | 架构违规、代码重复、类型规范、Tailwind 规范 |
| vercel-react-best-practices | 7 | 串行查询瀑布、bundle 体积、memoize 缺失 |
| web-design-guidelines | 15 | 可访问性、焦点状态、排版、导航、空状态一致性 |
| web-artifacts-builder | 6 | 响应式断点、视觉层级、交互入口、图表可读性 |
八、修复优先级建议
P0(立即修复 — 架构与安全)
- BUG-P001:
children/[studentId]/page.tsx移除直接 DB 访问,下沉到parent/data-access.ts - BUG-P002:权限校验加
parentId条件,防止信息泄露 - BUG-P005:抽取
ParentChildrenDataPage共享组件,消除 attendance/grades 重复
P1(短期修复 — 规范与性能)
- BUG-P008:
<a href>改为<Link> - BUG-P012:
child-card.tsx使用cn()替代字符串拼接 - BUG-P011:抽取
getInitials到共享 utils - BUG-P028:
getChildBasicInfo并行化查询 - BUG-P019:
child-grade-summary.tsx拆分服务端/客户端组件 - BUG-P027:
toWeekday移除as断言 - BUG-P029:
getChildBasicInfo显式标注返回类型
P2(机会修复 — UX 与代码质量)
- BUG-P009:问候语时区处理
- BUG-P013:使用
truncate替代手动截断 - BUG-P015, BUG-P016:卡片可访问性增强
- BUG-P023:
...→… - BUG-P031:补充类型 JSDoc
- UIX-P01~P06:界面优化项
九、标杆实践(建议保留)
| 实践 | 位置 | 说明 |
|---|---|---|
cache() 包裹 data-access |
data-access.ts:33, 58, 185, 209 |
符合 server-cache-react,单次请求去重 |
Promise.all 并行获取子女数据 |
data-access.ts:190-196, 225-227 |
符合 async-parallel,消除瀑布 |
| Server Component 默认 | 7/8 组件为 Server Component | 仅 child-grade-summary.tsx 因 recharts 标记 client |
import type 正确使用 |
所有类型导入均使用 import type |
符合编码规范 4.2.6 |
server-only 标注 |
data-access.ts:1 |
防止 data-access 被客户端误引入 |
| 空状态处理完整 | 所有页面均使用 EmptyState 组件 |
UX 一致性良好 |
说明:本报告基于 2026-06-18 代码状态生成。修复后需同步更新
docs/architecture/004_architecture_impact_map.md2.19 节与005_architecture_data.json的 parent 模块节点。