Files
xiaoxia-saas/docs/voice-material-library-refactor-plan.md
xiaoxia 40776cd4fa
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m36s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Validate - Code Quality (push) Failing after 4m40s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m22s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m21s
CI/CD Pipeline / Unit Tests (push) Failing after 6m50s
CI/CD Pipeline / Integration Tests (push) Successful in 2m30s
CI/CD Pipeline / Frontend Lint (push) Successful in 42s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m43s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 13m59s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m13s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m22s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 32s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 6m9s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 6m45s
refactor(voices): Phase 1 - extract types, constants and utils (#873)
2026-07-25 12:25:14 +08:00

199 lines
8.4 KiB
Markdown
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VoiceMaterialLibrary 页面拆分方案
## 一、现状摸底
**文件:** `apps/web/src/pages/voice-materials/VoiceMaterialLibrary.tsx`
**总行数:** 1966 行
**CSS 文件:** `apps/web/src/pages/voice-materials/voice-materials.css` (1262 行)
### 代码质量
| 指标 | 数量 | 评价 |
|------|------|------|
| `any` 类型 | 0 | ✅ 优秀 |
| `@ts-ignore` | 0 | ✅ 优秀 |
| `eslint-disable` | 0 | ✅ 优秀 |
| TODO/FIXME | 0 | ✅ 干净 |
### 内联组件分布
| 组件 | 行数 | 职责 |
|------|------|------|
| `TagSelector` | 148行 (197-345) | 标签选择器(输入+建议+选择) |
| `MaterialForm` | 187行 (356-543) | 上传/编辑表单(名称/描述/性别/标签/文件) |
| `VoiceMaterialCard` | 220行 (543-763) | 卡片视图 + 音频播放进度条 |
| `VoiceMaterialRow` | 153行 (763-916) | 列表视图 + 音频播放进度条 |
| **VoiceMaterialLibrary(主组件)** | **1051行 (916-1966)** | **页面主逻辑 + 渲染** |
### 主组件内部结构(1051行)
- **状态 & hooks**:约 230行
- 数据查询:assets / libraries / tags / presetVoices
- 视图状态:viewMode / searchText / filterGender / filterTagId
- 播放状态:playingId / currentTime / audioRef / volume / pausedMaterial
- 弹窗:uploadOpen / editingMaterial / ttsOpen
- 批量:selectedIds / uploadProgress / batchCustomTag
- TTSttsText / ttsVoiceId / ttsSpeed / ttsJobId / ttsStatus / ttsAudioUrl / ttsError
- **播放控制函数**:约 100行(stopPlayback/startPlayback/handlePlay/handlePause/handleSeek/handleVolumeChange/toggleMute
- **数据操作函数**:约 60行(handleUpload/handleEdit/handleDelete
- **筛选 & 统计**:约 40行(filtered/tagCountMap
- **批量操作**:约 80行(handleToggleSelect/handleSelectAll/handleBatchDelete/handleBatchTag/handleBatchCustomTag
- **TTS 合成**:约 70行(handleTtsSynthesize/handleTtsSave
- **渲染 JSX**:约 473行
## 二、拆分策略
跟 GeneratePage 同样的思路:**按职责拆分,功能不变,纯结构优化。**
拆分四阶段,每个阶段独立 PR,逐步降低主文件复杂度。
---
## Phase 1:抽离常量、类型、工具函数
**目标:** 把纯数据定义和纯函数抽出去,主文件只留组件。
### 抽离内容
| 新文件 | 内容 | 行数估计 |
|--------|------|---------|
| `types.ts` | `VoiceGender` / `ViewMode` / `VoiceMaterial` / `VoiceAssetMetadata` 等类型定义 | ~40行 |
| `constants.ts` | `MAX_CARD_TAGS` / `MAX_ROW_TAGS` / `TAG_VARIANTS` / `GENDER_OPTIONS` | ~25行 |
| `utils/format.ts` | `formatDuration` / `formatFileSize` / `formatDate` / `genderLabel` / `genderIcon` / `genderClass` | ~35行 |
| `utils/audio.ts` | `getAudioDuration`(纯工具,跟组件无关) | ~20行 |
| `utils/mappers.ts` | `mapAssetToMaterial` / `buildMetadata`(数据映射) | ~35行 |
### 预期效果
- 主文件减少约 **155 行**1966 → ~1811
- 工具函数可复用、可单测
---
## Phase 2:抽离已有内联子组件
**目标:** 把 4 个已经定义好的内联组件拆成独立文件,主文件只保留 VoiceMaterialLibrary 主组件。
### 抽离内容
| 新文件 | 原位置 | 行数 | 备注 |
|--------|--------|------|------|
| `components/TagSelector.tsx` | 197-345行 | ~148行 | 标签选择器 |
| `components/MaterialForm.tsx` | 356-543行 | ~187行 | 上传/编辑表单 |
| `components/VoiceMaterialCard.tsx` | 543-763行 | ~220行 | 卡片视图,带播放控制 |
| `components/VoiceMaterialRow.tsx` | 763-916行 | ~153行 | 列表视图,带播放控制 |
### 播放状态共享方案
Card 和 Row 都有播放进度条(共 ~60行重复代码),但播放状态在主组件里。
**方案:** 播放状态提升到主组件,Card/Row 只接收 prop 并回调:
- `isPlaying` / `currentTime` / `onPlay` / `onPause` / `onSeek`
- 主组件统一管理 audioRef 和播放状态
这样 Card 和 Row 是纯展示组件,播放逻辑集中在主组件。
### 预期效果
- 主文件减少约 **700 行**1811 → ~1111
- 主组件专注页面逻辑,子组件专注展示
---
## Phase 3:主组件拆分 — 业务逻辑抽 Hook
**目标:** 把主组件里的业务逻辑按领域拆成自定义 Hook,主组件只负责组装。
### 抽离 Hooks
| Hook 文件 | 职责 | 管理的状态 |
|-----------|------|-----------|
| `hooks/useVoiceMaterials.ts` | 素材列表查询 + 筛选 + 增删改 | assets / searchText / filterGender / filterTagId / uploadMutation / editMutation / deleteMutation |
| `hooks/useAudioPlayer.ts` | 音频播放控制 | playingId / currentTime / volume / audioRef / pausedMaterial |
| `hooks/useBatchOperations.ts` | 批量操作 | selectedIds / handleToggleSelect / handleSelectAll / handleBatchDelete / handleBatchTag |
| `hooks/useTtsSynthesize.ts` | TTS 合成逻辑 | ttsText / ttsVoiceId / ttsSpeed / ttsJobId / ttsStatus / ttsAudioUrl / ttsError |
### 预期效果
- 主组件减少约 **500 行**1111 → ~611
- 每个 Hook 职责单一,可独立测试
- 主组件变成"组装器",可读性大幅提升
---
## Phase 4UI 组件细化 — 工具栏 & 弹窗拆分
**目标:** 把主组件里的大块 JSX 拆成独立 UI 组件。
### 抽离 UI 组件
| 组件文件 | 内容 | 行数估计 |
|----------|------|---------|
| `components/LibraryToolbar.tsx` | 顶部工具栏:搜索框 + 性别筛选 + 视图切换 + 结果计数 | ~60行 |
| `components/TagFilterBar.tsx` | 标签筛选药丸条(全部 + 各标签计数) | ~45行 |
| `components/BatchActionBar.tsx` | 批量操作栏:全选 + 计数 + 批量打标签 + 批量删除 | ~70行 |
| `components/UploadModal.tsx` | 上传弹窗(包裹 MaterialForm | ~30行 |
| `components/EditModal.tsx` | 编辑弹窗(包裹 MaterialForm | ~30行 |
| `components/TtsSynthesizeModal.tsx` | AI 配音合成弹窗:文本输入 + 音色选择 + 语速 + 合成结果预览 + 保存 | ~150行 |
| `components/EmptyState.tsx` | 空状态展示 | ~20行 |
| `components/VoiceMaterialGrid.tsx` | 卡片视图网格容器 | ~25行 |
| `components/VoiceMaterialList.tsx` | 列表视图表格容器 | ~25行 |
### 预期效果
- 主组件 JSX 部分减少约 **400 行**611 → ~211
- 每个 UI 组件职责清晰,方便后续迭代
---
## 三、拆分后文件结构
```
apps/web/src/pages/voice-materials/
├── VoiceMaterialLibrary.tsx # 主组件(~211行,组装器)
├── voice-materials.css # 样式(保持不变,后续再拆)
├── types.ts # 类型定义
├── constants.ts # 常量
├── utils/
│ ├── format.ts # 格式化工具
│ ├── audio.ts # 音频工具
│ └── mappers.ts # 数据映射
├── hooks/
│ ├── useVoiceMaterials.ts # 素材数据 + 筛选 + 增删改
│ ├── useAudioPlayer.ts # 音频播放控制
│ ├── useBatchOperations.ts # 批量操作
│ └── useTtsSynthesize.ts # TTS合成
└── components/
├── TagSelector.tsx # 标签选择器
├── MaterialForm.tsx # 上传/编辑表单
├── VoiceMaterialCard.tsx # 卡片视图
├── VoiceMaterialRow.tsx # 列表视图
├── VoiceMaterialGrid.tsx # 卡片网格容器
├── VoiceMaterialList.tsx # 列表容器
├── LibraryToolbar.tsx # 顶部工具栏
├── TagFilterBar.tsx # 标签筛选条
├── BatchActionBar.tsx # 批量操作栏
├── UploadModal.tsx # 上传弹窗
├── EditModal.tsx # 编辑弹窗
├── TtsSynthesizeModal.tsx # TTS合成弹窗
└── EmptyState.tsx # 空状态
```
### 拆分前后对比
| 指标 | 拆分前 | 拆分后 | 变化 |
|------|--------|--------|------|
| 主文件行数 | 1966 | ~211 | **-89%** |
| 文件数量 | 2 | 22 | +20 |
| 最大文件 | 1966行 | ~220行 | -89% |
| 可测试性 | 低 | 高 | 每个Hook/组件可单测 |
## 四、实施顺序 & 风险
1. **Phase 1**:最低风险,纯抽离,零逻辑变化
2. **Phase 2**:低风险,组件本来就是独立定义的,只是挪位置
3. **Phase 3**:中风险,Hook 拆分需要仔细梳理状态依赖
4. **Phase 4**:低风险,JSX 拆分,纯结构调整
**每个 Phase 完成后提 PRCI 全绿再合,跟 GeneratePage 同样节奏。**