feat(ui): 新建我的音色页面
Deploy / Deploy Staging (push) Failing after 194h34m44s
CI/CD Pipeline / Frontend Lint (push) Failing after 194h35m24s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 194h35m35s
Deploy / Production Browser E2E (push) Failing after 1853h9m36s
Deploy / Build Production Runtime Images (push) Failing after 1853h9m40s
Deploy / Deploy Production (push) Failing after 1853h40m59s
Deploy / Deploy Staging (push) Failing after 194h34m44s
CI/CD Pipeline / Frontend Lint (push) Failing after 194h35m24s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 194h35m35s
Deploy / Production Browser E2E (push) Failing after 1853h9m36s
Deploy / Build Production Runtime Images (push) Failing after 1853h9m40s
Deploy / Deploy Production (push) Failing after 1853h40m59s
- 新增 api/voiceClone.ts: mock 数据模块(4条音色,含 ready/processing 状态) - 新增 pages/voice-clone/VoiceClone.tsx: 主页面,卡片网格+空状态+编辑弹窗+toast - 新增 pages/voice-clone/voice-clone.css: V21 设计系统样式 - 更新路由 /voice-clone、侧边栏菜单、Header导航、PageHead面包屑 - 零 antd 依赖,使用 V21 UI 组件(Button, Tooltip) - TypeScript 检查通过
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 音色克隆 API
|
||||
* Sprint 4 — mock 数据,后续迁移真实 API
|
||||
*/
|
||||
|
||||
/** 克隆音色状态 */
|
||||
export type VoiceCloneStatus = "ready" | "processing" | "failed";
|
||||
|
||||
/** 克隆音色条目 */
|
||||
export interface VoiceClone {
|
||||
id: string;
|
||||
name: string;
|
||||
duration_seconds: number;
|
||||
status: VoiceCloneStatus;
|
||||
sample_url?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
/** 创建克隆请求 */
|
||||
export interface CreateVoiceCloneRequest {
|
||||
name: string;
|
||||
audio_url: string;
|
||||
}
|
||||
|
||||
/* ── Mock 数据 ─────────────────────────────────────────── */
|
||||
|
||||
const MOCK_CLONES: VoiceClone[] = [
|
||||
{
|
||||
id: "vc-001",
|
||||
name: "我的播音腔",
|
||||
duration_seconds: 128,
|
||||
status: "ready",
|
||||
sample_url: "",
|
||||
created_at: "2026-06-20T10:30:00Z",
|
||||
updated_at: "2026-06-20T10:35:00Z",
|
||||
},
|
||||
{
|
||||
id: "vc-002",
|
||||
name: "温柔女声",
|
||||
duration_seconds: 256,
|
||||
status: "ready",
|
||||
sample_url: "",
|
||||
created_at: "2026-06-22T14:00:00Z",
|
||||
updated_at: "2026-06-22T14:08:00Z",
|
||||
},
|
||||
{
|
||||
id: "vc-003",
|
||||
name: "磁性男声",
|
||||
duration_seconds: 95,
|
||||
status: "processing",
|
||||
created_at: "2026-06-28T09:15:00Z",
|
||||
updated_at: "2026-06-28T09:15:00Z",
|
||||
},
|
||||
{
|
||||
id: "vc-004",
|
||||
name: "童声模仿",
|
||||
duration_seconds: 60,
|
||||
status: "ready",
|
||||
sample_url: "",
|
||||
created_at: "2026-06-25T16:45:00Z",
|
||||
updated_at: "2026-06-25T16:50:00Z",
|
||||
},
|
||||
];
|
||||
|
||||
/* ── 辅助函数 ─────────────────────────────────────────── */
|
||||
|
||||
const delay = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
/** 格式化时长 */
|
||||
export const formatDuration = (seconds: number): string => {
|
||||
const m = Math.floor(seconds / 60);
|
||||
const s = seconds % 60;
|
||||
return `${m}:${String(s).padStart(2, "0")}`;
|
||||
};
|
||||
|
||||
/* ── API 函数 ─────────────────────────────────────────── */
|
||||
|
||||
/** 获取所有克隆音色 */
|
||||
export const getVoiceClones = async (): Promise<VoiceClone[]> => {
|
||||
await delay(300);
|
||||
return [...MOCK_CLONES];
|
||||
};
|
||||
|
||||
/** 获取单个克隆音色详情 */
|
||||
export const getVoiceCloneDetail = async (
|
||||
id: string,
|
||||
): Promise<VoiceClone | null> => {
|
||||
await delay(200);
|
||||
return MOCK_CLONES.find((v) => v.id === id) ?? null;
|
||||
};
|
||||
|
||||
/** 创建克隆音色 */
|
||||
export const createVoiceClone = async (
|
||||
data: CreateVoiceCloneRequest,
|
||||
): Promise<VoiceClone> => {
|
||||
await delay(500);
|
||||
const newClone: VoiceClone = {
|
||||
id: `vc-${Date.now()}`,
|
||||
name: data.name,
|
||||
duration_seconds: 0,
|
||||
status: "processing",
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
};
|
||||
MOCK_CLONES.unshift(newClone);
|
||||
return newClone;
|
||||
};
|
||||
|
||||
/** 删除克隆音色 */
|
||||
export const deleteVoiceClone = async (id: string): Promise<void> => {
|
||||
await delay(300);
|
||||
const idx = MOCK_CLONES.findIndex((v) => v.id === id);
|
||||
if (idx >= 0) MOCK_CLONES.splice(idx, 1);
|
||||
};
|
||||
|
||||
/** 更新克隆音色名称 */
|
||||
export const updateVoiceClone = async (
|
||||
id: string,
|
||||
data: Partial<Pick<VoiceClone, "name">>,
|
||||
): Promise<VoiceClone> => {
|
||||
await delay(300);
|
||||
const clone = MOCK_CLONES.find((v) => v.id === id);
|
||||
if (!clone) throw new Error("音色不存在");
|
||||
Object.assign(clone, data, { updated_at: new Date().toISOString() });
|
||||
return { ...clone };
|
||||
};
|
||||
Reference in New Issue
Block a user