From f0eed0ca3fb3f9fee2f8a08537834a2ea29c40ae Mon Sep 17 00:00:00 2001 From: CI Test Date: Wed, 1 Jul 2026 12:26:48 +0800 Subject: [PATCH] style: fix prettier formatting issues --- apps/web/src/api/accounts.ts | 6 +- apps/web/src/components/business/business.css | 12 +- apps/web/src/components/layout/Header.tsx | 2 - apps/web/src/components/layout/MainLayout.css | 1 - apps/web/src/components/layout/PageHead.tsx | 10 +- apps/web/src/components/layout/Sidebar.tsx | 4 +- .../src/components/modals/CloneVoiceModal.tsx | 22 +- .../components/modals/clone-voice-modal.css | 43 ++- apps/web/src/components/ui/Button.tsx | 11 +- apps/web/src/components/ui/Form.tsx | 5 +- apps/web/src/components/ui/Input.tsx | 18 +- apps/web/src/components/ui/Select.tsx | 5 +- apps/web/src/components/ui/Table.tsx | 5 +- apps/web/src/components/ui/Tag.tsx | 5 +- apps/web/src/components/ui/Tooltip.tsx | 5 +- apps/web/src/pages/accounts/Accounts.tsx | 9 +- apps/web/src/pages/accounts/accounts.css | 2 +- apps/web/src/pages/assets/AssetLibrary.tsx | 203 +++++++++-- apps/web/src/pages/dashboard/Dashboard.tsx | 5 +- apps/web/src/pages/dashboard/dashboard.css | 6 +- .../pages/duplication/DuplicationDetail.tsx | 38 ++- .../pages/duplication/DuplicationResults.tsx | 53 +-- .../pages/duplication/DuplicationUpload.tsx | 13 +- .../web/src/pages/duplication/duplication.css | 2 +- .../components/TemplatePanel.tsx | 26 +- apps/web/src/pages/generate/GeneratePage.tsx | 94 +++-- apps/web/src/pages/generate/generate.css | 6 +- apps/web/src/pages/history/TaskHistory.tsx | 6 +- apps/web/src/pages/home/HomePage.tsx | 57 +++- apps/web/src/pages/home/home-page.css | 58 +++- .../web/src/pages/products/ProductLibrary.tsx | 322 +++++++++++++++--- apps/web/src/pages/products/products.css | 12 +- apps/web/src/pages/profile/Settings.tsx | 12 +- apps/web/src/pages/subscription/Billing.css | 16 +- apps/web/src/pages/subscription/Billing.tsx | 4 +- apps/web/src/pages/subscription/Plans.css | 24 +- .../subscription/UpgradeSubscription.css | 15 +- .../subscription/UpgradeSubscription.tsx | 13 +- .../src/pages/templates/TemplateLibrary.tsx | 2 +- apps/web/src/pages/titles/TitleLibrary.tsx | 289 +++++++++++++--- apps/web/src/pages/titles/titles.css | 16 +- apps/web/src/pages/voice-clone/VoiceClone.tsx | 4 +- .../web/src/pages/voice-clone/voice-clone.css | 49 ++- apps/web/src/pages/voices/VoiceLibrary.tsx | 214 ++++++++++-- apps/web/src/styles/global.css | 24 +- 45 files changed, 1339 insertions(+), 409 deletions(-) diff --git a/apps/web/src/api/accounts.ts b/apps/web/src/api/accounts.ts index 5ca839b0b..24209fcd2 100644 --- a/apps/web/src/api/accounts.ts +++ b/apps/web/src/api/accounts.ts @@ -116,7 +116,7 @@ const delay = (ms: number) => new Promise((r) => setTimeout(r, ms)); /** 获取指定平台的账号列表 */ export async function getAccountsByPlatform( - platformId: PlatformId + platformId: PlatformId, ): Promise { await delay(300); return MOCK_ACCOUNTS.filter((a) => a.platform_id === platformId); @@ -129,9 +129,7 @@ export async function getAllAccounts(): Promise { } /** 绑定新账号 */ -export async function bindAccount( - data: BindAccountRequest -): Promise { +export async function bindAccount(data: BindAccountRequest): Promise { await delay(500); const newAccount: Account = { id: `acc-${Date.now()}`, diff --git a/apps/web/src/components/business/business.css b/apps/web/src/components/business/business.css index 8c3239583..40d3fb6a8 100644 --- a/apps/web/src/components/business/business.css +++ b/apps/web/src/components/business/business.css @@ -145,7 +145,8 @@ .xx-search-input:focus { border-color: var(--primary-color); - box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary-color) 10%, transparent); + box-shadow: 0 0 0 4px + color-mix(in srgb, var(--primary-color) 10%, transparent); } /* ==================== Modal ==================== */ @@ -221,7 +222,8 @@ .xx-quota-item:hover { border-color: var(--primary-color); - box-shadow: 0 8px 24px color-mix(in srgb, var(--primary-color) 10%, transparent); + box-shadow: 0 8px 24px + color-mix(in srgb, var(--primary-color) 10%, transparent); } /* ==================== 进度条 ==================== */ @@ -331,7 +333,8 @@ .ant-select-focused .ant-select-selector { border-color: var(--primary-color) !important; - box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 10%, transparent) !important; + box-shadow: 0 0 0 3px + color-mix(in srgb, var(--primary-color) 10%, transparent) !important; } /* Input overrides */ @@ -347,7 +350,8 @@ .ant-input:focus { border-color: var(--primary-color) !important; - box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 10%, transparent) !important; + box-shadow: 0 0 0 3px + color-mix(in srgb, var(--primary-color) 10%, transparent) !important; } /* Progress overrides */ diff --git a/apps/web/src/components/layout/Header.tsx b/apps/web/src/components/layout/Header.tsx index cd1fda1b5..cb1223282 100644 --- a/apps/web/src/components/layout/Header.tsx +++ b/apps/web/src/components/layout/Header.tsx @@ -17,8 +17,6 @@ import type { MenuProps } from "antd"; import { NAV_ITEMS } from "@/config/navigation"; import "./Header.css"; - - const Header: React.FC = () => { const navigate = useNavigate(); const location = useLocation(); diff --git a/apps/web/src/components/layout/MainLayout.css b/apps/web/src/components/layout/MainLayout.css index 1a9c2dc47..369e4ddd0 100644 --- a/apps/web/src/components/layout/MainLayout.css +++ b/apps/web/src/components/layout/MainLayout.css @@ -142,7 +142,6 @@ .xx-app-sidebar:not(.xx-collapsed) .xx-sidebar-toggle-label { display: inline; } - } /* ============================================================ diff --git a/apps/web/src/components/layout/PageHead.tsx b/apps/web/src/components/layout/PageHead.tsx index a5ac6f96b..1036cf3e7 100644 --- a/apps/web/src/components/layout/PageHead.tsx +++ b/apps/web/src/components/layout/PageHead.tsx @@ -138,7 +138,10 @@ const PageHead: React.FC = ({ {breadcrumbItems.map((item, index) => { const isLast = index === breadcrumbItems.length - 1; return ( -
  • +
  • {index > 0 && ( )} @@ -157,7 +160,10 @@ const PageHead: React.FC = ({ {item.label} ) : ( - + {index === 0 ? ( ) : null} diff --git a/apps/web/src/components/layout/Sidebar.tsx b/apps/web/src/components/layout/Sidebar.tsx index 553541b44..6d887f142 100644 --- a/apps/web/src/components/layout/Sidebar.tsx +++ b/apps/web/src/components/layout/Sidebar.tsx @@ -33,7 +33,9 @@ const Sidebar: React.FC = () => { }; return ( -
    +
    {NAV_GROUPS.map((group) => (
    {!collapsed && ( diff --git a/apps/web/src/components/modals/CloneVoiceModal.tsx b/apps/web/src/components/modals/CloneVoiceModal.tsx index 3fddcf256..07c157b7c 100644 --- a/apps/web/src/components/modals/CloneVoiceModal.tsx +++ b/apps/web/src/components/modals/CloneVoiceModal.tsx @@ -124,7 +124,9 @@ const CloneVoiceModal: React.FC = ({ // Mock:模拟上传 + 克隆过程 const result = await createVoiceClone({ name, - audio_url: selectedFile ? `mock://${selectedFile.name}` : "mock://recorded-audio", + audio_url: selectedFile + ? `mock://${selectedFile.name}` + : "mock://recorded-audio", }); setStep("success"); @@ -211,7 +213,9 @@ const CloneVoiceModal: React.FC = ({

    - {isRecording ? "录制中…再次点击停止" : "点击按钮开始录制你的声音"} + {isRecording + ? "录制中…再次点击停止" + : "点击按钮开始录制你的声音"}

    -
    @@ -255,7 +265,9 @@ const CloneVoiceModal: React.FC = ({

    克隆已提交

    -

    音色正在生成中,完成后将出现在列表中

    +

    + 音色正在生成中,完成后将出现在列表中 +

    )} diff --git a/apps/web/src/components/modals/clone-voice-modal.css b/apps/web/src/components/modals/clone-voice-modal.css index 7843f4a0d..33cc71dce 100644 --- a/apps/web/src/components/modals/clone-voice-modal.css +++ b/apps/web/src/components/modals/clone-voice-modal.css @@ -36,13 +36,16 @@ color: var(--text-primary, #101828); font-size: 14px; line-height: 1.5; - transition: border-color 0.2s, box-shadow 0.2s; + transition: + border-color 0.2s, + box-shadow 0.2s; outline: none; } .cvm-input:focus { border-color: var(--primary, #6366f1); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 12%, transparent); + box-shadow: 0 0 0 3px + color-mix(in srgb, var(--primary-color) 12%, transparent); } .cvm-input::placeholder { @@ -58,7 +61,9 @@ text-align: center; background: var(--bg-subtle, #f8fafc); cursor: pointer; - transition: border-color 0.2s, background 0.2s; + transition: + border-color 0.2s, + background 0.2s; } .cvm-upload-zone:hover { @@ -135,10 +140,17 @@ font-size: 32px; line-height: 1; padding: 0; - background: linear-gradient(135deg, var(--error-color, #ef4444), var(--error-dark, #dc2626)); + background: linear-gradient( + 135deg, + var(--error-color, #ef4444), + var(--error-dark, #dc2626) + ); color: var(--text-inverse); - box-shadow: 0 4px 14px color-mix(in srgb, var(--error-color, #ef4444) 35%, transparent); - transition: transform 0.15s, box-shadow 0.15s; + box-shadow: 0 4px 14px + color-mix(in srgb, var(--error-color, #ef4444) 35%, transparent); + transition: + transform 0.15s, + box-shadow 0.15s; display: inline-flex; align-items: center; justify-content: center; @@ -146,7 +158,8 @@ .cvm-record-btn:hover { transform: scale(1.06); - box-shadow: 0 6px 20px color-mix(in srgb, var(--error-color, #ef4444) 45%, transparent); + box-shadow: 0 6px 20px + color-mix(in srgb, var(--error-color, #ef4444) 45%, transparent); } .cvm-record-btn:active { @@ -158,8 +171,15 @@ } @keyframes cvm-pulse { - 0%, 100% { box-shadow: 0 4px 14px color-mix(in srgb, var(--error-color, #ef4444) 35%, transparent); } - 50% { box-shadow: 0 4px 28px color-mix(in srgb, var(--error-color, #ef4444) 60%, transparent); } + 0%, + 100% { + box-shadow: 0 4px 14px + color-mix(in srgb, var(--error-color, #ef4444) 35%, transparent); + } + 50% { + box-shadow: 0 4px 28px + color-mix(in srgb, var(--error-color, #ef4444) 60%, transparent); + } } /* ── 提示条 ─────────────────────────────────────────────── */ @@ -215,7 +235,9 @@ } @keyframes cvm-spin { - to { transform: rotate(360deg); } + to { + transform: rotate(360deg); + } } .cvm-uploading-text { @@ -262,7 +284,6 @@ /* ── 响应式 ─────────────────────────────────────────────── */ - @media (max-width: 768px) { .cvm-overlay { padding: var(--space-md); diff --git a/apps/web/src/components/ui/Button.tsx b/apps/web/src/components/ui/Button.tsx index 16b33a1e4..a6fd9522b 100644 --- a/apps/web/src/components/ui/Button.tsx +++ b/apps/web/src/components/ui/Button.tsx @@ -9,12 +9,7 @@ import classNames from "classnames"; import "./ui.css"; export type ButtonType = - | "primary" - | "secondary" - | "ghost" - | "text" - | "danger" - | "link"; + "primary" | "secondary" | "ghost" | "text" | "danger" | "link"; export type ButtonSize = "sm" | "md" | "lg"; @@ -70,7 +65,9 @@ const Button: React.FC = ({ ...rest }) => { const antdType = type ?? toAntdType(buttonType); - const antdSize = size ?? (buttonSize === "sm" ? "small" : buttonSize === "lg" ? "large" : "middle"); + const antdSize = + size ?? + (buttonSize === "sm" ? "small" : buttonSize === "lg" ? "large" : "middle"); const v21Class = classNames( "xx-btn", diff --git a/apps/web/src/components/ui/Form.tsx b/apps/web/src/components/ui/Form.tsx index f8647b396..50e1f9ecf 100644 --- a/apps/web/src/components/ui/Form.tsx +++ b/apps/web/src/components/ui/Form.tsx @@ -20,7 +20,10 @@ const Form = ({ className, compact, children, ...rest }: FormProps) => { className, ); return ( - )}> + )} + > {children as React.ReactNode} ); diff --git a/apps/web/src/components/ui/Input.tsx b/apps/web/src/components/ui/Input.tsx index 23ad6f212..405af59ca 100644 --- a/apps/web/src/components/ui/Input.tsx +++ b/apps/web/src/components/ui/Input.tsx @@ -9,7 +9,11 @@ import type { InputRef } from "antd/es/input"; import classNames from "classnames"; import "./ui.css"; -const { Search: AntSearch, TextArea: AntTextArea, Password: AntPassword } = AntInput; +const { + Search: AntSearch, + TextArea: AntTextArea, + Password: AntPassword, +} = AntInput; export interface InputProps extends AntInputProps { /** 是否处于错误状态 */ @@ -46,11 +50,7 @@ const Search: React.FC< const TextArea: React.FC< React.ComponentProps & { error?: boolean } > = ({ className, error, ...rest }) => { - const v21Class = classNames( - "xx-input", - error && "xx-input-error", - className, - ); + const v21Class = classNames("xx-input", error && "xx-input-error", className); return ; }; @@ -59,11 +59,7 @@ const Password = React.forwardRef< InputRef, React.ComponentProps & { error?: boolean } >(({ className, error, ...rest }, ref) => { - const v21Class = classNames( - "xx-input", - error && "xx-input-error", - className, - ); + const v21Class = classNames("xx-input", error && "xx-input-error", className); return ; }); Password.displayName = "Password"; diff --git a/apps/web/src/components/ui/Select.tsx b/apps/web/src/components/ui/Select.tsx index 86d6b50c8..ae2aed3f6 100644 --- a/apps/web/src/components/ui/Select.tsx +++ b/apps/web/src/components/ui/Select.tsx @@ -19,10 +19,7 @@ const Select: React.FC = ({ ...rest }) => { const v21Class = classNames("xx-select", className); - const v21DropdownClass = classNames( - "xx-select-dropdown", - dropdownClassName, - ); + const v21DropdownClass = classNames("xx-select-dropdown", dropdownClassName); return ( - extends AntTableProps { +export interface TableProps< + RecordType = unknown, +> extends AntTableProps { /** 使用 V21 样式 */ v21?: boolean; } diff --git a/apps/web/src/components/ui/Tag.tsx b/apps/web/src/components/ui/Tag.tsx index 357b82939..25859e684 100644 --- a/apps/web/src/components/ui/Tag.tsx +++ b/apps/web/src/components/ui/Tag.tsx @@ -4,7 +4,10 @@ */ import React from "react"; import { Tag as AntTag, Badge as AntBadge } from "antd"; -import type { TagProps as AntTagProps, BadgeProps as AntBadgeProps } from "antd"; +import type { + TagProps as AntTagProps, + BadgeProps as AntBadgeProps, +} from "antd"; import classNames from "classnames"; import "./ui.css"; diff --git a/apps/web/src/components/ui/Tooltip.tsx b/apps/web/src/components/ui/Tooltip.tsx index d75f310c0..77f4b7e00 100644 --- a/apps/web/src/components/ui/Tooltip.tsx +++ b/apps/web/src/components/ui/Tooltip.tsx @@ -3,10 +3,7 @@ * 封装 Ant Design Tooltip 和 Popover,应用 V21 设计系统样式 */ import React from "react"; -import { - Tooltip as AntTooltip, - Popover as AntPopover, -} from "antd"; +import { Tooltip as AntTooltip, Popover as AntPopover } from "antd"; import classNames from "classnames"; import "./ui.css"; diff --git a/apps/web/src/pages/accounts/Accounts.tsx b/apps/web/src/pages/accounts/Accounts.tsx index f5f56dcfc..30b2d6076 100644 --- a/apps/web/src/pages/accounts/Accounts.tsx +++ b/apps/web/src/pages/accounts/Accounts.tsx @@ -186,7 +186,7 @@ const Accounts: React.FC = () => { /** 统计已绑定账号数 */ const totalBound = accountQueries.reduce( (sum, q) => sum + (q.data?.length ?? 0), - 0 + 0, ); const totalPlatforms = PLATFORMS.length; @@ -215,10 +215,9 @@ const Accounts: React.FC = () => {
    📊 - 已绑定{" "} - {totalBound} 个账号 / - 支持 {totalPlatforms}{" "} - 个平台 + 已绑定 {totalBound}{" "} + 个账号 / 支持{" "} + {totalPlatforms} 个平台
    diff --git a/apps/web/src/pages/accounts/accounts.css b/apps/web/src/pages/accounts/accounts.css index eab3ade67..1c24dd4bf 100644 --- a/apps/web/src/pages/accounts/accounts.css +++ b/apps/web/src/pages/accounts/accounts.css @@ -35,7 +35,7 @@ } .acc-card:hover { - box-shadow: 0 4px 20px var(--shadow-sm, rgba(0,0,0,0.06)); + box-shadow: 0 4px 20px var(--shadow-sm, rgba(0, 0, 0, 0.06)); } /* ── 卡片头部 ───────────────────────────────────────────── */ diff --git a/apps/web/src/pages/assets/AssetLibrary.tsx b/apps/web/src/pages/assets/AssetLibrary.tsx index 3c242fcc5..1fc381f4c 100644 --- a/apps/web/src/pages/assets/AssetLibrary.tsx +++ b/apps/web/src/pages/assets/AssetLibrary.tsx @@ -4,12 +4,7 @@ * 使用 mock 数据,后端 API 对接暂不要求 */ import React, { useMemo, useState } from "react"; -import { - Upload, - Modal as AntModal, - message, - Popconfirm, -} from "antd"; +import { Upload, Modal as AntModal, message, Popconfirm } from "antd"; import { PlusOutlined, SearchOutlined, @@ -61,18 +56,122 @@ const MOCK_LIBRARIES: LibraryItem[] = [ ]; const MOCK_ASSETS: AssetItem[] = [ - { id: "a-1", name: "产品功能演示_v2.mp4", kind: "video", status: "ok", statusLabel: "合格", duration: "02:34", size: 128, createdAt: "2026-06-28" }, - { id: "a-2", name: "品牌宣传片_终版.mp4", kind: "video", status: "warn", statusLabel: "待优化", duration: "05:12", size: 356, createdAt: "2026-06-27" }, - { id: "a-3", name: "用户案例_张三.mp4", kind: "video", status: "bad", statusLabel: "不合格", duration: "01:48", size: 96, createdAt: "2026-06-26" }, - { id: "a-4", name: "功能讲解_配音.mp3", kind: "voice", status: "ok", statusLabel: "合格", duration: "03:22", size: 48, createdAt: "2026-06-25" }, - { id: "a-5", name: "旁白_中文版.wav", kind: "voice", status: "info", statusLabel: "处理中", duration: "04:05", size: 72, createdAt: "2026-06-24" }, - { id: "a-6", name: "Banner_春季活动.png", kind: "image", status: "ok", statusLabel: "合格", size: 2.4, createdAt: "2026-06-23" }, - { id: "a-7", name: "产品截图_首页.png", kind: "image", status: "ok", statusLabel: "合格", size: 1.8, createdAt: "2026-06-22" }, - { id: "a-8", name: "社交媒体封面.jpg", kind: "image", status: "warn", statusLabel: "待优化", size: 3.2, createdAt: "2026-06-21" }, - { id: "a-9", name: "操作指南_录屏.mp4", kind: "video", status: "ok", statusLabel: "合格", duration: "08:15", size: 512, createdAt: "2026-06-20" }, - { id: "a-10", name: "背景音乐_轻快.mp3", kind: "voice", status: "ok", statusLabel: "合格", duration: "02:00", size: 36, createdAt: "2026-06-19" }, - { id: "a-11", name: "Logo_高清.png", kind: "image", status: "ok", statusLabel: "合格", size: 0.8, createdAt: "2026-06-18" }, - { id: "a-12", name: "活动预告_短视频.mp4", kind: "video", status: "info", statusLabel: "处理中", duration: "00:30", size: 64, createdAt: "2026-06-17" }, + { + id: "a-1", + name: "产品功能演示_v2.mp4", + kind: "video", + status: "ok", + statusLabel: "合格", + duration: "02:34", + size: 128, + createdAt: "2026-06-28", + }, + { + id: "a-2", + name: "品牌宣传片_终版.mp4", + kind: "video", + status: "warn", + statusLabel: "待优化", + duration: "05:12", + size: 356, + createdAt: "2026-06-27", + }, + { + id: "a-3", + name: "用户案例_张三.mp4", + kind: "video", + status: "bad", + statusLabel: "不合格", + duration: "01:48", + size: 96, + createdAt: "2026-06-26", + }, + { + id: "a-4", + name: "功能讲解_配音.mp3", + kind: "voice", + status: "ok", + statusLabel: "合格", + duration: "03:22", + size: 48, + createdAt: "2026-06-25", + }, + { + id: "a-5", + name: "旁白_中文版.wav", + kind: "voice", + status: "info", + statusLabel: "处理中", + duration: "04:05", + size: 72, + createdAt: "2026-06-24", + }, + { + id: "a-6", + name: "Banner_春季活动.png", + kind: "image", + status: "ok", + statusLabel: "合格", + size: 2.4, + createdAt: "2026-06-23", + }, + { + id: "a-7", + name: "产品截图_首页.png", + kind: "image", + status: "ok", + statusLabel: "合格", + size: 1.8, + createdAt: "2026-06-22", + }, + { + id: "a-8", + name: "社交媒体封面.jpg", + kind: "image", + status: "warn", + statusLabel: "待优化", + size: 3.2, + createdAt: "2026-06-21", + }, + { + id: "a-9", + name: "操作指南_录屏.mp4", + kind: "video", + status: "ok", + statusLabel: "合格", + duration: "08:15", + size: 512, + createdAt: "2026-06-20", + }, + { + id: "a-10", + name: "背景音乐_轻快.mp3", + kind: "voice", + status: "ok", + statusLabel: "合格", + duration: "02:00", + size: 36, + createdAt: "2026-06-19", + }, + { + id: "a-11", + name: "Logo_高清.png", + kind: "image", + status: "ok", + statusLabel: "合格", + size: 0.8, + createdAt: "2026-06-18", + }, + { + id: "a-12", + name: "活动预告_短视频.mp4", + kind: "video", + status: "info", + statusLabel: "处理中", + duration: "00:30", + size: 64, + createdAt: "2026-06-17", + }, ]; const MAX_FILE_SIZE = 2048 * 1024 * 1024; @@ -83,17 +182,23 @@ const LARGE_FILE_THRESHOLD = 100 * 1024 * 1024; * ============================================================ */ const kindIcon = (kind: AssetKind) => { switch (kind) { - case "video": return ; - case "voice": return ; - case "image": return ; + case "video": + return ; + case "voice": + return ; + case "image": + return ; } }; const kindLabel = (kind: AssetKind) => { switch (kind) { - case "video": return "视频"; - case "voice": return "配音"; - case "image": return "图片"; + case "video": + return "视频"; + case "voice": + return "配音"; + case "image": + return "图片"; } }; @@ -116,9 +221,7 @@ const StatusPill: React.FC<{ status: StatusType; label: string }> = ({ status, label, }) => ( - - {label} - + {label} ); /* ============================================================ @@ -335,7 +438,13 @@ const AssetLibrary: React.FC = () => { className={`xx-asset-library-item${lib.id === activeLibId ? " active" : ""}`} onClick={() => setActiveLibId(lib.id)} > -
    +

    {kindIcon(lib.kind)} {lib.name}

    @@ -434,7 +543,12 @@ const AssetLibrary: React.FC = () => { - + 共 {filteredAssets.length} 个素材
    @@ -455,7 +569,11 @@ const AssetLibrary: React.FC = () => { okText="删除" cancelText="取消" > - @@ -496,9 +614,22 @@ const AssetLibrary: React.FC = () => { cancelText="取消" destroyOnClose > -
    +
    -
    +
    名称
    { />
    -
    +
    类型
    {

    点击或拖拽素材到此区域上传

    -

    - 支持视频、图片、音频格式 -

    +

    支持视频、图片、音频格式

    @@ -673,7 +696,8 @@ const GeneratePage: React.FC = () => { {/* 提示 */} - 💡 提示:录音环境越安静,克隆效果越好。建议使用专业麦克风录制。 + 💡 + 提示:录音环境越安静,克隆效果越好。建议使用专业麦克风录制。
    )} @@ -907,11 +931,11 @@ const GeneratePage: React.FC = () => { {/* ════════════ 右侧:预览面板 ════════════ */}
    - -
    + +
    预览 @@ -927,14 +951,23 @@ const GeneratePage: React.FC = () => { controls preload="none" poster="" - style={{ width: "100%", height: "100%", objectFit: "cover" }} + style={{ + width: "100%", + height: "100%", + objectFit: "cover", + }} /> ) : (
    - + 预览视频
    @@ -983,7 +1016,10 @@ const GeneratePage: React.FC = () => {
    ))} {/* 播放头 */} -
    +
    {/* 场景列表 */} @@ -991,7 +1027,9 @@ const GeneratePage: React.FC = () => { {MOCK_TIMELINE.map((item, idx) => (
    {idx + 1}
    - {item.scene} + + {item.scene} + {item.time}
    ))} @@ -1012,18 +1050,10 @@ const GeneratePage: React.FC = () => {
    {generated && (
    - -
    diff --git a/apps/web/src/pages/generate/generate.css b/apps/web/src/pages/generate/generate.css index b895df2a4..54998ce48 100644 --- a/apps/web/src/pages/generate/generate.css +++ b/apps/web/src/pages/generate/generate.css @@ -383,7 +383,11 @@ .xx-video-preview-overlay { position: absolute; inset: 0; - background: radial-gradient(circle at 72% 28%, rgba(255, 255, 255, 0.2), transparent 40%); + background: radial-gradient( + circle at 72% 28%, + rgba(255, 255, 255, 0.2), + transparent 40% + ); pointer-events: none; } diff --git a/apps/web/src/pages/history/TaskHistory.tsx b/apps/web/src/pages/history/TaskHistory.tsx index c508de52e..8febe80d7 100644 --- a/apps/web/src/pages/history/TaskHistory.tsx +++ b/apps/web/src/pages/history/TaskHistory.tsx @@ -211,7 +211,7 @@ const TaskHistory: React.FC = () => { const totalPages = Math.ceil(filteredTasks.length / PAGE_SIZE); const paginatedTasks = filteredTasks.slice( (currentPage - 1) * PAGE_SIZE, - currentPage * PAGE_SIZE + currentPage * PAGE_SIZE, ); // 切换 Tab 时重置页码 @@ -249,9 +249,7 @@ const TaskHistory: React.FC = () => { onClick={() => handleTabChange(tab.key)} > {tab.label} - - {tabCounts[tab.key]} - + {tabCounts[tab.key]} ))}
    diff --git a/apps/web/src/pages/home/HomePage.tsx b/apps/web/src/pages/home/HomePage.tsx index beac13d81..a77c83c36 100644 --- a/apps/web/src/pages/home/HomePage.tsx +++ b/apps/web/src/pages/home/HomePage.tsx @@ -29,14 +29,22 @@ const HeroSection: React.FC = () => { 一键生成短视频

    - 基于先进的 AI 技术,自动识别视频亮点,智能剪辑、配音、加字幕。 - 30 秒内将长视频转化为适合各平台传播的精品短视频。 + 基于先进的 AI 技术,自动识别视频亮点,智能剪辑、配音、加字幕。 30 + 秒内将长视频转化为适合各平台传播的精品短视频。

    - -
    @@ -48,7 +56,11 @@ const HeroSection: React.FC = () => {
    🎬
    -
    @@ -153,7 +165,12 @@ const PLANS = [ price: "免费", period: "", desc: "适合个人体验,快速上手", - features: ["每月 5 次 AI 生成", "720p 视频导出", "基础模板库", "1 个平台账号绑定"], + features: [ + "每月 5 次 AI 生成", + "720p 视频导出", + "基础模板库", + "1 个平台账号绑定", + ], highlighted: false, cta: "免费开始", }, @@ -210,7 +227,9 @@ const PricingSection: React.FC = () => {

    {plan.name}

    {plan.price} - {plan.period && {plan.period}} + {plan.period && ( + {plan.period} + )}

    {plan.desc}

      @@ -247,7 +266,11 @@ const CTASection: React.FC = () => {

      免费注册,立即体验 AI 智能视频创作。无需信用卡,零风险上手。

      -
    @@ -273,15 +296,27 @@ const HomePage: React.FC = () => { {/* 顶部导航栏 */}
    -
    - -
    diff --git a/apps/web/src/pages/home/home-page.css b/apps/web/src/pages/home/home-page.css index f66e2c7c8..391f2bb8d 100644 --- a/apps/web/src/pages/home/home-page.css +++ b/apps/web/src/pages/home/home-page.css @@ -12,7 +12,8 @@ min-height: 100vh; background: var(--bg-base, #fafafa); color: var(--text-primary, #1a1a2e); - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; overflow-x: hidden; } @@ -24,7 +25,7 @@ left: 0; right: 0; z-index: 100; - background: var(--bg-glass, rgba(255,255,255,0.72)); + background: var(--bg-glass, rgba(255, 255, 255, 0.72)); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border-bottom: 1px solid var(--line, rgba(0, 0, 0, 0.06)); @@ -70,7 +71,11 @@ .hp-hero { padding: 120px 24px 60px; - background: linear-gradient(180deg, color-mix(in srgb, var(--primary-color) 4%, transparent) 0%, transparent 60%); + background: linear-gradient( + 180deg, + color-mix(in srgb, var(--primary-color) 4%, transparent) 0%, + transparent 60% + ); } .hp-hero-inner { @@ -133,12 +138,17 @@ position: relative; border-radius: var(--radius-lg, 16px); overflow: hidden; - background: linear-gradient(135deg, var(--primary-color) 0%, var(--color-primary-400, #8b5cf6) 100%); + background: linear-gradient( + 135deg, + var(--primary-color) 0%, + var(--color-primary-400, #8b5cf6) 100% + ); aspect-ratio: 16 / 10; display: flex; align-items: center; justify-content: center; - box-shadow: 0 20px 60px color-mix(in srgb, var(--primary-color) 18%, transparent); + box-shadow: 0 20px 60px + color-mix(in srgb, var(--primary-color) 18%, transparent); } .hp-hero-video-inner { @@ -159,7 +169,7 @@ width: 56px; height: 56px; border-radius: 50%; - background: var(--bg-elevated, rgba(255,255,255,0.92)); + background: var(--bg-elevated, rgba(255, 255, 255, 0.92)); border: none; cursor: pointer; display: flex; @@ -167,13 +177,15 @@ justify-content: center; font-size: 20px; color: var(--primary, var(--primary-color)); - box-shadow: 0 4px 20px var(--shadow-lg, rgba(0,0,0,0.15)); - transition: transform 0.2s ease, box-shadow 0.2s ease; + box-shadow: 0 4px 20px var(--shadow-lg, rgba(0, 0, 0, 0.15)); + transition: + transform 0.2s ease, + box-shadow 0.2s ease; } .hp-hero-play:hover { transform: scale(1.08); - box-shadow: 0 6px 28px var(--shadow-xl, rgba(0,0,0,0.2)); + box-shadow: 0 6px 28px var(--shadow-xl, rgba(0, 0, 0, 0.2)); } .hp-hero-info { @@ -249,12 +261,14 @@ background: var(--bg-surface, #fff); border: 1px solid var(--line, rgba(0, 0, 0, 0.06)); text-align: center; - transition: transform 0.2s ease, box-shadow 0.2s ease; + transition: + transform 0.2s ease, + box-shadow 0.2s ease; } .hp-feature-card:hover { transform: translateY(-4px); - box-shadow: 0 12px 40px var(--shadow-md, rgba(0,0,0,0.08)); + box-shadow: 0 12px 40px var(--shadow-md, rgba(0, 0, 0, 0.08)); } .hp-feature-icon { @@ -287,7 +301,12 @@ .hp-workflow { padding: 80px 0; - background: linear-gradient(180deg, transparent 0%, rgba(99, 102, 241, 0.03) 50%, transparent 100%); + background: linear-gradient( + 180deg, + transparent 0%, + rgba(99, 102, 241, 0.03) 50%, + transparent 100% + ); } .hp-step-grid { @@ -375,17 +394,20 @@ display: flex; flex-direction: column; gap: 16px; - transition: transform 0.2s ease, box-shadow 0.2s ease; + transition: + transform 0.2s ease, + box-shadow 0.2s ease; } .hp-pricing-card:hover { transform: translateY(-4px); - box-shadow: 0 12px 40px var(--shadow-md, rgba(0,0,0,0.08)); + box-shadow: 0 12px 40px var(--shadow-md, rgba(0, 0, 0, 0.08)); } .hp-pricing-card--highlight { border-color: var(--primary, var(--primary-color)); - box-shadow: 0 8px 32px color-mix(in srgb, var(--primary-color) 12%, transparent); + box-shadow: 0 8px 32px + color-mix(in srgb, var(--primary-color) 12%, transparent); } .hp-pricing-badge { @@ -461,7 +483,11 @@ text-align: center; padding: 60px 40px; border-radius: var(--radius-lg, 16px); - background: linear-gradient(135deg, color-mix(in srgb, var(--primary-color) 6%, transparent) 0%, color-mix(in srgb, var(--color-primary-400, #8b5cf6) 6%, transparent) 100%); + background: linear-gradient( + 135deg, + color-mix(in srgb, var(--primary-color) 6%, transparent) 0%, + color-mix(in srgb, var(--color-primary-400, #8b5cf6) 6%, transparent) 100% + ); border: 1px solid var(--line, rgba(0, 0, 0, 0.06)); display: flex; flex-direction: column; diff --git a/apps/web/src/pages/products/ProductLibrary.tsx b/apps/web/src/pages/products/ProductLibrary.tsx index 30826e4a0..edc688cb9 100644 --- a/apps/web/src/pages/products/ProductLibrary.tsx +++ b/apps/web/src/pages/products/ProductLibrary.tsx @@ -3,7 +3,13 @@ * 卡片网格布局,支持视频播放/下载/分享、批量操作、筛选 * 使用 mock 数据,后端 API 对接暂不要求 */ -import React, { useMemo, useState, useRef, useEffect, useCallback } from "react"; +import React, { + useMemo, + useState, + useRef, + useEffect, + useCallback, +} from "react"; import { message, Popconfirm } from "antd"; import { SearchOutlined, @@ -57,29 +63,162 @@ const GRADIENTS = [ ]; const MOCK_PRODUCTS: ProductItem[] = [ - { id: "p-1", name: "产品介绍_智能客服系统_v2", status: "completed", duration: 95, date: "2026-06-28", gradient: GRADIENTS[0], duplicateRate: 3.2, isPublished: true, resolution: "1080×1920", fileSize: 45.6 }, - { id: "p-2", name: "品牌宣传_春季活动", status: "completed", duration: 60, date: "2026-06-27", gradient: GRADIENTS[1], duplicateRate: 5.8, isPublished: true, resolution: "1080×1920", fileSize: 32.1 }, - { id: "p-3", name: "教程_如何使用AI生成视频", status: "completed", duration: 180, date: "2026-06-26", gradient: GRADIENTS[2], duplicateRate: 1.5, isPublished: false, resolution: "1080×1920", fileSize: 78.3 }, - { id: "p-4", name: "电商推广_夏日特惠", status: "processing", duration: 45, date: "2026-06-25", gradient: GRADIENTS[3], duplicateRate: 0, isPublished: false, resolution: "1080×1920", fileSize: 0 }, - { id: "p-5", name: "知识科普_量子计算入门", status: "completed", duration: 120, date: "2026-06-24", gradient: GRADIENTS[4], duplicateRate: 8.1, isPublished: false, resolution: "1080×1920", fileSize: 56.2 }, - { id: "p-6", name: "企业宣传片_科技创新", status: "review", duration: 150, date: "2026-06-23", gradient: GRADIENTS[5], duplicateRate: 12.5, isPublished: false, resolution: "1080×1920", fileSize: 68.9 }, - { id: "p-7", name: "短视频_产品开箱测评", status: "completed", duration: 75, date: "2026-06-22", gradient: GRADIENTS[6], duplicateRate: 6.3, isPublished: true, resolution: "1080×1920", fileSize: 38.7 }, - { id: "p-8", name: "Vlog_日常工作记录", status: "completed", duration: 200, date: "2026-06-21", gradient: GRADIENTS[7], duplicateRate: 2.1, isPublished: false, resolution: "1080×1920", fileSize: 92.4 }, - { id: "p-9", name: "广告_新品发布会预告", status: "failed", duration: 30, date: "2026-06-20", gradient: GRADIENTS[8], duplicateRate: 0, isPublished: false, resolution: "1080×1920", fileSize: 0 }, - { id: "p-10", name: "教育课程_AI基础讲解", status: "completed", duration: 300, date: "2026-06-19", gradient: GRADIENTS[9], duplicateRate: 4.7, isPublished: false, resolution: "1080×1920", fileSize: 125.8 }, - { id: "p-11", name: "直播带货_美妆专场", status: "review", duration: 90, date: "2026-06-18", gradient: GRADIENTS[10], duplicateRate: 15.3, isPublished: false, resolution: "1080×1920", fileSize: 42.1 }, - { id: "p-12", name: "品牌故事_创始人访谈", status: "completed", duration: 240, date: "2026-06-17", gradient: GRADIENTS[11], duplicateRate: 7.9, isPublished: false, resolution: "1080×1920", fileSize: 108.5 }, + { + id: "p-1", + name: "产品介绍_智能客服系统_v2", + status: "completed", + duration: 95, + date: "2026-06-28", + gradient: GRADIENTS[0], + duplicateRate: 3.2, + isPublished: true, + resolution: "1080×1920", + fileSize: 45.6, + }, + { + id: "p-2", + name: "品牌宣传_春季活动", + status: "completed", + duration: 60, + date: "2026-06-27", + gradient: GRADIENTS[1], + duplicateRate: 5.8, + isPublished: true, + resolution: "1080×1920", + fileSize: 32.1, + }, + { + id: "p-3", + name: "教程_如何使用AI生成视频", + status: "completed", + duration: 180, + date: "2026-06-26", + gradient: GRADIENTS[2], + duplicateRate: 1.5, + isPublished: false, + resolution: "1080×1920", + fileSize: 78.3, + }, + { + id: "p-4", + name: "电商推广_夏日特惠", + status: "processing", + duration: 45, + date: "2026-06-25", + gradient: GRADIENTS[3], + duplicateRate: 0, + isPublished: false, + resolution: "1080×1920", + fileSize: 0, + }, + { + id: "p-5", + name: "知识科普_量子计算入门", + status: "completed", + duration: 120, + date: "2026-06-24", + gradient: GRADIENTS[4], + duplicateRate: 8.1, + isPublished: false, + resolution: "1080×1920", + fileSize: 56.2, + }, + { + id: "p-6", + name: "企业宣传片_科技创新", + status: "review", + duration: 150, + date: "2026-06-23", + gradient: GRADIENTS[5], + duplicateRate: 12.5, + isPublished: false, + resolution: "1080×1920", + fileSize: 68.9, + }, + { + id: "p-7", + name: "短视频_产品开箱测评", + status: "completed", + duration: 75, + date: "2026-06-22", + gradient: GRADIENTS[6], + duplicateRate: 6.3, + isPublished: true, + resolution: "1080×1920", + fileSize: 38.7, + }, + { + id: "p-8", + name: "Vlog_日常工作记录", + status: "completed", + duration: 200, + date: "2026-06-21", + gradient: GRADIENTS[7], + duplicateRate: 2.1, + isPublished: false, + resolution: "1080×1920", + fileSize: 92.4, + }, + { + id: "p-9", + name: "广告_新品发布会预告", + status: "failed", + duration: 30, + date: "2026-06-20", + gradient: GRADIENTS[8], + duplicateRate: 0, + isPublished: false, + resolution: "1080×1920", + fileSize: 0, + }, + { + id: "p-10", + name: "教育课程_AI基础讲解", + status: "completed", + duration: 300, + date: "2026-06-19", + gradient: GRADIENTS[9], + duplicateRate: 4.7, + isPublished: false, + resolution: "1080×1920", + fileSize: 125.8, + }, + { + id: "p-11", + name: "直播带货_美妆专场", + status: "review", + duration: 90, + date: "2026-06-18", + gradient: GRADIENTS[10], + duplicateRate: 15.3, + isPublished: false, + resolution: "1080×1920", + fileSize: 42.1, + }, + { + id: "p-12", + name: "品牌故事_创始人访谈", + status: "completed", + duration: 240, + date: "2026-06-17", + gradient: GRADIENTS[11], + duplicateRate: 7.9, + isPublished: false, + resolution: "1080×1920", + fileSize: 108.5, + }, ]; /* ============================================================ * 工具函数 * ============================================================ */ -const statusConfig: Record = { - completed: { text: "已完成", className: "completed" }, - processing: { text: "处理中", className: "processing" }, - review: { text: "待复核", className: "review" }, - failed: { text: "失败", className: "failed" }, -}; +const statusConfig: Record = + { + completed: { text: "已完成", className: "completed" }, + processing: { text: "处理中", className: "processing" }, + review: { text: "待复核", className: "review" }, + failed: { text: "失败", className: "failed" }, + }; /** 格式化时间 mm:ss */ const formatTime = (seconds: number): string => { @@ -108,11 +247,26 @@ const ProductCard: React.FC<{ onShare: (product: ProductItem) => void; onDelete: (id: string) => void; onPublish: (product: ProductItem) => void; -}> = ({ product, isSelected, batchMode, onToggleSelect, onPlay, onDownload, onShare, onDelete, onPublish }) => { +}> = ({ + product, + isSelected, + batchMode, + onToggleSelect, + onPlay, + onDownload, + onShare, + onDelete, + onPublish, +}) => { const st = statusConfig[product.status]; /** 查重率样式 */ - const dupClass = product.duplicateRate <= 5 ? "good" : product.duplicateRate <= 15 ? "warn" : "bad"; + const dupClass = + product.duplicateRate <= 5 + ? "good" + : product.duplicateRate <= 15 + ? "warn" + : "bad"; /** 点击卡片 */ const handleCardClick = () => { @@ -144,18 +298,21 @@ const ProductCard: React.FC<{
    {/* 已发布徽章(右上角) */} - {product.isPublished && ( -
    ✅ 已发布
    - )} + {product.isPublished &&
    ✅ 已发布
    } {/* 缩略图 */}
    -
    +
    {product.duration > 0 && ( - {formatTime(product.duration)} + + {formatTime(product.duration)} + )}
    @@ -194,7 +351,10 @@ const ProductCard: React.FC<{ 分享 {product.isPublished ? ( - + ✅ 已发布 ) : ( @@ -211,7 +371,10 @@ const ProductCard: React.FC<{ {/* 删除按钮(不在批量模式下显示) */} {!batchMode && ( -
    e.stopPropagation()}> +
    e.stopPropagation()} + > onDelete(product.id)} @@ -356,20 +519,28 @@ const VideoPlayer: React.FC<{
    分辨率: - {product.resolution} + + {product.resolution} +
    时长: - {formatTime(product.duration)} + + {formatTime(product.duration)} +
    文件大小: - {formatSize(product.fileSize)} + + {formatSize(product.fileSize)} +
    查重率: - {product.duplicateRate > 0 ? `${product.duplicateRate.toFixed(1)}%` : "-"} + {product.duplicateRate > 0 + ? `${product.duplicateRate.toFixed(1)}%` + : "-"}
    @@ -422,7 +593,9 @@ const ProductLibrary: React.FC = () => { const [selectedIds, setSelectedIds] = useState>(new Set()); /* 播放器 */ - const [playingProduct, setPlayingProduct] = useState(null); + const [playingProduct, setPlayingProduct] = useState( + null, + ); /* 派生数据 */ const batchMode = selectedIds.size > 0; @@ -442,10 +615,14 @@ const ProductLibrary: React.FC = () => { const d = new Date(p.date); const diff = (now.getTime() - d.getTime()) / (1000 * 60 * 60 * 24); switch (filterTime) { - case "today": return diff < 1; - case "week": return diff <= 7; - case "month": return diff <= 30; - default: return true; + case "today": + return diff < 1; + case "week": + return diff <= 7; + case "month": + return diff <= 30; + default: + return true; } }); } @@ -454,10 +631,14 @@ const ProductLibrary: React.FC = () => { if (filterDuration !== "all") { list = list.filter((p) => { switch (filterDuration) { - case "short": return p.duration > 0 && p.duration <= 60; - case "medium": return p.duration > 60 && p.duration <= 180; - case "long": return p.duration > 180; - default: return true; + case "short": + return p.duration > 0 && p.duration <= 60; + case "medium": + return p.duration > 60 && p.duration <= 180; + case "long": + return p.duration > 180; + default: + return true; } }); } @@ -472,7 +653,9 @@ const ProductLibrary: React.FC = () => { }, [products, filterStatus, filterTime, filterDuration, searchText]); /* 全选 */ - const allSelected = filteredProducts.length > 0 && filteredProducts.every((p) => selectedIds.has(p.id)); + const allSelected = + filteredProducts.length > 0 && + filteredProducts.every((p) => selectedIds.has(p.id)); const handleSelectAll = () => { if (allSelected) { @@ -507,7 +690,7 @@ const ProductLibrary: React.FC = () => { const link = `https://xiaoxia.ai/share/${product.id}`; navigator.clipboard?.writeText(link).then( () => message.success(`分享链接已复制:${link}`), - () => message.success(`分享链接:${link}(请手动复制)`) + () => message.success(`分享链接:${link}(请手动复制)`), ); }; @@ -525,7 +708,7 @@ const ProductLibrary: React.FC = () => { /* 发布 */ const handlePublish = (product: ProductItem) => { setProducts((prev) => - prev.map((p) => (p.id === product.id ? { ...p, isPublished: true } : p)) + prev.map((p) => (p.id === product.id ? { ...p, isPublished: true } : p)), ); message.success(`"${product.name}" 已发布`); }; @@ -549,7 +732,7 @@ const ProductLibrary: React.FC = () => { const handleBatchPublish = () => { const ids = Array.from(selectedIds); setProducts((prev) => - prev.map((p) => (ids.includes(p.id) ? { ...p, isPublished: true } : p)) + prev.map((p) => (ids.includes(p.id) ? { ...p, isPublished: true } : p)), ); message.success(`已批量发布 ${ids.length} 个视频`); setSelectedIds(new Set()); @@ -559,9 +742,15 @@ const ProductLibrary: React.FC = () => {
    {/* 页面头部 */}
    -

    成片库

    +

    + 成片库 +

    -
    @@ -585,10 +774,20 @@ const ProductLibrary: React.FC = () => {
    - - { okText="删除" cancelText="取消" > - -
    @@ -655,7 +862,12 @@ const ProductLibrary: React.FC = () => { />
    - + 共 {filteredProducts.length} 个成片
    @@ -685,7 +897,11 @@ const ProductLibrary: React.FC = () => {

    暂无成片,去一键生成吧

    -
    diff --git a/apps/web/src/pages/products/products.css b/apps/web/src/pages/products/products.css index 5a6a8016a..4aeb647ea 100644 --- a/apps/web/src/pages/products/products.css +++ b/apps/web/src/pages/products/products.css @@ -163,7 +163,9 @@ /* 选中状态 */ .xx-product-card.selected { border-color: var(--primary-color); - box-shadow: 0 0 0 2px var(--primary-soft), var(--shadow-sm); + box-shadow: + 0 0 0 2px var(--primary-soft), + var(--shadow-sm); } /* 已发布状态 - 降低透明度 */ @@ -447,8 +449,12 @@ } @keyframes player-fade-in { - from { opacity: 0; } - to { opacity: 1; } + from { + opacity: 0; + } + to { + opacity: 1; + } } .xx-player-container { diff --git a/apps/web/src/pages/profile/Settings.tsx b/apps/web/src/pages/profile/Settings.tsx index e65bcac61..ae4f3f56e 100644 --- a/apps/web/src/pages/profile/Settings.tsx +++ b/apps/web/src/pages/profile/Settings.tsx @@ -37,20 +37,12 @@ const Settings: React.FC = () => {
    - +
    - +
    diff --git a/apps/web/src/pages/subscription/Billing.css b/apps/web/src/pages/subscription/Billing.css index 53ad03241..4300c058e 100644 --- a/apps/web/src/pages/subscription/Billing.css +++ b/apps/web/src/pages/subscription/Billing.css @@ -159,7 +159,9 @@ } @keyframes xx-toggle-spin { - to { transform: rotate(360deg); } + to { + transform: rotate(360deg); + } } /* ── Spinner 组件 ───────────────────────────────── */ @@ -193,8 +195,16 @@ } @keyframes xx-spinner-bounce { - 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; } - 40% { transform: scale(1); opacity: 1; } + 0%, + 80%, + 100% { + transform: scale(0.6); + opacity: 0.4; + } + 40% { + transform: scale(1); + opacity: 1; + } } @media (max-width: 768px) { diff --git a/apps/web/src/pages/subscription/Billing.tsx b/apps/web/src/pages/subscription/Billing.tsx index 68bfb7b50..ae493f7a1 100644 --- a/apps/web/src/pages/subscription/Billing.tsx +++ b/apps/web/src/pages/subscription/Billing.tsx @@ -45,7 +45,9 @@ const ToggleSwitch: React.FC<{ ); /** 自定义 Spinner 组件 */ -const Spinner: React.FC<{ size?: "small" | "large" }> = ({ size = "large" }) => ( +const Spinner: React.FC<{ size?: "small" | "large" }> = ({ + size = "large", +}) => (
    diff --git a/apps/web/src/pages/subscription/Plans.css b/apps/web/src/pages/subscription/Plans.css index 21016f966..d42bacdc0 100644 --- a/apps/web/src/pages/subscription/Plans.css +++ b/apps/web/src/pages/subscription/Plans.css @@ -66,7 +66,8 @@ .xx-plan-card:hover { border-color: var(--primary-color); - box-shadow: 0 32px 90px color-mix(in srgb, var(--primary-color) 15%, transparent); + box-shadow: 0 32px 90px + color-mix(in srgb, var(--primary-color) 15%, transparent); transform: translateY(-4px); } @@ -88,7 +89,11 @@ top: -12px; left: 50%; transform: translateX(-50%); - background: linear-gradient(135deg, var(--color-primary-500), var(--primary-color)); + background: linear-gradient( + 135deg, + var(--color-primary-500), + var(--primary-color) + ); color: var(--text-inverse); padding: 6px 16px; border-radius: 999px; @@ -189,7 +194,11 @@ } .xx-subscribe-btn.primary { - background: linear-gradient(135deg, var(--color-primary-500), var(--primary-color)) !important; + background: linear-gradient( + 135deg, + var(--color-primary-500), + var(--primary-color) + ) !important; color: var(--text-inverse) !important; border: none !important; box-shadow: var(--shadow-primary) !important; @@ -213,10 +222,15 @@ /* 企业版按钮 */ .xx-subscribe-btn.enterprise { - background: linear-gradient(135deg, var(--accent-color), var(--accent-dark)) !important; + background: linear-gradient( + 135deg, + var(--accent-color), + var(--accent-dark) + ) !important; color: var(--text-inverse) !important; border: none !important; - box-shadow: 0 14px 26px color-mix(in srgb, var(--accent-color) 22%, transparent) !important; + box-shadow: 0 14px 26px + color-mix(in srgb, var(--accent-color) 22%, transparent) !important; } @media (max-width: 480px) { diff --git a/apps/web/src/pages/subscription/UpgradeSubscription.css b/apps/web/src/pages/subscription/UpgradeSubscription.css index c77ac3905..9f49458ba 100644 --- a/apps/web/src/pages/subscription/UpgradeSubscription.css +++ b/apps/web/src/pages/subscription/UpgradeSubscription.css @@ -32,7 +32,8 @@ .xx-upgrade-card:hover { border-color: var(--primary-color); - box-shadow: 0 8px 30px color-mix(in srgb, var(--primary-color) 10%, transparent); + box-shadow: 0 8px 30px + color-mix(in srgb, var(--primary-color) 10%, transparent); } .xx-upgrade-card.selected { @@ -163,8 +164,16 @@ } @keyframes xx-spinner-bounce { - 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; } - 40% { transform: scale(1); opacity: 1; } + 0%, + 80%, + 100% { + transform: scale(0.6); + opacity: 0.4; + } + 40% { + transform: scale(1); + opacity: 1; + } } @media (max-width: 480px) { diff --git a/apps/web/src/pages/subscription/UpgradeSubscription.tsx b/apps/web/src/pages/subscription/UpgradeSubscription.tsx index 588f7494c..87390d4aa 100644 --- a/apps/web/src/pages/subscription/UpgradeSubscription.tsx +++ b/apps/web/src/pages/subscription/UpgradeSubscription.tsx @@ -52,16 +52,16 @@ const BillingCycleSwitch: React.FC<{ > ¥{yearlyPrice}/年 {yearlyPrice > 0 && monthlyPrice > 0 && ( - - 省 ¥{monthlyPrice * 12 - yearlyPrice} - + 省 ¥{monthlyPrice * 12 - yearlyPrice} )}
    ); /** 自定义 Spinner 组件 */ -const Spinner: React.FC<{ size?: "small" | "large" }> = ({ size = "large" }) => ( +const Spinner: React.FC<{ size?: "small" | "large" }> = ({ + size = "large", +}) => (
    @@ -181,7 +181,10 @@ const UpgradeSubscription: React.FC = () => { return (
    - +
    {(["standard", "pro", "enterprise"] as PlanType[]).map((planId) => { diff --git a/apps/web/src/pages/templates/TemplateLibrary.tsx b/apps/web/src/pages/templates/TemplateLibrary.tsx index ab8f44f88..33387ee74 100644 --- a/apps/web/src/pages/templates/TemplateLibrary.tsx +++ b/apps/web/src/pages/templates/TemplateLibrary.tsx @@ -122,7 +122,7 @@ const TemplateLibrary: React.FC = () => { "全部" | TemplateCategory >("全部"); const [favorites, setFavorites] = useState>( - () => new Set(mockTemplates.filter((t) => t.isFavorite).map((t) => t.id)) + () => new Set(mockTemplates.filter((t) => t.isFavorite).map((t) => t.id)), ); /** 切换收藏 */ diff --git a/apps/web/src/pages/titles/TitleLibrary.tsx b/apps/web/src/pages/titles/TitleLibrary.tsx index b30964858..21988e7e2 100644 --- a/apps/web/src/pages/titles/TitleLibrary.tsx +++ b/apps/web/src/pages/titles/TitleLibrary.tsx @@ -5,11 +5,7 @@ * 使用 mock 数据,后端 API 对接暂不要求 */ import React, { useMemo, useState, useCallback } from "react"; -import { - Modal as AntModal, - message, - Popconfirm, -} from "antd"; +import { Modal as AntModal, message, Popconfirm } from "antd"; import { PlusOutlined, SearchOutlined, @@ -61,21 +57,141 @@ const MOCK_CATEGORIES: CategoryItem[] = [ ]; const MOCK_TITLES: TitleData[] = [ - { id: "t-1", content: "这家隐藏在巷子里的小店,味道绝了!", type: "hot", industry: "food", usageCount: 128, isFavorited: true, createdAt: "2026-06-28" }, - { id: "t-2", content: "2026 年最值得入手的 5 款蓝牙耳机", type: "hot", industry: "tech", usageCount: 96, isFavorited: false, createdAt: "2026-06-27" }, - { id: "t-3", content: "周末在家做了一道妈妈的味道", type: "normal", industry: "food", usageCount: 42, isFavorited: false, createdAt: "2026-06-26" }, - { id: "t-4", content: "用 AI 帮我写了一周的小红书文案,效果惊人", type: "hot", industry: "tech", usageCount: 215, isFavorited: true, createdAt: "2026-06-25" }, - { id: "t-5", content: "今天穿了一套被路人要链接的衣服", type: "creative", industry: "beauty", usageCount: 67, isFavorited: false, createdAt: "2026-06-24" }, - { id: "t-6", content: "分享我的早起 5 点俱乐部 30 天打卡体验", type: "normal", industry: "education", usageCount: 38, isFavorited: true, createdAt: "2026-06-23" }, - { id: "t-7", content: "这个平价面霜居然比大牌还好用?", type: "hot", industry: "beauty", usageCount: 183, isFavorited: false, createdAt: "2026-06-22" }, - { id: "t-8", content: "一个人的旅行也可以很精彩", type: "normal", industry: "travel", usageCount: 55, isFavorited: false, createdAt: "2026-06-21" }, - { id: "t-9", content: "考研上岸!我的备考时间管理方法全公开", type: "hot", industry: "education", usageCount: 147, isFavorited: true, createdAt: "2026-06-20" }, - { id: "t-10", content: "把旧 T 恤改造成时尚单品,零成本!", type: "creative", industry: "beauty", usageCount: 29, isFavorited: false, createdAt: "2026-06-19" }, - { id: "t-11", content: "这家咖啡馆的氛围感也太好了吧", type: "normal", industry: "food", usageCount: 74, isFavorited: false, createdAt: "2026-06-18" }, - { id: "t-12", content: "手机摄影技巧:拍出电影感画面", type: "creative", industry: "tech", usageCount: 61, isFavorited: false, createdAt: "2026-06-17" }, - { id: "t-13", content: "带娃旅行必备清单,少带一样都崩溃", type: "hot", industry: "travel", usageCount: 109, isFavorited: false, createdAt: "2026-06-16" }, - { id: "t-14", content: "30 天学会一门新语言?我的实验记录", type: "creative", industry: "education", usageCount: 33, isFavorited: false, createdAt: "2026-06-15" }, - { id: "t-15", content: "今天做了一道让全家惊艳的菜", type: "normal", industry: "food", usageCount: 48, isFavorited: false, createdAt: "2026-06-14" }, + { + id: "t-1", + content: "这家隐藏在巷子里的小店,味道绝了!", + type: "hot", + industry: "food", + usageCount: 128, + isFavorited: true, + createdAt: "2026-06-28", + }, + { + id: "t-2", + content: "2026 年最值得入手的 5 款蓝牙耳机", + type: "hot", + industry: "tech", + usageCount: 96, + isFavorited: false, + createdAt: "2026-06-27", + }, + { + id: "t-3", + content: "周末在家做了一道妈妈的味道", + type: "normal", + industry: "food", + usageCount: 42, + isFavorited: false, + createdAt: "2026-06-26", + }, + { + id: "t-4", + content: "用 AI 帮我写了一周的小红书文案,效果惊人", + type: "hot", + industry: "tech", + usageCount: 215, + isFavorited: true, + createdAt: "2026-06-25", + }, + { + id: "t-5", + content: "今天穿了一套被路人要链接的衣服", + type: "creative", + industry: "beauty", + usageCount: 67, + isFavorited: false, + createdAt: "2026-06-24", + }, + { + id: "t-6", + content: "分享我的早起 5 点俱乐部 30 天打卡体验", + type: "normal", + industry: "education", + usageCount: 38, + isFavorited: true, + createdAt: "2026-06-23", + }, + { + id: "t-7", + content: "这个平价面霜居然比大牌还好用?", + type: "hot", + industry: "beauty", + usageCount: 183, + isFavorited: false, + createdAt: "2026-06-22", + }, + { + id: "t-8", + content: "一个人的旅行也可以很精彩", + type: "normal", + industry: "travel", + usageCount: 55, + isFavorited: false, + createdAt: "2026-06-21", + }, + { + id: "t-9", + content: "考研上岸!我的备考时间管理方法全公开", + type: "hot", + industry: "education", + usageCount: 147, + isFavorited: true, + createdAt: "2026-06-20", + }, + { + id: "t-10", + content: "把旧 T 恤改造成时尚单品,零成本!", + type: "creative", + industry: "beauty", + usageCount: 29, + isFavorited: false, + createdAt: "2026-06-19", + }, + { + id: "t-11", + content: "这家咖啡馆的氛围感也太好了吧", + type: "normal", + industry: "food", + usageCount: 74, + isFavorited: false, + createdAt: "2026-06-18", + }, + { + id: "t-12", + content: "手机摄影技巧:拍出电影感画面", + type: "creative", + industry: "tech", + usageCount: 61, + isFavorited: false, + createdAt: "2026-06-17", + }, + { + id: "t-13", + content: "带娃旅行必备清单,少带一样都崩溃", + type: "hot", + industry: "travel", + usageCount: 109, + isFavorited: false, + createdAt: "2026-06-16", + }, + { + id: "t-14", + content: "30 天学会一门新语言?我的实验记录", + type: "creative", + industry: "education", + usageCount: 33, + isFavorited: false, + createdAt: "2026-06-15", + }, + { + id: "t-15", + content: "今天做了一道让全家惊艳的菜", + type: "normal", + industry: "food", + usageCount: 48, + isFavorited: false, + createdAt: "2026-06-14", + }, ]; /* ============================================================ @@ -83,9 +199,12 @@ const MOCK_TITLES: TitleData[] = [ * ============================================================ */ const typeLabel = (type: TitleType): string => { switch (type) { - case "hot": return "爆款"; - case "normal": return "常规"; - case "creative": return "创意"; + case "hot": + return "爆款"; + case "normal": + return "常规"; + case "creative": + return "创意"; } }; @@ -185,12 +304,8 @@ const TitleCard: React.FC<{ {typeLabel(title.type)} - - 使用 {title.usageCount} 次 - - - {title.createdAt} - + 使用 {title.usageCount} 次 + {title.createdAt}
    @@ -294,11 +409,11 @@ const TitleLibrary: React.FC = () => { if (activeCatId !== "cat-all") { const catName = activeCategory?.name || ""; const catToIndustry: Record = { - "美食探店": "food", - "科技数码": "tech", - "生活日常": "general", - "美妆穿搭": "beauty", - "教育学习": "education", + 美食探店: "food", + 科技数码: "tech", + 生活日常: "general", + 美妆穿搭: "beauty", + 教育学习: "education", }; const mappedIndustry = catToIndustry[catName]; if (mappedIndustry) { @@ -338,14 +453,22 @@ const TitleLibrary: React.FC = () => { } return list; - }, [titles, activeCatId, activeCategory, filterType, filterIndustry, filterFrequency, searchText]); + }, [ + titles, + activeCatId, + activeCategory, + filterType, + filterIndustry, + filterFrequency, + searchText, + ]); /* 收藏切换 */ const handleToggleFavorite = useCallback((id: string) => { setTitles((prev) => prev.map((t) => - t.id === id ? { ...t, isFavorited: !t.isFavorited } : t - ) + t.id === id ? { ...t, isFavorited: !t.isFavorited } : t, + ), ); }, []); @@ -372,8 +495,8 @@ const TitleLibrary: React.FC = () => { } setTitles((prev) => prev.map((t) => - t.id === editingId ? { ...t, content: editText.trim() } : t - ) + t.id === editingId ? { ...t, content: editText.trim() } : t, + ), ); setEditingId(null); setEditText(""); @@ -501,7 +624,15 @@ const TitleLibrary: React.FC = () => { className={`xx-title-category-item${cat.id === activeCatId ? " active" : ""}`} onClick={() => setActiveCatId(cat.id)} > -
    +

    {cat.name} @@ -592,10 +723,20 @@ const TitleLibrary: React.FC = () => { />

    - -
    @@ -625,7 +766,11 @@ const TitleLibrary: React.FC = () => {
    -

    {searchText ? "未找到匹配的标题" : "暂无标题,点击「新建标题」或「AI 生成标题」开始"}

    +

    + {searchText + ? "未找到匹配的标题" + : "暂无标题,点击「新建标题」或「AI 生成标题」开始"} +

    )}
    @@ -642,7 +787,13 @@ const TitleLibrary: React.FC = () => { destroyOnClose >
    -
    +
    分类名称
    { cancelText="取消" destroyOnClose > -
    +
    -
    +
    标题内容
    { />
    -
    +
    标题类型
    { {/* AI 生成结果列表 */} {aiResults.length > 0 && (
    -
    +
    已生成 {aiResults.length} 个候选标题,点击采纳或复制:
    {aiResults.map((text, idx) => ( diff --git a/apps/web/src/pages/titles/titles.css b/apps/web/src/pages/titles/titles.css index fccdc3353..6d7c2ccfa 100644 --- a/apps/web/src/pages/titles/titles.css +++ b/apps/web/src/pages/titles/titles.css @@ -406,12 +406,20 @@ animation: ai-dot-bounce 1.4s ease-in-out infinite both; } -.xx-ai-loading-dot:nth-child(1) { animation-delay: 0s; } -.xx-ai-loading-dot:nth-child(2) { animation-delay: 0.16s; } -.xx-ai-loading-dot:nth-child(3) { animation-delay: 0.32s; } +.xx-ai-loading-dot:nth-child(1) { + animation-delay: 0s; +} +.xx-ai-loading-dot:nth-child(2) { + animation-delay: 0.16s; +} +.xx-ai-loading-dot:nth-child(3) { + animation-delay: 0.32s; +} @keyframes ai-dot-bounce { - 0%, 80%, 100% { + 0%, + 80%, + 100% { transform: scale(0.4); opacity: 0.4; } diff --git a/apps/web/src/pages/voice-clone/VoiceClone.tsx b/apps/web/src/pages/voice-clone/VoiceClone.tsx index 7810d4719..3b5869e2f 100644 --- a/apps/web/src/pages/voice-clone/VoiceClone.tsx +++ b/apps/web/src/pages/voice-clone/VoiceClone.tsx @@ -267,9 +267,7 @@ const VoiceClone: React.FC = () => {
    🎤

    还没有克隆音色

    -

    - 上传你的声音,AI将克隆你的专属音色 -

    +

    上传你的声音,AI将克隆你的专属音色

    diff --git a/apps/web/src/pages/voice-clone/voice-clone.css b/apps/web/src/pages/voice-clone/voice-clone.css index 2dd059409..d92671c0e 100644 --- a/apps/web/src/pages/voice-clone/voice-clone.css +++ b/apps/web/src/pages/voice-clone/voice-clone.css @@ -27,12 +27,15 @@ border: 1px solid var(--line, #e2e8f0); border-radius: var(--radius-lg, 14px); padding: 20px; - transition: border-color 0.2s, box-shadow 0.2s; + transition: + border-color 0.2s, + box-shadow 0.2s; } .vc-card:hover { border-color: var(--primary-light, #6366f1); - box-shadow: 0 4px 20px color-mix(in srgb, var(--primary-color) 8%, transparent); + box-shadow: 0 4px 20px + color-mix(in srgb, var(--primary-color) 8%, transparent); } /* 右上角操作按钮 */ @@ -61,7 +64,9 @@ color: var(--muted, #64748b); font-size: 13px; cursor: pointer; - transition: background 0.15s, color 0.15s; + transition: + background 0.15s, + color 0.15s; } .vc-card-action-btn:hover { @@ -95,13 +100,22 @@ } .vc-card-avatar--processing { - background: linear-gradient(135deg, var(--text-secondary), var(--color-gray-500)); + background: linear-gradient( + 135deg, + var(--text-secondary), + var(--color-gray-500) + ); animation: vc-pulse 2s ease-in-out infinite; } @keyframes vc-pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.6; } + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.6; + } } .vc-card-info { @@ -158,8 +172,13 @@ } @keyframes vc-blink { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.3; } + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.3; + } } /* 元信息 */ @@ -246,7 +265,7 @@ font-weight: 500; background: var(--bg-card, #fff); border: 1px solid var(--line, #e2e8f0); - box-shadow: 0 4px 12px var(--shadow-sm, rgba(0,0,0,0.08)); + box-shadow: 0 4px 12px var(--shadow-sm, rgba(0, 0, 0, 0.08)); animation: vc-toast-in 0.25s ease-out; display: flex; align-items: center; @@ -282,13 +301,17 @@ z-index: 1000; display: grid; place-items: center; - background: var(--overlay-bg, rgba(0,0,0,0.4)); + background: var(--overlay-bg, rgba(0, 0, 0, 0.4)); animation: vc-fade-in 0.2s; } @keyframes vc-fade-in { - from { opacity: 0; } - to { opacity: 1; } + from { + opacity: 0; + } + to { + opacity: 1; + } } .vc-edit-dialog { @@ -297,7 +320,7 @@ padding: 24px; width: 400px; max-width: calc(100vw - 40px); - box-shadow: 0 20px 60px var(--shadow-lg, rgba(0,0,0,0.15)); + box-shadow: 0 20px 60px var(--shadow-lg, rgba(0, 0, 0, 0.15)); animation: vc-scale-in 0.2s ease-out; } diff --git a/apps/web/src/pages/voices/VoiceLibrary.tsx b/apps/web/src/pages/voices/VoiceLibrary.tsx index 04e0cc07f..35593316e 100644 --- a/apps/web/src/pages/voices/VoiceLibrary.tsx +++ b/apps/web/src/pages/voices/VoiceLibrary.tsx @@ -4,11 +4,7 @@ * 使用 mock 数据,后端 API 对接暂不要求 */ import React, { useMemo, useState, useRef, useEffect } from "react"; -import { - Modal as AntModal, - message, - Popconfirm, -} from "antd"; +import { Modal as AntModal, message, Popconfirm } from "antd"; import { PlusOutlined, SearchOutlined, @@ -56,16 +52,96 @@ const MOCK_LIBRARIES: LibraryItem[] = [ ]; const MOCK_VOICES: VoiceItem[] = [ - { id: "v-1", name: "磁性男声_深沉", voiceType: "male", language: "zh", duration: 45, tags: ["磁性", "深沉"], createdAt: "2026-06-28" }, - { id: "v-2", name: "温暖男声_治愈", voiceType: "male", language: "zh", duration: 38, tags: ["温暖", "治愈"], createdAt: "2026-06-27" }, - { id: "v-3", name: "活力女声_青春", voiceType: "female", language: "zh", duration: 52, tags: ["活力", "青春"], createdAt: "2026-06-26" }, - { id: "v-4", name: "温柔女声_优雅", voiceType: "female", language: "zh", duration: 41, tags: ["温柔", "优雅"], createdAt: "2026-06-25" }, - { id: "v-5", name: "可爱童声_活泼", voiceType: "child", language: "zh", duration: 35, tags: ["可爱", "活泼"], createdAt: "2026-06-24" }, - { id: "v-6", name: "沉稳男声_专业", voiceType: "male", language: "en", duration: 48, tags: ["沉稳", "专业"], createdAt: "2026-06-23" }, - { id: "v-7", name: "甜美女声_清新", voiceType: "female", language: "ja", duration: 42, tags: ["甜美", "清新"], createdAt: "2026-06-22" }, - { id: "v-8", name: "沧桑男声_故事感", voiceType: "elderly", language: "zh", duration: 56, tags: ["沧桑", "故事感"], createdAt: "2026-06-21" }, - { id: "v-9", name: "知性女声_商务", voiceType: "female", language: "ko", duration: 39, tags: ["知性", "商务"], createdAt: "2026-06-20" }, - { id: "v-10", name: "阳光男声_运动", voiceType: "male", language: "zh", duration: 44, tags: ["阳光", "运动"], createdAt: "2026-06-19" }, + { + id: "v-1", + name: "磁性男声_深沉", + voiceType: "male", + language: "zh", + duration: 45, + tags: ["磁性", "深沉"], + createdAt: "2026-06-28", + }, + { + id: "v-2", + name: "温暖男声_治愈", + voiceType: "male", + language: "zh", + duration: 38, + tags: ["温暖", "治愈"], + createdAt: "2026-06-27", + }, + { + id: "v-3", + name: "活力女声_青春", + voiceType: "female", + language: "zh", + duration: 52, + tags: ["活力", "青春"], + createdAt: "2026-06-26", + }, + { + id: "v-4", + name: "温柔女声_优雅", + voiceType: "female", + language: "zh", + duration: 41, + tags: ["温柔", "优雅"], + createdAt: "2026-06-25", + }, + { + id: "v-5", + name: "可爱童声_活泼", + voiceType: "child", + language: "zh", + duration: 35, + tags: ["可爱", "活泼"], + createdAt: "2026-06-24", + }, + { + id: "v-6", + name: "沉稳男声_专业", + voiceType: "male", + language: "en", + duration: 48, + tags: ["沉稳", "专业"], + createdAt: "2026-06-23", + }, + { + id: "v-7", + name: "甜美女声_清新", + voiceType: "female", + language: "ja", + duration: 42, + tags: ["甜美", "清新"], + createdAt: "2026-06-22", + }, + { + id: "v-8", + name: "沧桑男声_故事感", + voiceType: "elderly", + language: "zh", + duration: 56, + tags: ["沧桑", "故事感"], + createdAt: "2026-06-21", + }, + { + id: "v-9", + name: "知性女声_商务", + voiceType: "female", + language: "ko", + duration: 39, + tags: ["知性", "商务"], + createdAt: "2026-06-20", + }, + { + id: "v-10", + name: "阳光男声_运动", + voiceType: "male", + language: "zh", + duration: 44, + tags: ["阳光", "运动"], + createdAt: "2026-06-19", + }, ]; /* ============================================================ @@ -73,19 +149,27 @@ const MOCK_VOICES: VoiceItem[] = [ * ============================================================ */ const voiceTypeLabel = (type: VoiceType) => { switch (type) { - case "male": return "男声"; - case "female": return "女声"; - case "child": return "童声"; - case "elderly": return "老年"; + case "male": + return "男声"; + case "female": + return "女声"; + case "child": + return "童声"; + case "elderly": + return "老年"; } }; const languageLabel = (lang: Language) => { switch (lang) { - case "zh": return "中文"; - case "en": return "英文"; - case "ja": return "日文"; - case "ko": return "韩文"; + case "zh": + return "中文"; + case "en": + return "英文"; + case "ja": + return "日文"; + case "ko": + return "韩文"; } }; @@ -185,9 +269,7 @@ const VoiceCard: React.FC<{ style={{ width: `${progress}%` }} />
    - - {formatTime(currentTime)} - + {formatTime(currentTime)}
    ); @@ -249,7 +331,7 @@ const VoiceLibrary: React.FC = () => { list = list.filter( (v) => v.name.toLowerCase().includes(q) || - v.tags.some((tag) => tag.toLowerCase().includes(q)) + v.tags.some((tag) => tag.toLowerCase().includes(q)), ); } @@ -366,7 +448,13 @@ const VoiceLibrary: React.FC = () => { className={`xx-voice-library-item${lib.id === activeLibId ? " active" : ""}`} onClick={() => setActiveLibId(lib.id)} > -
    +

    {lib.name}

    @@ -444,13 +532,27 @@ const VoiceLibrary: React.FC = () => { />
    - - -
    @@ -492,9 +594,22 @@ const VoiceLibrary: React.FC = () => { cancelText="取消" destroyOnClose > -
    +
    -
    +
    名称
    { />
    -
    +
    音色类型