7.5 KiB
Executable File
VoiceLibrary 页面重构方案
现状分析
当前文件: apps/web/src/pages/voices/VoiceLibrary.tsx — 1783 行
代码质量:
any: 0 处ts-ignore: 0 处eslint-disable: 0 处TODO: 1 处- 整体质量良好,重构阻力小
目录结构(重构后)
apps/web/src/pages/voices/
├── VoiceLibrary.tsx # 主组件(目标:~550 行,-69%)
├── types.ts # 类型定义
├── constants.ts # 常量 + 配置
├── utils/
│ ├── format.ts # 格式化工具函数
│ └── audio.ts # 音频工具函数
├── components/
│ ├── VoiceCard.tsx # 预设音色卡片
│ ├── CloneVoiceCard.tsx # 克隆音色卡片
│ ├── CloneDetailModal.tsx # 克隆详情弹窗
│ ├── CloneCardSkeleton.tsx # 克隆卡片骨架屏
│ ├── UploadModal.tsx # 上传音色弹窗
│ ├── TTSModal.tsx # TTS合成弹窗
│ ├── VoiceFilterBar.tsx # 筛选栏(搜索/性别/语言)
│ └── VoiceTabBar.tsx # Tab切换栏
└── hooks/
├── useVoices.ts # 音色数据查询 + 筛选
├── useAudioPlayer.ts # 音频播放控制
├── useVoiceUpload.ts # 音色上传逻辑
├── useTTS.ts # TTS合成逻辑
└── useCloneVoice.ts # 克隆音色操作
三阶段渐进式重构
Phase 1:抽离类型、常量、工具函数
目标: 主文件 1783 → ~1550 行(-13%)
抽出内容:
-
types.ts— 类型定义(~60行)PresetVoiceDisplay/ClonedVoiceDisplay/VoiceCardProps/CloneVoiceCardPropsTabKey/Toast/VoiceUploadMetadata- 现有
mapPresetToDisplay/mapCloneToDisplay数据映射函数
-
constants.ts— 常量配置(~40行)CLONE_STATUS_CONFIG克隆状态配置GENDER_LABEL_MAP/LANGUAGE_LABEL_MAP性别/语言标签映射- Tab 配置项
-
utils/format.ts— 格式化工具(~25行)formatTime时长格式化formatFileSize文件大小格式化genderLabel/languageLabel/genderClass
-
utils/audio.ts— 音频工具(~15行)getAudioDuration获取音频文件时长
-
format.test.ts— 工具函数单测- 覆盖 formatTime / formatFileSize 等纯函数
Phase 1 交付:
- 新增文件:6 个(types.ts / constants.ts / utils/format.ts / utils/audio.ts / format.test.ts)
- 主文件减少:~230 行
- 纯机械抽离,无逻辑改动
Phase 2:抽离子组件
目标: 主文件 1550 → ~950 行(-39%)
抽出组件:
-
components/VoiceCard.tsx— 预设音色卡片(~120行)- 卡片渲染:头像、名称、性别标签、播放按钮、进度条、收藏
- Props:voice / playingId / currentTime / onPlay / onPause / onSeek / onToggleStar
-
components/CloneVoiceCard.tsx— 克隆音色卡片(~160行)- 三种状态:processing / success / failed
- 操作:播放、详情、删除、重试、使用
- Props:voice / playingId / currentTime / onPlayPause / onShowDetail / onDelete / onRetry / onUse
-
components/CloneDetailModal.tsx— 克隆详情弹窗(~90行)- 展示克隆音色详细信息
- 状态展示、音频播放、操作按钮
-
components/CloneCardSkeleton.tsx— 克隆卡片骨架屏(~20行)- 加载状态占位
-
components/UploadModal.tsx— 上传音色弹窗(~180行)- 文件选择、名称/性别/描述填写
- 上传进度展示
- Props:open / onClose / onUpload
-
components/TTSModal.tsx— TTS合成弹窗(~170行)- 文本输入、音色选择、语速调节
- 合成状态轮询(idle/synthesizing/done/error)
- 保存到素材库
- Props:open / onClose / onSave
-
components/VoiceFilterBar.tsx— 筛选栏(~80行)- 搜索框、性别筛选、语言筛选
- Props:searchText / filterGender / filterLang / onChange handlers
Phase 2 交付:
- 新增组件:7 个
- 主文件减少:~600 行
- 纯UI抽离,业务逻辑保留在主组件
Phase 3:抽离业务逻辑 Hook
目标: 主文件 950 → ~550 行(-42%)
抽出 Hook:
-
hooks/useVoices.ts— 音色数据 Hook(~200行)- 三个 useQuery:presetVoices / cloneVoices / materialVoices / unifiedStats
- 筛选逻辑:searchText / filterGender / filterLang → filteredPreset / filteredClone
- 统计数据:presetCount / cloneCount / materialCount
- 收藏切换 handleToggleStar
- 返回:data / loading / filtered / counts / handlers
-
hooks/useAudioPlayer.ts— 音频播放控制 Hook(~120行)- playingId / currentTime / intervalRef 状态
- handlePlay / handlePause / handleSeek
- 自动停止(切换新音频时停止旧的)
- 组件卸载清理
- 注意:与 VoiceMaterialLibrary 的 useAudioPlayer 类似但有差异(一个操作DOM音频,一个操作audio元素),评估是否复用还是独立
-
hooks/useVoiceUpload.ts— 音色上传 Hook(~120行)- uploadFile / uploadName / uploadGender / uploadDesc / uploadProgress 状态
- uploadMutation(上传文件 + 创建音色)
- buildVoiceMetadata 元数据构建
- getAudioDuration 时长获取
- 成功后刷新列表 + toast 关闭弹窗
-
hooks/useTTS.ts— TTS合成 Hook(~110行)- ttsOpen / ttsText / ttsVoiceId / ttsSpeed 弹窗状态
- ttsJobId / ttsStatus / ttsAudioUrl / ttsError 合成状态
- handleTtsSynthesize 发起合成 + 轮询
- handleTtsSave 保存到素材库
- handleTtsClose 清理状态
-
hooks/useCloneVoice.ts— 克隆音色操作 Hook(~80行)- deleteMutation / retryMutation
- handleCloneDelete / handleCloneRetry
- detailVoice 详情状态
- 操作后刷新列表
Phase 3 交付:
- 新增 Hook:5 个
- 主文件减少:~400 行
- 主文件只剩:Tab切换逻辑 + JSX 组装 + 顶层状态编排
最终效果汇总
| 阶段 | 主文件行数 | 减少行数 | 减少比例 | 新增文件 |
|---|---|---|---|---|
| 初始 | 1783 | — | — | — |
| Phase 1 | ~1550 | -233 | -13.1% | 5 |
| Phase 2 | ~950 | -600 | -38.7% | 7 |
| Phase 3 | ~550 | -400 | -42.1% | 5 |
| 总计 | ~550 | -1233 | -69.2% | 17 |
与 GeneratePage / VoiceMaterialLibrary 的异同
相同点:
- 三阶段渐进式重构(类型常量 → 组件 → Hook)
- 每阶段独立PR,独立验证
- 每阶段加 smoke test 保证 vitest related 模式可用
- 代码质量基线高(0 any / 0 ts-ignore)
不同点:
- VoiceLibrary 有两个数据源(预设音色 + 克隆音色),还有TTS和上传功能
- 音频播放逻辑与 VoiceMaterialLibrary 类似但操作的音频类型不同
- 有 CloneModal 是外部组件(已在 @/components/voice/CloneModal),不需要重写
- 骨架屏组件比较简单
风险与注意事项
- vitest related 模式:每阶段新增文件需建立测试覆盖,避免 "No test files found"
- Preview Deploy:环境问题导致失败,不影响代码质量
- 音频播放逻辑:VoiceLibrary 和 VoiceMaterialLibrary 的播放控制类似但是两套独立实现,后续可考虑抽象为共享 Hook
- 克隆状态:CLONE_STATUS_CONFIG 是核心常量,抽离时注意类型完整
- 上传逻辑:uploadMutation 内部逻辑较复杂(上传文件 + 获取时长 + 创建音色),抽 Hook 时保持逻辑不变