- RBAC: 新增30个权限点、DataScope行级权限、requirePermission守卫,所有57+ Server Action接入权限校验 - UI拆分: exam-form(1623行→11文件)、textbook-reader(744行→7文件),均降至300行以内 - 测试: 新增5个单元测试文件(19用例),修复4个集成测试文件(38用例全部通过) - 架构文档: 新增架构影响地图(004/005)、标准功能清单(006)、差距审计报告(007) - 项目规则: 架构图优先规则,改码必同步图 - 安全: rehype-sanitize净化、AES加密API Key、权限路由守卫 - 无障碍: skip-link、aria-label、prefers-reduced-motion - 性能: next/font优化、next/image、代码分割
11 KiB
UI 代码结构重构计划
基于 2026-06-16 架构审核,整体评分 7.2/10 目标:补齐工程细节短板,达到企业级标准(8.5+)
审核评分总览
| 维度 | 评分 | 状态 |
|---|---|---|
| 目录结构分层 | 8/10 | 良好 |
| 组件设计 | 6.5/10 | 需重构 |
| 复用与抽象 | 6/10 | 需重构 |
| 样式组织 | 8.5/10 | 优秀 |
| 可维护性 | 7/10 | 一般 |
| 性能相关 | 7/10 | 一般 |
| 测试与质量保障 | 5.5/10 | 需重构 |
| 安全性 | 7.5/10 | 良好 |
P0 — 紧急(阻塞级质量问题)
P0-1 拆分巨型组件
问题: exam-form.tsx(1623 行)、textbook-reader.tsx(744 行)严重违反单一职责,难以维护和测试。
exam-form.tsx 拆分方案
src/modules/exams/components/
exam-form.tsx ← 保留为容器组件(~200 行),组合子组件
exam-basic-info-form.tsx ← 考试基本信息(名称、科目、时间)
exam-ai-generator.tsx ← AI 生成试卷功能
exam-structure-editor.tsx ← 试卷结构编辑
exam-question-selector.tsx ← 题目选择器
exam-preview-panel.tsx ← 试卷预览面板
exam-form-actions.tsx ← 表单操作按钮组
textbook-reader.tsx 拆分方案
src/modules/textbooks/components/
textbook-reader.tsx ← 保留为容器组件(~150 行)
textbook-content-panel.tsx ← 内容阅读面板(Markdown 渲染)
knowledge-point-list.tsx ← 知识点列表
knowledge-graph.tsx ← 知识图谱可视化
knowledge-point-dialogs.tsx ← 创建/编辑知识点 Dialog
textbook-editor-panel.tsx ← 编辑模式面板
use-text-selection.ts ← 文本选择逻辑 Hook
use-knowledge-point-actions.ts ← 知识点操作逻辑 Hook
验收标准:
- 单文件不超过 300 行
- 每个子组件职责单一,可独立测试
- 容器组件仅负责组合和状态分发
P0-2 修复无障碍(a11y)缺陷
问题: 全项目仅 7 处 aria-label,但 180 处 onClick;存在 <div onClick> 非语义化标签。
修复清单
-
图标按钮补 aria-label
- 搜索所有
<Button.*size="icon"或仅含图标的按钮,补充aria-label - 涉及文件:exam-actions.tsx、question-columns.tsx、exam-columns.tsx、breadcrumb.tsx 等
- 搜索所有
-
替换
<div onClick>为语义化标签- textbook-reader.tsx:434 — 知识点卡片 →
<button> - 搜索所有
<div onClick/<span onClick模式,逐一替换
- textbook-reader.tsx:434 — 知识点卡片 →
-
表单控件补 label
- 确认所有
<Input>/<Select>有关联<Label htmlFor>或aria-label
- 确认所有
-
添加 skip-link
- Root Layout 或 Dashboard Layout 添加:
<a href="#main-content" className="sr-only focus:not-sr-only ..."> Skip to main content </a> <main>添加id="main-content"
- Root Layout 或 Dashboard Layout 添加:
验收标准:
- axe-core 扫描零严重违规
- 所有图标按钮有
aria-label - 无
<div onClick>/<span onClick>模式
P1 — 重要(架构级改进)
P1-1 创建自定义 Hooks 层
问题: 整个项目零自定义 Hook,逻辑耦合在组件中。
通用 Hooks(src/shared/hooks/)
src/shared/hooks/
use-action-with-toast.ts ← Server Action + toast 反馈
use-async-action.ts ← 异步操作 loading/error 状态
use-debounce.ts ← 防抖
use-media-query.ts ← 响应式断点
use-local-storage.ts ← 本地存储
示例实现:
// src/shared/hooks/use-action-with-toast.ts
"use client"
import { useTransition } from "react"
import { toast } from "sonner"
import type { ActionState } from "@/shared/types/action-state"
export function useActionWithToast<T>() {
const [isPending, startTransition] = useTransition()
const execute = async (action: () => Promise<ActionState<T>>) => {
startTransition(async () => {
const result = await action()
if (result.success) {
toast.success(result.message || "操作成功")
} else {
toast.error(result.message || "操作失败")
}
})
}
return { isPending, execute }
}
模块级 Hooks(src/modules/*/hooks/)
src/modules/exams/hooks/
use-exam-form.ts ← 考试表单状态管理
use-exam-filters.ts ← 考试筛选逻辑
src/modules/textbooks/hooks/
use-text-selection.ts ← 文本选择 + 知识点创建
use-knowledge-point-actions.ts ← 知识点 CRUD 操作
src/modules/homework/hooks/
use-homework-submission.ts ← 作业提交逻辑
验收标准:
- 组件中无超过 3 个
useState的逻辑块(应提取为 Hook) - 通用 Hook 有单元测试
P1-2 添加动画降级(prefers-reduced-motion)
问题: 全项目零处 prefers-reduced-motion 引用,不符合 WCAG 2.1。
方案
- globals.css 添加全局降级
@layer base {
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
}
- tailwindcss-animate 适配
确认 tailwindcss-animate 生成的动画类在 prefers-reduced-motion: reduce 下被正确降级。如不支持,在 globals.css 中补充:
@media (prefers-reduced-motion: reduce) {
.animate-accordion-down,
.animate-accordion-up {
animation: none !important;
}
}
验收标准:
- 操作系统开启"减少动态效果"后,页面无动画
- Lighthouse Accessibility 评分 ≥ 90
P1-3 引入 Next.js 图片与字体优化
next/image
- 搜索所有
<img>标签,替换为next/image - 配置
next.config.ts的images.remotePatterns(如有外部图片源)
next/font
// src/app/layout.tsx
import { Inter } from "next/font/google"
const inter = Inter({
subsets: ["latin"],
display: "swap",
variable: "--font-inter",
})
验收标准:
- Lighthouse Performance 无 "Properly size images" 警告
- 无 "Eliminate render-blocking resources" 字体相关警告
P2 — 改进(质量提升)
P2-1 补充组件单元测试
问题: 零组件测试、零 Hook 测试、零工具函数测试。
测试目录结构(co-location 模式)
src/modules/exams/components/
exam-card.tsx
exam-card.test.tsx ← 新增
src/shared/hooks/
use-action-with-toast.ts
use-action-with-toast.test.ts ← 新增
src/shared/lib/
utils.ts
utils.test.ts ← 新增
优先级
- 高:
useActionWithToast、cn()、formatDate() - 高:ExamCard、QuestionColumns、HomeworkAssignmentForm
- 中:各模块 data-access.ts(mock DB 测试)
- 低:UI 基础组件(shadcn/ui 已有上游测试)
配置更新
// vitest.config.ts 添加
test: {
include: ["src/**/*.test.{ts,tsx}"],
}
验收标准:
- 关键路径覆盖率 > 80%
- CI 中
npm run test:integration包含单元测试
P2-2 统一 Tailwind v4 配置
问题: tailwind.config.ts(v3 风格)与 globals.css 的 @theme inline(v4 风格)并存。
方案
- 将
tailwind.config.ts中的theme.extend迁移至globals.css的@theme inline块 - 删除
tailwind.config.ts中与 CSS 重复的定义 - 保留
tailwind.config.ts仅用于plugins和content配置(如 v4 仍需要)
验收标准:
- 无重复的主题定义
npm run build无 Tailwind deprecation 警告
P2-3 清理 Mock 数据
问题: src/modules/exams/mock-data.ts 和 src/modules/questions/mock-data.ts 残留在生产代码中。
方案
src/mocks/ ← 新增
exam-data.ts ← 从 modules/exams/mock-data.ts 迁移
question-data.ts ← 从 modules/questions/mock-data.ts 迁移
tests/mocks/ ← 或放测试目录
exam-factories.ts ← 使用 @faker-js/faker 生成
question-factories.ts
- 删除
src/modules/*/mock-data.ts - 确认无生产代码引用 mock 数据
验收标准:
src/modules/下无mock-data.ts- 生产构建不包含 mock 数据
P3 — 优化(锦上添花)
P3-1 i18n 文案提取
问题: 硬编码中文字案散布在组件中。
方案
- 引入
next-intl或轻量 i18n 方案 - 提取所有用户可见文案到
src/shared/i18n/zh-CN.json - 代码中通过
t("knowledgePoint.created")引用
P3-2 替换原生 confirm/alert
问题: textbook-reader.tsx 使用 confirm(),与项目 UI 风格不一致。
方案
- 创建
useConfirmDialogHook,封装 AlertDialog - 全局搜索
confirm(和alert(,逐一替换
P3-3 react-markdown 安全加固
问题: textbook-reader.tsx 渲染用户内容,remarkGfm 启用 HTML 后需防 XSS。
方案
import rehypeSanitize from "rehype-sanitize"
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkBreaks]}
rehypePlugins={[rehypeSanitize]} ← 新增
>
P3-4 formatDate 国际化
问题: utils.ts 中 formatDate 硬编码 "en-US"。
方案
export function formatDate(date: string | Date, locale = "zh-CN") {
return new Intl.DateTimeFormat(locale, {
year: "numeric",
month: "short",
day: "numeric",
}).format(new Date(date))
}
P3-5 AI 聊天接口速率限制
问题: /api/ai/chat 需确认是否有服务端速率限制。
方案
- 使用
@upstash/ratelimit或自实现基于 IP/用户的速率限制 - 建议限制:每用户每分钟 10 次请求
执行时间线
| 阶段 | 内容 | 预计周期 |
|---|---|---|
| 第 1 周 | P0-1 exam-form.tsx 拆分 | 3 天 |
| 第 1 周 | P0-1 textbook-reader.tsx 拆分 | 2 天 |
| 第 2 周 | P0-2 无障碍修复 | 3 天 |
| 第 2 周 | P1-1 创建 Hooks 层 | 2 天 |
| 第 3 周 | P1-2 动画降级 | 1 天 |
| 第 3 周 | P1-3 图片/字体优化 | 2 天 |
| 第 3-4 周 | P2-1 补充单元测试 | 5 天 |
| 第 4 周 | P2-2 + P2-3 配置清理 | 2 天 |
| 持续 | P3 优化项 | 随迭代推进 |
验收检查清单
- 单文件不超过 300 行
- axe-core 扫描零严重 a11y 违规
- Lighthouse Accessibility ≥ 90
- Lighthouse Performance ≥ 85
- 关键路径测试覆盖率 > 80%
npm run build零错误零警告npm run lint零错误npm run typecheck零错误- 无
<div onClick>/<span onClick>模式 - 所有图标按钮有
aria-label prefers-reduced-motion降级生效- 生产构建不含 mock 数据