Files
xiaoxia-saas/docs/voice-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

7.5 KiB
Executable File
Raw Permalink Blame History

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%

抽出内容:

  1. types.ts — 类型定义(~60行)

    • PresetVoiceDisplay / ClonedVoiceDisplay / VoiceCardProps / CloneVoiceCardProps
    • TabKey / Toast / VoiceUploadMetadata
    • 现有 mapPresetToDisplay / mapCloneToDisplay 数据映射函数
  2. constants.ts — 常量配置(~40行)

    • CLONE_STATUS_CONFIG 克隆状态配置
    • GENDER_LABEL_MAP / LANGUAGE_LABEL_MAP 性别/语言标签映射
    • Tab 配置项
  3. utils/format.ts — 格式化工具(~25行)

    • formatTime 时长格式化
    • formatFileSize 文件大小格式化
    • genderLabel / languageLabel / genderClass
  4. utils/audio.ts — 音频工具(~15行)

    • getAudioDuration 获取音频文件时长
  5. 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%

抽出组件:

  1. components/VoiceCard.tsx — 预设音色卡片(~120行)

    • 卡片渲染:头像、名称、性别标签、播放按钮、进度条、收藏
    • Propsvoice / playingId / currentTime / onPlay / onPause / onSeek / onToggleStar
  2. components/CloneVoiceCard.tsx — 克隆音色卡片(~160行)

    • 三种状态:processing / success / failed
    • 操作:播放、详情、删除、重试、使用
    • Propsvoice / playingId / currentTime / onPlayPause / onShowDetail / onDelete / onRetry / onUse
  3. components/CloneDetailModal.tsx — 克隆详情弹窗(~90行)

    • 展示克隆音色详细信息
    • 状态展示、音频播放、操作按钮
  4. components/CloneCardSkeleton.tsx — 克隆卡片骨架屏(~20行)

    • 加载状态占位
  5. components/UploadModal.tsx — 上传音色弹窗(~180行)

    • 文件选择、名称/性别/描述填写
    • 上传进度展示
    • Propsopen / onClose / onUpload
  6. components/TTSModal.tsx — TTS合成弹窗(~170行)

    • 文本输入、音色选择、语速调节
    • 合成状态轮询(idle/synthesizing/done/error
    • 保存到素材库
    • Propsopen / onClose / onSave
  7. components/VoiceFilterBar.tsx — 筛选栏(~80行)

    • 搜索框、性别筛选、语言筛选
    • PropssearchText / filterGender / filterLang / onChange handlers

Phase 2 交付:

  • 新增组件:7 个
  • 主文件减少:~600 行
  • 纯UI抽离,业务逻辑保留在主组件

Phase 3:抽离业务逻辑 Hook

目标: 主文件 950 → ~550 行(-42%

抽出 Hook

  1. hooks/useVoices.ts — 音色数据 Hook~200行)

    • 三个 useQuerypresetVoices / cloneVoices / materialVoices / unifiedStats
    • 筛选逻辑:searchText / filterGender / filterLang → filteredPreset / filteredClone
    • 统计数据:presetCount / cloneCount / materialCount
    • 收藏切换 handleToggleStar
    • 返回:data / loading / filtered / counts / handlers
  2. hooks/useAudioPlayer.ts — 音频播放控制 Hook(~120行)

    • playingId / currentTime / intervalRef 状态
    • handlePlay / handlePause / handleSeek
    • 自动停止(切换新音频时停止旧的)
    • 组件卸载清理
    • 注意:与 VoiceMaterialLibrary 的 useAudioPlayer 类似但有差异(一个操作DOM音频,一个操作audio元素),评估是否复用还是独立
  3. hooks/useVoiceUpload.ts — 音色上传 Hook~120行)

    • uploadFile / uploadName / uploadGender / uploadDesc / uploadProgress 状态
    • uploadMutation(上传文件 + 创建音色)
    • buildVoiceMetadata 元数据构建
    • getAudioDuration 时长获取
    • 成功后刷新列表 + toast 关闭弹窗
  4. hooks/useTTS.ts — TTS合成 Hook~110行)

    • ttsOpen / ttsText / ttsVoiceId / ttsSpeed 弹窗状态
    • ttsJobId / ttsStatus / ttsAudioUrl / ttsError 合成状态
    • handleTtsSynthesize 发起合成 + 轮询
    • handleTtsSave 保存到素材库
    • handleTtsClose 清理状态
  5. hooks/useCloneVoice.ts — 克隆音色操作 Hook(~80行)

    • deleteMutation / retryMutation
    • handleCloneDelete / handleCloneRetry
    • detailVoice 详情状态
    • 操作后刷新列表

Phase 3 交付:

  • 新增 Hook5 个
  • 主文件减少:~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),不需要重写
  • 骨架屏组件比较简单

风险与注意事项

  1. vitest related 模式:每阶段新增文件需建立测试覆盖,避免 "No test files found"
  2. Preview Deploy:环境问题导致失败,不影响代码质量
  3. 音频播放逻辑VoiceLibrary 和 VoiceMaterialLibrary 的播放控制类似但是两套独立实现,后续可考虑抽象为共享 Hook
  4. 克隆状态CLONE_STATUS_CONFIG 是核心常量,抽离时注意类型完整
  5. 上传逻辑uploadMutation 内部逻辑较复杂(上传文件 + 获取时长 + 创建音色),抽 Hook 时保持逻辑不变