feat(task-3.12): 我的音色页面 - 克隆音色卡片 + 状态管理 + 空状态引导 #146

Closed
xiaoxia wants to merge 1 commits from feat/my-voices-page into develop
Owner

任务 3.12 — 我的音色页面(MyVoices.tsx)

变更文件

  • apps/web/src/pages/my-voices/MyVoices.tsx(新增,455 行)
  • apps/web/src/pages/my-voices/my-voices.css(新增,398 行)
  • apps/web/src/router/index.tsx(路由注册 /app/my-voices

功能点

  1. 克隆音色卡片列表 — 网格布局,卡片展示音色名称、创建日期、时长
  2. 状态标签 — ready(就绪)/ processing(克隆中)/ failed(失败),含动态进度条
  3. 试听播放 — 点击播放/暂停,同一时间只播放一个音色
  4. 编辑名称 — Modal 弹窗编辑
  5. 删除 — Modal 确认删除
  6. 空状态引导 — 引导用户去配音库克隆新音色
  7. 统计栏 — 显示总数、就绪数、克隆中数
  8. Toast 提示 — 操作成功/失败反馈

CSS 规范

  • 命名前缀 xx-mv-,与配音库页面 xx-voice- 风格一致
  • 全部使用 CSS 变量,无硬编码颜色
  • 响应式:1200px / 768px / 480px 三级适配

API 预留

  • fetchClonedVoices() — GET /api/v1/voice-clones
  • deleteClonedVoice(id) — DELETE /api/v1/voice-clones/{id}
  • updateClonedVoiceName(id, name) — PUT /api/v1/voice-clones/{id}
  • getVoicePreviewUrl(id) — 获取试听音频 URL
  • 当前使用 Mock 数据,后续替换为真实 API
## 任务 3.12 — 我的音色页面(MyVoices.tsx) ### 变更文件 - `apps/web/src/pages/my-voices/MyVoices.tsx`(新增,455 行) - `apps/web/src/pages/my-voices/my-voices.css`(新增,398 行) - `apps/web/src/router/index.tsx`(路由注册 `/app/my-voices`) ### 功能点 1. **克隆音色卡片列表** — 网格布局,卡片展示音色名称、创建日期、时长 2. **状态标签** — ready(就绪)/ processing(克隆中)/ failed(失败),含动态进度条 3. **试听播放** — 点击播放/暂停,同一时间只播放一个音色 4. **编辑名称** — Modal 弹窗编辑 5. **删除** — Modal 确认删除 6. **空状态引导** — 引导用户去配音库克隆新音色 7. **统计栏** — 显示总数、就绪数、克隆中数 8. **Toast 提示** — 操作成功/失败反馈 ### CSS 规范 - 命名前缀 `xx-mv-`,与配音库页面 `xx-voice-` 风格一致 - 全部使用 CSS 变量,无硬编码颜色 - 响应式:1200px / 768px / 480px 三级适配 ### API 预留 - `fetchClonedVoices()` — GET /api/v1/voice-clones - `deleteClonedVoice(id)` — DELETE /api/v1/voice-clones/{id} - `updateClonedVoiceName(id, name)` — PUT /api/v1/voice-clones/{id} - `getVoicePreviewUrl(id)` — 获取试听音频 URL - 当前使用 Mock 数据,后续替换为真实 API
xiaoxia added 1 commit 2026-07-01 14:54:57 +08:00
feat(task-3.12): 我的音色页面 - 克隆音色卡片列表 + 状态管理 + 空状态引导
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 189h49m41s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 189h49m45s
62139aae38
Author
Owner

代码审查结果: 通过

PR #146 — feat(task-3.12): 我的音色页面(克隆音色卡片 + 状态管理 + 空状态引导)
变更: 3 文件,+860/-0
审查范围: MyVoices.tsx (455行) + my-voices.css (398行) + router/index.tsx (+7行)


👍 优点

  1. 组件架构清晰 — VoiceCard 子组件职责单一,Props 接口设计合理(isPlaying/onTogglePlay/onEdit/onDelete)
  2. 类型定义完整 — CloneStatus 联合类型、ClonedVoice 接口、ToastItem、VoiceCardProps 类型齐全
  3. API 预留规范 — fetchClonedVoices/deleteClonedVoice/updateClonedVoiceName/getVoicePreviewUrl 四个 stub 函数清晰,TODO 注释明确后续替换点
  4. CSS 变量全覆盖 — 无硬编码颜色,全部使用 var(--xx, fallback) 格式,与配音库页面风格一致
  5. 命名前缀规范xx-mv-xx-voice- 保持统一命名体系
  6. 三级响应式 — 1200px / 768px / 480px 断点齐全,grid auto-fill minmax() 自适应
  7. 动效得当 — processing 状态 pulse 动画、status dot blink、toast slide-in,增强反馈感
  8. 单音播放互斥 — audioRef 管理,切歌先 pause 前一个,ended 事件自动重置状态
  9. 性能优化 — useCallback 包裹 showToast/handleTogglePlay,避免不必要重渲染
  10. 空状态引导完善 — 引导去配音库克隆,与 VoiceLibrary 的 handleCloneNew 路由衔接

🔧 建议(P3 — 不阻塞合并)

P3-1:useEffect 缺少错误处理

useEffect(() => {
  fetchClonedVoices().then((data) => {
    setVoices(data);
    setLoading(false);
  });
}, []);

缺少 .catch() — 若 API 失败,loading 永远为 true,页面卡在"加载中..."。建议:

useEffect(() => {
  fetchClonedVoices()
    .then((data) => { setVoices(data); setLoading(false); })
    .catch(() => { showToast("加载失败,请重试", "error"); setLoading(false); });
}, []);

P3-2:编辑/删除操作缺少 try/catch
handleEditConfirmhandleDeleteConfirm 直接 await API 调用,无错误处理。当 Mock 替换为真实 API 后,网络错误会导致 Promise reject 未捕获。建议包裹 try/catch 并 showToast 提示。

P3-3:组件卸载时未清理 Audio
audioRef.current 在组件卸载时未 pause/销毁。用户正在试听时切换到其他页面,音频会继续播放。建议:

useEffect(() => {
  return () => { audioRef.current?.pause(); };
}, []);

📊 审查总结

维度 评分 说明
组件设计 VoiceCard 分离、Props 清晰、状态管理合理
CSS 规范 变量全覆盖、命名统一、三级响应式
API 预留 接口清晰,Mock 数据结构合理,待接真实 API
错误处理 缺少 catch/unmount cleanup,P3 级别
可维护性 注释充分、结构清晰、与配音库风格一致

结论: 通过,可合并到 develop。 3 个 P3 建议在后续 API 对接时一并处理。

## 代码审查结果:✅ 通过 **PR #146** — feat(task-3.12): 我的音色页面(克隆音色卡片 + 状态管理 + 空状态引导) **变更**: 3 文件,+860/-0 **审查范围**: MyVoices.tsx (455行) + my-voices.css (398行) + router/index.tsx (+7行) --- ### 👍 优点 1. **组件架构清晰** — VoiceCard 子组件职责单一,Props 接口设计合理(isPlaying/onTogglePlay/onEdit/onDelete) 2. **类型定义完整** — CloneStatus 联合类型、ClonedVoice 接口、ToastItem、VoiceCardProps 类型齐全 3. **API 预留规范** — fetchClonedVoices/deleteClonedVoice/updateClonedVoiceName/getVoicePreviewUrl 四个 stub 函数清晰,TODO 注释明确后续替换点 4. **CSS 变量全覆盖** — 无硬编码颜色,全部使用 `var(--xx, fallback)` 格式,与配音库页面风格一致 5. **命名前缀规范** — `xx-mv-` 与 `xx-voice-` 保持统一命名体系 6. **三级响应式** — 1200px / 768px / 480px 断点齐全,grid `auto-fill minmax()` 自适应 7. **动效得当** — processing 状态 pulse 动画、status dot blink、toast slide-in,增强反馈感 8. **单音播放互斥** — audioRef 管理,切歌先 pause 前一个,ended 事件自动重置状态 9. **性能优化** — useCallback 包裹 showToast/handleTogglePlay,避免不必要重渲染 10. **空状态引导完善** — 引导去配音库克隆,与 VoiceLibrary 的 handleCloneNew 路由衔接 --- ### 🔧 建议(P3 — 不阻塞合并) **P3-1:useEffect 缺少错误处理** ```typescript useEffect(() => { fetchClonedVoices().then((data) => { setVoices(data); setLoading(false); }); }, []); ``` 缺少 `.catch()` — 若 API 失败,`loading` 永远为 `true`,页面卡在"加载中..."。建议: ```typescript useEffect(() => { fetchClonedVoices() .then((data) => { setVoices(data); setLoading(false); }) .catch(() => { showToast("加载失败,请重试", "error"); setLoading(false); }); }, []); ``` **P3-2:编辑/删除操作缺少 try/catch** `handleEditConfirm` 和 `handleDeleteConfirm` 直接 await API 调用,无错误处理。当 Mock 替换为真实 API 后,网络错误会导致 Promise reject 未捕获。建议包裹 try/catch 并 showToast 提示。 **P3-3:组件卸载时未清理 Audio** `audioRef.current` 在组件卸载时未 pause/销毁。用户正在试听时切换到其他页面,音频会继续播放。建议: ```typescript useEffect(() => { return () => { audioRef.current?.pause(); }; }, []); ``` --- ### 📊 审查总结 | 维度 | 评分 | 说明 | |------|------|------| | 组件设计 | ⭐⭐⭐⭐⭐ | VoiceCard 分离、Props 清晰、状态管理合理 | | CSS 规范 | ⭐⭐⭐⭐⭐ | 变量全覆盖、命名统一、三级响应式 | | API 预留 | ⭐⭐⭐⭐ | 接口清晰,Mock 数据结构合理,待接真实 API | | 错误处理 | ⭐⭐⭐ | 缺少 catch/unmount cleanup,P3 级别 | | 可维护性 | ⭐⭐⭐⭐⭐ | 注释充分、结构清晰、与配音库风格一致 | **结论:✅ 通过,可合并到 develop。** 3 个 P3 建议在后续 API 对接时一并处理。
xiaoxia closed this pull request 2026-07-01 15:01:59 +08:00
Some checks are pending
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 189h49m41s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 189h49m45s

Pull request closed

Sign in to join this conversation.