Files
NextEdu/docs/architecture/003_ui_refactoring_plan.md
SpecialX 125f7ec54c
Some checks failed
CI / build-deploy (push) Has been cancelled
refactor: RBAC权限系统重构 + UI组件拆分 + 测试修复 + 架构文档
- 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、代码分割
2026-06-16 23:38:33 +08:00

11 KiB
Raw Blame History

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.tsx1623 行、textbook-reader.tsx744 行)严重违反单一职责,难以维护和测试。

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> 非语义化标签。

修复清单

  1. 图标按钮补 aria-label

    • 搜索所有 <Button.*size="icon" 或仅含图标的按钮,补充 aria-label
    • 涉及文件exam-actions.tsx、question-columns.tsx、exam-columns.tsx、breadcrumb.tsx 等
  2. 替换 <div onClick> 为语义化标签

    • textbook-reader.tsx:434 — 知识点卡片 → <button>
    • 搜索所有 <div onClick / <span onClick 模式,逐一替换
  3. 表单控件补 label

    • 确认所有 <Input> / <Select> 有关联 <Label htmlFor>aria-label
  4. 添加 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"

验收标准:

  • axe-core 扫描零严重违规
  • 所有图标按钮有 aria-label
  • <div onClick> / <span onClick> 模式

P1 — 重要(架构级改进)

P1-1 创建自定义 Hooks 层

问题: 整个项目零自定义 Hook逻辑耦合在组件中。

通用 Hookssrc/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 }
}

模块级 Hookssrc/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。

方案

  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;
    }
  }
}
  1. 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.tsimages.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                   ← 新增

优先级

  1. useActionWithToastcn()formatDate()
  2. ExamCard、QuestionColumns、HomeworkAssignmentForm
  3. :各模块 data-access.tsmock DB 测试)
  4. UI 基础组件shadcn/ui 已有上游测试)

配置更新

// vitest.config.ts 添加
test: {
  include: ["src/**/*.test.{ts,tsx}"],
}

验收标准:

  • 关键路径覆盖率 > 80%
  • CI 中 npm run test:integration 包含单元测试

P2-2 统一 Tailwind v4 配置

问题: tailwind.config.tsv3 风格)与 globals.css@theme inlinev4 风格)并存。

方案

  1. tailwind.config.ts 中的 theme.extend 迁移至 globals.css@theme inline
  2. 删除 tailwind.config.ts 中与 CSS 重复的定义
  3. 保留 tailwind.config.ts 仅用于 pluginscontent 配置(如 v4 仍需要)

验收标准:

  • 无重复的主题定义
  • npm run build 无 Tailwind deprecation 警告

P2-3 清理 Mock 数据

问题: src/modules/exams/mock-data.tssrc/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 风格不一致。

方案

  • 创建 useConfirmDialog Hook封装 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.tsformatDate 硬编码 "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 数据