> ⚠️ **已归档文档**
> 本文档是 2026-06-16 架构审核后的 UI 代码结构重构计划（评分 7.2/10），描述的是待修复的质量问题与重构方案。
> 当前重构计划已执行完毕，组件拆分、复用抽象、测试保障等改进已落地，最新架构状态详见 [004 架构影响地图](./004_architecture_impact_map.md)。
> 保留用于历史参考，不再维护。

---

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

#### 修复清单

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 添加：
     ```tsx
     <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，逻辑耦合在组件中。

#### 通用 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             ← 本地存储
```

示例实现：

```typescript
// 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>(): { isPending: boolean; execute: (action: () => Promise<ActionState<T>>) => Promise<void> } {
  const [isPending, startTransition] = useTransition()

  const execute = async (action: () => Promise<ActionState<T>>): Promise<void> => {
    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。

#### 方案

1. **globals.css 添加全局降级**

```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;
    }
  }
}
```

2. **tailwindcss-animate 适配**

确认 `tailwindcss-animate` 生成的动画类在 `prefers-reduced-motion: reduce` 下被正确降级。如不支持，在 globals.css 中补充：

```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

```typescript
// 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. **高**：`useActionWithToast`、`cn()`、`formatDate()`
2. **高**：ExamCard、QuestionColumns、HomeworkAssignmentForm
3. **中**：各模块 data-access.ts（mock DB 测试）
4. **低**：UI 基础组件（shadcn/ui 已有上游测试）

#### 配置更新

```typescript
// 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 风格）并存。

#### 方案

1. 将 `tailwind.config.ts` 中的 `theme.extend` 迁移至 `globals.css` 的 `@theme inline` 块
2. 删除 `tailwind.config.ts` 中与 CSS 重复的定义
3. 保留 `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 风格不一致。

#### 方案

- 创建 `useConfirmDialog` Hook，封装 AlertDialog
- 全局搜索 `confirm(` 和 `alert(`，逐一替换

### P3-3 react-markdown 安全加固

**问题：** textbook-reader.tsx 渲染用户内容，remarkGfm 启用 HTML 后需防 XSS。

#### 方案

```typescript
import rehypeSanitize from "rehype-sanitize"

<ReactMarkdown
  remarkPlugins={[remarkGfm, remarkBreaks]}
  rehypePlugins={[rehypeSanitize]}    ← 新增
>
```

### P3-4 formatDate 国际化

**问题：** `utils.ts` 中 `formatDate` 硬编码 `"en-US"`。

#### 方案

```typescript
export function formatDate(date: string | Date, locale = "zh-CN"): string {
  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 数据
