+ {/* ── 进度阶段(上传中 / 克隆中) ──────────────── */}
+ {isProcessing && (
+
)}
- {/* ── 成功阶段 ──────────────────────────────────── */}
- {phase === "success" && (
-
-
✅
-
克隆已提交
-
- 音色正在生成中,完成后将出现在「我的克隆」列表中
-
+ {/* ── 完成阶段 ──────────────────────────────────── */}
+ {phase === "done" && (
+
+ {/* 步骤指示器(全部完成) */}
+
+ {PROGRESS_STEPS.map((step, idx) => (
+
+ {idx > 0 && (
+
+ )}
+
+
+ ))}
+
+
+
+
🎉
+
克隆已提交
+
+ 音色正在生成中,完成后将出现在「我的克隆」列表中
+
+
)}
diff --git a/apps/web/src/components/voice/clone-modal.css b/apps/web/src/components/voice/clone-modal.css
index b88eed8d6..e12456c3e 100644
--- a/apps/web/src/components/voice/clone-modal.css
+++ b/apps/web/src/components/voice/clone-modal.css
@@ -284,41 +284,262 @@
margin: 0;
}
+/* ── 分隔线 ───────────────────────────────────────────── */
+
+.xx-clonemodal-divider {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin: 4px 0;
+}
+
+.xx-clonemodal-divider-line {
+ flex: 1;
+ height: 1px;
+ background: var(--xx-color-border, #e5e7eb);
+}
+
+.xx-clonemodal-divider-text {
+ font-size: 13px;
+ color: var(--xx-color-text-secondary, #6b7280);
+ font-weight: 500;
+}
+
+/* ── 录制区域 ─────────────────────────────────────────── */
+
+.xx-clonemodal-record-area {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 16px;
+ border: 1px solid var(--xx-color-border, #e5e7eb);
+ border-radius: 8px;
+ background: var(--xx-color-bg-secondary, #f9fafb);
+}
+
+.xx-clonemodal-record-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.xx-clonemodal-record-hint {
+ margin: 0;
+ font-size: 13px;
+ color: var(--xx-color-text-secondary, #6b7280);
+}
+
+/* 录制波形动画 */
+.xx-clonemodal-record-wave {
+ display: flex;
+ align-items: flex-end;
+ gap: 3px;
+ height: 20px;
+}
+
+.xx-clonemodal-record-wave-bar {
+ width: 3px;
+ background: var(--xx-color-primary, #6366f1);
+ border-radius: 999px;
+ animation: xx-clonemodal-wave 0.8s ease-in-out infinite alternate;
+}
+
+.xx-clonemodal-record-wave-bar:nth-child(1) { height: 40%; animation-delay: 0s; }
+.xx-clonemodal-record-wave-bar:nth-child(2) { height: 70%; animation-delay: 0.15s; }
+.xx-clonemodal-record-wave-bar:nth-child(3) { height: 100%; animation-delay: 0.3s; }
+.xx-clonemodal-record-wave-bar:nth-child(4) { height: 60%; animation-delay: 0.45s; }
+.xx-clonemodal-record-wave-bar:nth-child(5) { height: 30%; animation-delay: 0.6s; }
+
+@keyframes xx-clonemodal-wave {
+ from { transform: scaleY(0.4); }
+ to { transform: scaleY(1); }
+}
+
+/* 录制按钮 */
+.xx-clonemodal-record-btn {
+ width: 48px;
+ height: 48px;
+ border: none;
+ border-radius: 50%;
+ background: var(--xx-color-bg, #fff);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+ font-size: 20px;
+ cursor: pointer;
+ transition: background 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+}
+
+.xx-clonemodal-record-btn:hover {
+ background: rgba(99, 102, 241, 0.06);
+ transform: scale(1.05);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
+}
+
+.xx-clonemodal-record-btn--recording {
+ background: var(--xx-color-error, #ef4444);
+ color: #fff;
+ animation: xx-clonemodal-pulse 1.2s ease-in-out infinite;
+}
+
+.xx-clonemodal-record-btn--recording:hover {
+ background: #dc2626;
+}
+
+@keyframes xx-clonemodal-pulse {
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
+ 50% { box-shadow: 0 0 0 8px rgba(239, 68, 68, 0); }
+}
+
+/* ── 进度阶段(上传中 / 克隆中 / 完成) ─────────────── */
+
+.xx-clonemodal-progress-body {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 24px;
+ padding: 24px 0 16px;
+}
+
+/* 进度步骤指示器 */
+.xx-clonemodal-steps-progress {
+ display: flex;
+ align-items: center;
+ gap: 0;
+ width: 100%;
+ max-width: 360px;
+}
+
+.xx-clonemodal-step-progress {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+ flex: 1;
+}
+
+.xx-clonemodal-step-icon {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ background: var(--xx-color-bg-tertiary, #f3f4f6);
+ border: 2px solid var(--xx-color-border, #e5e7eb);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 16px;
+ color: var(--xx-color-text-secondary, #6b7280);
+ transition: all 0.3s ease;
+}
+
+.xx-clonemodal-step-progress--active .xx-clonemodal-step-icon {
+ background: rgba(99, 102, 241, 0.08);
+ border-color: var(--xx-color-primary, #6366f1);
+ color: var(--xx-color-primary, #6366f1);
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.12);
+}
+
+.xx-clonemodal-step-progress--done .xx-clonemodal-step-icon {
+ background: rgba(34, 197, 94, 0.08);
+ border-color: #22c55e;
+ color: #22c55e;
+}
+
+.xx-clonemodal-step-progress--active .xx-clonemodal-step-label {
+ color: var(--xx-color-primary, #6366f1);
+ font-weight: 600;
+}
+
+.xx-clonemodal-step-progress--done .xx-clonemodal-step-label {
+ color: #22c55e;
+}
+
+.xx-clonemodal-step-connector {
+ flex: 0 0 40px;
+ height: 2px;
+ background: var(--xx-color-border, #e5e7eb);
+ margin-bottom: 20px;
+ transition: background 0.3s ease;
+}
+
+.xx-clonemodal-step-connector--done {
+ background: #86efac;
+}
+
+/* 进度信息 */
+.xx-clonemodal-progress-info {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ text-align: center;
+}
+
+.xx-clonemodal-progress-spinner {
+ width: 48px;
+ height: 48px;
+ border: 3px solid rgba(99, 102, 241, 0.15);
+ border-top-color: var(--xx-color-primary, #6366f1);
+ border-radius: 50%;
+ animation: xx-clonemodal-spin 0.8s linear infinite;
+}
+
+.xx-clonemodal-progress-spinner--cloning {
+ border-color: rgba(34, 197, 94, 0.15);
+ border-top-color: #22c55e;
+}
+
+.xx-clonemodal-progress-text {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--xx-color-text, #111827);
+}
+
+.xx-clonemodal-progress-sub {
+ margin: 0;
+ font-size: 13px;
+ color: var(--xx-color-text-secondary, #6b7280);
+}
+
/* ── 成功状态 ───────────────────────────────────────────── */
.xx-clonemodal-success {
display: flex;
flex-direction: column;
align-items: center;
- justify-content: center;
- padding: 48px 20px;
+ gap: 8px;
text-align: center;
}
.xx-clonemodal-success-icon {
- width: 56px;
- height: 56px;
- border-radius: 50%;
- background: rgba(34, 197, 94, 0.1);
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 28px;
- margin-bottom: 16px;
+ font-size: 48px;
+ line-height: 1;
+ animation: xx-clonemodal-bounce 0.6s ease-out;
+}
+
+@keyframes xx-clonemodal-bounce {
+ 0% { transform: scale(0); opacity: 0; }
+ 60% { transform: scale(1.2); opacity: 1; }
+ 100% { transform: scale(1); }
}
.xx-clonemodal-success-title {
+ margin: 0;
font-size: 18px;
- font-weight: 600;
+ font-weight: 700;
color: var(--xx-color-text, #111827);
- margin: 0 0 8px;
}
.xx-clonemodal-success-desc {
+ margin: 0;
font-size: 13px;
color: var(--xx-color-text-secondary, #6b7280);
- margin: 0;
- line-height: 1.5;
+ max-width: 280px;
+ line-height: 1.6;
}
/* ── 响应式 ─────────────────────────────────────────────── */
@@ -342,4 +563,48 @@
height: 24px;
font-size: 12px;
}
+
+ .xx-clonemodal-record-area {
+ flex-direction: column;
+ align-items: stretch;
+ text-align: center;
+ }
+
+ .xx-clonemodal-record-btn {
+ align-self: center;
+ }
+
+ .xx-clonemodal-steps-progress {
+ max-width: 280px;
+ }
+
+ .xx-clonemodal-footer {
+ flex-direction: column-reverse;
+ }
+
+ .xx-clonemodal-footer > * {
+ width: 100%;
+ }
+}
+
+@media (max-width: 480px) {
+ .xx-clonemodal-step-icon {
+ width: 32px;
+ height: 32px;
+ font-size: 13px;
+ }
+
+ .xx-clonemodal-step-connector {
+ flex: 0 0 16px;
+ margin-bottom: 16px;
+ }
+
+ .xx-clonemodal-progress-spinner {
+ width: 36px;
+ height: 36px;
+ }
+
+ .xx-clonemodal-success-icon {
+ font-size: 36px;
+ }
}
diff --git a/apps/web/src/config/navigation.ts b/apps/web/src/config/navigation.ts
index 49e2939b5..a32cf0f96 100644
--- a/apps/web/src/config/navigation.ts
+++ b/apps/web/src/config/navigation.ts
@@ -33,72 +33,75 @@ export interface NavGroup {
items: NavItem[];
}
-/** 全量导航项(Header 扁平列表使用) */
+/**
+ * 全量导航项(Header 扁平列表使用)
+ * 所有路径统一使用 /app 前缀,与路由配置保持一致
+ */
export const NAV_ITEMS: NavItem[] = [
{
key: "dashboard",
label: "概览",
- path: "/dashboard",
+ path: "/app/dashboard",
icon: React.createElement(DashboardOutlined),
},
{
key: "assets",
label: "素材库",
- path: "/assets",
+ path: "/app/assets",
icon: React.createElement(FileOutlined),
},
{
key: "titles",
label: "标题库",
- path: "/titles",
+ path: "/app/titles",
icon: React.createElement(FileTextOutlined),
},
{
key: "voices",
label: "配音库",
- path: "/voices",
+ path: "/app/voices",
icon: React.createElement(AudioOutlined),
},
{
key: "templates",
label: "模板库",
- path: "/templates",
+ path: "/app/templates",
icon: React.createElement(AppstoreOutlined),
},
{
key: "editing-planner",
label: "剪辑编辑器",
- path: "/editing-planner",
+ path: "/app/editing-planner",
icon: React.createElement(EditOutlined),
},
{
key: "my-templates",
label: "我的模板",
- path: "/my-templates",
+ path: "/app/my-templates",
icon: React.createElement(FolderOutlined),
},
{
key: "generate",
label: "一键生成",
- path: "/generate",
+ path: "/app/generate",
icon: React.createElement(VideoCameraOutlined),
},
{
key: "history",
label: "任务历史",
- path: "/history",
+ path: "/app/history",
icon: React.createElement(HistoryOutlined),
},
{
key: "products",
label: "成品库",
- path: "/products",
+ path: "/app/products",
icon: React.createElement(TrophyOutlined),
},
{
key: "duplication",
label: "查重",
- path: "/duplication",
+ path: "/app/duplication",
icon: React.createElement(ScanOutlined),
},
];
@@ -111,19 +114,19 @@ export const NAV_GROUPS: NavGroup[] = [
{
key: "dashboard",
label: "概览",
- path: "/dashboard",
+ path: "/app/dashboard",
icon: React.createElement(DashboardOutlined),
},
{
key: "generate",
label: "一键生成",
- path: "/generate",
+ path: "/app/generate",
icon: React.createElement(VideoCameraOutlined),
},
{
key: "editing-planner",
label: "剪辑编辑器",
- path: "/editing-planner",
+ path: "/app/editing-planner",
icon: React.createElement(EditOutlined),
},
],
@@ -134,37 +137,37 @@ export const NAV_GROUPS: NavGroup[] = [
{
key: "assets",
label: "素材库",
- path: "/assets",
+ path: "/app/assets",
icon: React.createElement(FileOutlined),
},
{
key: "voices",
label: "配音库",
- path: "/voices",
+ path: "/app/voices",
icon: React.createElement(AudioOutlined),
},
{
key: "titles",
label: "标题库",
- path: "/titles",
+ path: "/app/titles",
icon: React.createElement(FileTextOutlined),
},
{
key: "products",
label: "成品库",
- path: "/products",
+ path: "/app/products",
icon: React.createElement(TrophyOutlined),
},
{
key: "templates",
label: "模板库",
- path: "/templates",
+ path: "/app/templates",
icon: React.createElement(AppstoreOutlined),
},
{
key: "my-templates",
label: "我的模板",
- path: "/my-templates",
+ path: "/app/my-templates",
icon: React.createElement(FolderOutlined),
},
],
@@ -175,25 +178,25 @@ export const NAV_GROUPS: NavGroup[] = [
{
key: "history",
label: "任务历史",
- path: "/history",
+ path: "/app/history",
icon: React.createElement(HistoryOutlined),
},
{
key: "duplication",
label: "查重",
- path: "/duplication",
+ path: "/app/duplication",
icon: React.createElement(ScanOutlined),
},
{
key: "admin",
label: "控制台",
- path: "/admin",
+ path: "/app/admin",
icon: React.createElement(ControlOutlined),
},
{
key: "subscription",
label: "订阅管理",
- path: "/subscription",
+ path: "/app/subscription",
icon: React.createElement(CrownOutlined),
},
],
diff --git a/apps/web/src/config/navigation.tsx b/apps/web/src/config/navigation.tsx
index 3fdab37c4..a66ef3fd9 100644
--- a/apps/web/src/config/navigation.tsx
+++ b/apps/web/src/config/navigation.tsx
@@ -40,63 +40,63 @@ export const NAV_ITEMS: NavItem[] = [
{
key: "dashboard",
label: "概览",
- path: "/dashboard",
+ path: "/app/dashboard",
icon:
,
},
- { key: "assets", label: "素材库", path: "/assets", icon:
},
+ { key: "assets", label: "素材库", path: "/app/assets", icon:
},
{
key: "titles",
label: "标题库",
- path: "/titles",
+ path: "/app/titles",
icon:
,
},
- { key: "voices", label: "配音库", path: "/voices", icon:
},
+ { key: "voices", label: "配音库", path: "/app/voices", icon:
},
{
key: "voice-clone",
label: "我的音色",
- path: "/voice-clone",
+ path: "/app/voice-clone",
icon:
,
},
{
key: "templates",
label: "模板库",
- path: "/templates",
+ path: "/app/templates",
icon:
,
},
{
key: "editing-planner",
label: "剪辑编辑器",
- path: "/editing-planner",
+ path: "/app/editing-planner",
icon:
,
},
{
key: "my-templates",
label: "我的模板",
- path: "/my-templates",
+ path: "/app/my-templates",
icon:
,
},
{
key: "generate",
label: "一键生成",
- path: "/generate",
+ path: "/app/generate",
icon:
,
},
{
key: "history",
label: "任务历史",
- path: "/history",
+ path: "/app/history",
icon:
,
},
{
key: "products",
label: "成品库",
- path: "/products",
+ path: "/app/products",
icon:
,
},
{
key: "duplication",
label: "查重",
- path: "/duplication",
+ path: "/app/duplication",
icon:
,
},
];
@@ -111,13 +111,13 @@ export const NAV_GROUPS: NavGroup[] = [
{
key: "dashboard",
label: "首页",
- path: "/dashboard",
+ path: "/app/dashboard",
icon:
,
},
{
key: "generate",
label: "一键生成",
- path: "/generate",
+ path: "/app/generate",
icon:
,
},
],
@@ -128,37 +128,37 @@ export const NAV_GROUPS: NavGroup[] = [
{
key: "assets",
label: "素材库",
- path: "/assets",
+ path: "/app/assets",
icon:
,
},
{
key: "voices",
label: "配音库",
- path: "/voices",
+ path: "/app/voices",
icon:
,
},
{
key: "voice-clone",
label: "我的音色",
- path: "/voice-clone",
+ path: "/app/voice-clone",
icon:
,
},
{
key: "titles",
label: "标题库",
- path: "/titles",
+ path: "/app/titles",
icon:
,
},
{
key: "products",
label: "成片库",
- path: "/products",
+ path: "/app/products",
icon:
,
},
{
key: "templates",
label: "模板库",
- path: "/templates",
+ path: "/app/templates",
icon:
,
},
],
@@ -169,19 +169,19 @@ export const NAV_GROUPS: NavGroup[] = [
{
key: "history",
label: "任务历史",
- path: "/history",
+ path: "/app/history",
icon:
,
},
{
key: "admin",
label: "控制台",
- path: "/admin",
+ path: "/app/admin",
icon:
,
},
{
key: "subscription",
label: "订阅管理",
- path: "/subscription",
+ path: "/app/subscription",
icon:
,
},
],
diff --git a/apps/web/src/pages/admin/AdminComingSoon.tsx b/apps/web/src/pages/admin/AdminComingSoon.tsx
index 77d3fd17b..7be809566 100644
--- a/apps/web/src/pages/admin/AdminComingSoon.tsx
+++ b/apps/web/src/pages/admin/AdminComingSoon.tsx
@@ -17,7 +17,7 @@ const AdminComingSoon: React.FC = () => {
extra={[