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
199 lines
8.4 KiB
Markdown
Executable File
199 lines
8.4 KiB
Markdown
Executable File
# 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
|
||
- TTS:ttsText / 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 4:UI 组件细化 — 工具栏 & 弹窗拆分
|
||
|
||
**目标:** 把主组件里的大块 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 完成后提 PR,CI 全绿再合,跟 GeneratePage 同样节奏。**
|