Files
xiaoxia-saas/docs/frontend-v21-ui-development-plan.md
灵应 bbb4878f58
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
Deploy / Deploy Staging (push) Has been cancelled
Deploy / Build Production Runtime Images (push) Has been cancelled
Deploy / Deploy Production (push) Has been cancelled
Deploy / Production Browser E2E (push) Has been cancelled
docs: 添加 V21 UI 开发文档和任务拆解文档
2026-06-30 22:17:39 +08:00

79 KiB
Raw Permalink Blame History

AIGC
AIGC
Label ContentProducer ProduceID ReservedCode1 ContentPropagator PropagateID ReservedCode2
1 001191110102MACQD9K64018705 15868733686388_0/project_7655981463858544923-files/docs/frontend-v21-ui-development-plan.md 001191110102MACQD9K64028705 15868733686388#1782829027273

V21 UI 前端开发指令文档

版本:V21 UI 1:1 还原开发指南
更新时间:2024年
技术栈:React 18 + TypeScript + Ant Design 5 + Tailwind CSS + React Query


目录

  1. 全局设计系统规范
  2. 全局布局规范
  3. 逐页开发指令
  4. 新增页面开发指令
  5. 基础组件封装规范
  6. 执行顺序建议

1. 全局设计系统规范

1.1 CSS 变量定义(global.css

从原型中提取的完整 CSS 变量系统:

/* ========== global.css ========== */

:root {
  /* ==================== 颜色系统 ==================== */
  
  /* 主色调 */
  --primary: #4f46e5;                    /* 主色 */
  --primary-dark: #4338ca;               /* 主色深 */
  --primary-soft: #eef2ff;               /* 主色浅(背景) */
  --primary-light: #818cf8;              /* 主色亮 */
  
  /* 中性色 */
  --slate: #0f172a;                      /* 标题/重要文字 */
  --muted: #64748b;                      /* 次要文字/占位符 */
  --line: #e2e8f0;                       /* 边框/分隔线 */
  --bg: #f8fafc;                         /* 页面背景 */
  
  /* 功能色 */
  --green: #10b981;                      /* 成功/通过 */
  --amber: #f59e0b;                      /* 警告/进行中 */
  --red: #ef4444;                        /* 错误/高风险 */
  
  /* ==================== 圆角系统 ==================== */
  --radius-sm: 14px;                     /* 小圆角(按钮、内边距) */
  --radius-md: 18px;                     /* 中圆角(卡片、模态框) */
  --radius-lg: 24px;                     /* 大圆角(容器) */
  --radius-xl: 28px;                     /* 超大圆角(主内容卡片) */
  
  /* ==================== 阴影系统 ==================== */
  --shadow-sm: 0 10px 30px rgba(15, 23, 42, 0.06);    /* 小阴影 */
  --shadow-md: 0 24px 70px rgba(15, 23, 42, 0.09);    /* 中阴影(卡片默认) */
  
  /* ==================== 间距系统(已对齐) ==================== */
  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 12px;
  --space-lg: 16px;
  --space-xl: 20px;
  --space-2xl: 24px;
  --space-3xl: 32px;
  
  /* ==================== 布局尺寸 ==================== */
  --sidebar-width: 240px;
  --header-height: 68px;
  --max-content-width: 1440px;
  
  /* ==================== 字体系统 ==================== */
  --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", Arial, sans-serif;
  --font-size-xs: 11px;
  --font-size-sm: 12px;
  --font-size-base: 14px;
  --font-size-md: 15px;
  --font-size-lg: 16px;
  --font-size-xl: 18px;
  --font-size-2xl: 22px;
  --font-size-3xl: 26px;
  --font-size-4xl: 28px;
  --font-size-hero: 44px;
  
  /* ==================== 动效 ==================== */
  --transition-fast: 0.15s ease;
  --transition-base: 0.18s ease;
  --transition-slow: 0.3s ease;
}

1.2 颜色使用规范

颜色名 色值 使用场景
--primary #4f46e5 主按钮、选中状态、强调文字
--primary-soft #eef2ff 选中/激活背景、标签背景
--primary-light #818cf8 图标、渐变色
--slate #0f172a 标题、主要文字
--muted #64748b 次要文字、时间戳
--line #e2e8f0 边框、分隔线
--bg #f8fafc 页面背景
--green #10b981 成功、通过、可发布
--amber #f59e0b 警告、进行中、待复核
--red #ef4444 错误、高风险、删除

1.3 标签(Tag/Pill)样式规范

/* 标签通用样式 */
.tag {
  display: inline-flex;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid #c7d2fe;
  background: #eef2ff;
  color: #4338ca;
  font-weight: 700;
  font-size: 13px;
}

/* 状态胶囊样式 */
.pill {
  font-size: 11px;
  padding: 4px 8px;
  border-radius: 999px;
  font-weight: 700;
  display: inline-block;
}

.pill.ok {              /* 成功/已通过 */
  background: #dcfce7;
  color: #166534;
}

.pill.warn {            /* 警告/待复核 */
  background: #ffedd5;
  color: #c2410c;
}

.pill.bad {             /* 错误/高风险 */
  background: #ffe4e6;
  color: #be123c;
}

.pill.info {            /* 信息/视频类型 */
  background: #dbeafe;
  color: #1d4ed8;
}

.pill.muted {           /* 禁用/品牌类 */
  background: #f1f5f9;
  color: #64748b;
}

1.4 按钮样式规范

/* 按钮基础样式 */
.btn {
  border: 0;
  border-radius: var(--radius-sm);  /* 14px */
  padding: 11px 18px;
  font-weight: 700;
  cursor: pointer;
  transition: var(--transition-base);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  font-size: var(--font-size-base);
}

/* 主要按钮(渐变) */
.btn.primary {
  background: linear-gradient(135deg, #6366f1, #4f46e5);
  color: #fff;
  box-shadow: 0 14px 26px rgba(79, 70, 229, 0.22);
}

.btn.primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 18px 34px rgba(79, 70, 229, 0.28);
}

/* 次要按钮(幽灵) */
.btn.ghost {
  background: #fff;
  border: 1px solid var(--line);
  color: #475569;
}

.btn.ghost:hover {
  border-color: #c7d2fe;
  color: #4338ca;
  background: #f8faff;
}

/* 小按钮 */
.btn.sm {
  padding: 8px 14px;
  font-size: var(--font-size-sm);
}

/* 激活态 */
.btn:active {
  transform: translateY(0);
}

/* 禁用态 */
.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

1.5 卡片样式规范

/* 通用卡片 */
.card {
  background: rgba(255, 255, 255, 0.94);
  border: 1px solid rgba(226, 232, 240, 0.95);
  border-radius: var(--radius-xl);  /* 28px */
  box-shadow: var(--shadow-md);
  padding: 28px;
}

/* 网格卡片(Hover效果) */
.card-hover {
  transition: var(--transition-base);
}

.card-hover:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-sm);
}

2. 全局布局规范

2.1 主应用布局(AppLayout

┌─────────────────────────────────────────────────────────────────┐
│  Header (height: 68px, sticky)                                  │
├──────────────┬──────────────────────────────────────────────────┤
│              │                                                   │
│   Sidebar    │              Main Content                        │
│   (240px)    │              (flex: 1)                           │
│              │                                                   │
│  - Logo      │   ┌─────────────────────────────────────────┐   │
│  - Nav       │   │  Page Head (flex space-between)         │   │
│  - Divider   │   │  - Title + Description                  │   │
│  - Bottom    │   │  - Action Buttons                       │   │
│              │   └─────────────────────────────────────────┘   │
│              │                                                   │
│              │   ┌─────────────────────────────────────────┐   │
│              │   │  Content Area                            │   │
│              │   │  (根据页面类型不同布局)                   │   │
│              │   └─────────────────────────────────────────┘   │
│              │                                                   │
└──────────────┴──────────────────────────────────────────────────┘

布局实现代码:

// components/layout/AppLayout.tsx
import React from 'react';
import { Outlet } from 'react-router-dom';
import Sidebar from './Sidebar';
import Header from './Header';

const AppLayout: React.FC = () => {
  return (
    <div style={{ minHeight: '100vh' }}>
      {/* 全局背景 */}
      <div className="bg" />
      <div className="dots" />
      
      <Header />
      
      <div className="app-layout" style={{ 
        display: 'grid', 
        gridTemplateColumns: '240px 1fr',
        gap: '24px',
        maxWidth: '1440px',
        margin: '0 auto',
        padding: '0 24px 60px'
      }}>
        <Sidebar />
        <main className="main-content">
          <Outlet />
        </main>
      </div>
    </div>
  );
};

export default AppLayout;

2.2 侧边栏导航结构(Sidebar

// components/layout/Sidebar.tsx
const navItems = [
  { icon: '📊', label: '控制台', path: '/dashboard' },
  { icon: '📝', label: '标题库', path: '/titles' },
  { icon: '📦', label: '素材库', path: '/assets' },
  { icon: '🎙️', label: '配音库', path: '/voices' },
  { icon: '🎨', label: '模板库', path: '/templates' },
];

const navItems2 = [
  { icon: '✨', label: '一键生成', path: '/generate', highlight: true },
  { icon: '🎬', label: '成片库', path: '/products' },
  { icon: '📋', label: '任务历史', path: '/history' },
  { icon: '🔍', label: '查重检测', path: '/duplication' },
  { icon: '✂️', label: '剪辑计划', path: '/editing-planner' },
  { icon: '🎤', label: '我的音色', path: '/voice-clone' },
  { icon: '🔑', label: '账号管理', path: '/accounts' },
];

const bottomItems = [
  { icon: '💎', label: '订阅管理', path: '/subscription' },
];

侧边栏样式(来自原型):

// Sidebar CSS
const sidebarStyle = {
  background: 'rgba(255, 255, 255, 0.88)',
  backdropFilter: 'blur(18px)',
  borderRight: '1px solid rgba(226, 232, 240, 0.9)',
  padding: '24px 16px',
  position: 'sticky' as const,
  top: '68px',
  height: 'calc(100vh - 68px)',
  overflowY: 'auto' as const,
};

const sidebarItemStyle = {
  display: 'flex',
  alignItems: 'center',
  gap: '12px',
  padding: '12px 14px',
  borderRadius: 'var(--radius-sm)',
  color: 'var(--muted)',
  cursor: 'pointer',
  fontWeight: 600,
  fontSize: '14px',
  transition: '0.15s ease',
};

const sidebarItemActiveStyle = {
  ...sidebarItemStyle,
  background: 'var(--primary-soft)',
  color: 'var(--primary)',
  fontWeight: 700,
};

const iconStyle = {
  width: '36px',
  height: '36px',
  borderRadius: '10px',
  display: 'grid',
  placeItems: 'center',
  fontSize: '18px',
  background: '#f1f5f9',
};

const iconActiveStyle = {
  ...iconStyle,
  background: 'var(--primary)',
  color: '#fff',
};

2.3 页面头部(PageHead

// components/common/PageHead.tsx
interface PageHeadProps {
  title: string;
  description?: string;
  tag?: string;
  actions?: React.ReactNode;
}

const PageHead: React.FC<PageHeadProps> = ({ 
  title, 
  description, 
  tag, 
  actions 
}) => {
  return (
    <div className="page-head" style={{
      display: 'flex',
      justifyContent: 'space-between',
      alignItems: 'flex-start',
      gap: '18px',
      marginBottom: '22px',
      paddingTop: '32px',
    }}>
      <div>
        {tag && <span className="tag">{tag}</span>}
        <h2 style={{
          fontSize: '26px',
          margin: tag ? '6px 0 0' : '0 0 6px',
          letterSpacing: '-0.02em',
        }}>{title}</h2>
        {description && (
          <p style={{ margin: '4px 0 0', color: 'var(--muted)', lineHeight: 1.7 }}>
            {description}
          </p>
        )}
      </div>
      {actions && <div style={{ display: 'flex', gap: '10px' }}>{actions}</div>}
    </div>
  );
};

2.4 页面容器(Wrap

// 全局页面包装器
const Wrap: React.FC<{ children: React.ReactNode }> = ({ children }) => (
  <div style={{
    maxWidth: '1440px',
    margin: '0 auto',
    padding: '0 24px 60px',
  }}>
    {children}
  </div>
);

3. 逐页开发指令

3.1 控制台 (Dashboard)

文件路径: pages/dashboard/Dashboard.tsx

路由: /dashboard

当前状态:

  • API 调用逻辑已实现
  • 统计数据获取逻辑已实现
  • ⚠️ KPI 卡片使用 Ant Design 样式
  • ⚠️ 最近任务使用 Table 布局,需改为卡片式
  • ⚠️ 缺少快速入口卡片网格(4列布局)

UI 差异清单:

差异项 当前状态 原型状态 优先级
KPI 卡片样式 Ant Design Card 渐变背景 + 大号数字 + 趋势 P2
最近任务布局 Table 卡片式列表 P2
快速入口网格 4列 feature-grid P2
页面头部欢迎语 - 带用户名的欢迎语 + 日期 P2

具体修改指令:

3.1.1 KPI 卡片网格

改什么: 将现有的 KPI 卡片改为原型设计的渐变背景卡片

改成什么样子:

┌────────────┬────────────┬────────────┬────────────┐
│    12      │    486     │    156     │   2.4GB    │
│  项目总数  │  素材总数   │  本月生成数 │  存储空间   │
│ ↑2本月新增 │ ↑38本月上传 │ ↑23% vs上月│  已用24%   │
└────────────┴────────────┴────────────┴────────────┘

使用 Ant Design/Tailwind 组件:

// 使用 Tailwind + 自定义样式
<div className="kpi-grid" style={{
  display: 'grid',
  gridTemplateColumns: 'repeat(4, 1fr)',
  gap: '16px',
  marginBottom: '24px',
}}>
  {kpiData.map((item) => (
    <div
      key={item.key}
      className="kpi"
      style={{
        background: 'linear-gradient(180deg, #fff, #f8fafc)',
        border: '1px solid var(--line)',
        borderRadius: 'var(--radius-lg)',
        padding: '20px',
      }}
    >
      <div style={{
        fontSize: '30px',
        fontWeight: 800,
        color: 'var(--slate)',
      }}>{item.value}</div>
      <div style={{
        color: 'var(--muted)',
        fontSize: '13px',
        marginTop: '4px',
      }}>{item.label}</div>
      <div style={{
        fontSize: '12px',
        marginTop: '8px',
        color: item.trend.includes('↑') ? 'var(--green)' : 'var(--muted)',
      }}>{item.trend}</div>
    </div>
  ))}
</div>

3.1.2 最近任务卡片列表

改什么: 将 Table 布局改为卡片式列表

改成什么样子:

<div className="task-list" style={{
  display: 'flex',
  flexDirection: 'column',
  gap: '10px',
}}>
  {recentTasks.map((task) => (
    <div
      key={task.id}
      className="task-item"
      style={{
        background: '#fff',
        border: '1px solid var(--line)',
        borderRadius: 'var(--radius-md)',
        padding: '16px',
        display: 'grid',
        gridTemplateColumns: '1fr auto auto auto',
        gap: '16px',
        alignItems: 'center',
      }}
    >
      {/* 任务信息 */}
      <div className="task-info">
        <h4 style={{ margin: '0 0 4px', fontSize: '14px', fontWeight: 600 }}>
          {task.name}
        </h4>
        <div style={{ fontSize: '12px', color: 'var(--muted)' }}>
          {task.type} · 模板:{task.template}
        </div>
      </div>
      
      {/* 状态标签 */}
      <StatusBadge status={task.status} />
      
      {/* 时间 */}
      <div style={{ textAlign: 'right', fontSize: '12px', color: 'var(--muted)' }}>
        <span style={{ display: 'block', marginBottom: '2px' }}>{task.date}</span>
        {task.status === 'completed' ? `耗时 ${task.duration}` : '进行中'}
      </div>
      
      {/* 操作按钮 */}
      <button className="btn ghost sm">查看</button>
    </div>
  ))}
</div>

3.1.3 快速入口卡片网格

改什么: 新增快速入口区域,使用 4 列 feature-grid

改成什么样子:

<div className="section">
  <div className="section-title">
    <h3>快速入口</h3>
  </div>
  <div className="feature-grid" style={{ margin: 0 }}>
    {quickEntries.map((entry) => (
      <div
        key={entry.id}
        className="feature"
        style={{ cursor: 'pointer' }}
        onClick={() => navigate(entry.path)}
      >
        <div
          className="icon"
          style={{
            background: entry.iconGradient,
            marginBottom: '14px',
          }}
        >
          {entry.icon}
        </div>
        <h3 style={{ margin: '0 0 8px', fontSize: '16px' }}>{entry.title}</h3>
        <p style={{ margin: 0, color: 'var(--muted)', fontSize: '13px' }}>
          {entry.description}
        </p>
      </div>
    ))}
  </div>
</div>

新增数据:

const quickEntries = [
  { id: 'titles', icon: '📝', iconGradient: 'linear-gradient(135deg, #6366f1, #4f46e5)', title: '标题库', description: '24条标题 · 5个分类', path: '/titles' },
  { id: 'assets', icon: '📦', iconGradient: 'linear-gradient(135deg, #0ea5e9, #0284c7)', title: '素材库', description: '486个素材 · 3个素材库', path: '/assets' },
  { id: 'generate', icon: '✨', iconGradient: 'linear-gradient(135deg, #10b981, #059669)', title: '一键生成', description: '开始创作新视频', path: '/generate' },
  { id: 'products', icon: '🎬', iconGradient: 'linear-gradient(135deg, #f59e0b, #d97706)', title: '成片库', description: '89个成片 · 3个待复核', path: '/products' },
];

优先级: P2


3.2 一键生成 (Generate)

文件路径: pages/generate/GeneratePage.tsx

路由: /generate

当前状态:

  • API 调用逻辑已实现
  • 缺失克隆声音展开区域
  • 缺失时间线预览区域
  • 缺失视频预览区域
  • 缺失重新生成按钮
  • ⚠️ 步骤条样式需调整
  • ⚠️ 表单卡片式选择需调整
  • ⚠️ 左右分栏布局需调整

UI 差异清单:

差异项 当前状态 原型状态 优先级
克隆声音展开区 展开/收起音频上传区 P1
时间线预览 右侧 sticky 预览 P1
视频预览区 9:16 竖版预览 P1
重新生成按钮 预览区左下角 P1
步骤条 - 居中 4 步骤 P2
选择卡片 - 选中带对勾图标 P2
左右分栏 - 1fr:320px P2

具体修改指令:

3.2.1 整体布局改造

改什么: 改为左右分栏布局(1fr : 320px)

<div className="generate-layout" style={{
  display: 'grid',
  gridTemplateColumns: '1fr 320px',
  gap: '24px',
}}>
  {/* 左侧表单区 */}
  <div className="generate-form">{/* ... */}</div>
  
  {/* 右侧预览区 */}
  <div className="generate-preview">{/* ... */}</div>
</div>

3.2.2 步骤条组件

改什么: 实现居中 4 步骤条

<div className="steps-bar" style={{
  display: 'flex',
  justifyContent: 'center',
  gap: '8px',
  marginBottom: '28px',
}}>
  {[
    { num: 1, label: '选择模板', active: true },
    { num: 2, label: '选择素材', active: false },
    { num: 3, label: '选择标题', active: false },
    { num: 4, label: '选择配音', active: false },
  ].map((step, idx) => (
    <React.Fragment key={step.num}>
      {idx > 0 && <span style={{ color: '#cbd5e1' }}></span>}
      <div
        className={`step-item ${step.active ? 'active' : ''}`}
        style={{
          display: 'flex',
          alignItems: 'center',
          gap: '8px',
          padding: '10px 16px',
          borderRadius: 'var(--radius-sm)',
          color: step.active ? 'var(--primary)' : 'var(--muted)',
          fontWeight: 600,
          fontSize: '14px',
          background: step.active ? 'var(--primary-soft)' : 'transparent',
        }}
      >
        <div style={{
          width: '26px',
          height: '26px',
          borderRadius: '50%',
          background: step.active ? 'var(--primary)' : '#f1f5f9',
          color: step.active ? '#fff' : 'inherit',
          display: 'grid',
          placeItems: 'center',
          fontSize: '12px',
          fontWeight: 700,
        }}>{step.num}</div>
        <span>{step.label}</span>
      </div>
    </React.Fragment>
  ))}
</div>

3.2.3 选择卡片组件

改什么: 模板/配音选择使用卡片式,带选中状态和勾选图标

<div className="choice-list" style={{
  display: 'grid',
  gridTemplateColumns: 'repeat(3, 1fr)',
  gap: '12px',
}}>
  {options.map((opt) => (
    <div
      key={opt.id}
      className={`choice-item ${selectedId === opt.id ? 'selected' : ''}`}
      onClick={() => setSelectedId(opt.id)}
      style={{
        background: '#fff',
        border: `2px solid ${selectedId === opt.id ? 'var(--primary)' : 'var(--line)'}`,
        borderRadius: 'var(--radius-md)',
        padding: '14px',
        cursor: 'pointer',
        position: 'relative',
        textAlign: 'center',
        transition: 'var(--transition-base)',
      }}
    >
      {/* 选中对勾 */}
      {selectedId === opt.id && (
        <div style={{
          position: 'absolute',
          right: '-6px',
          top: '-6px',
          width: '22px',
          height: '22px',
          borderRadius: '50%',
          background: 'var(--primary)',
          color: '#fff',
          display: 'grid',
          placeItems: 'center',
          fontSize: '12px',
        }}></div>
      )}
      
      {/* 缩略图 */}
      <div style={{
        height: '60px',
        borderRadius: 'var(--radius-sm)',
        display: 'grid',
        placeItems: 'center',
        color: '#fff',
        fontSize: '24px',
        marginBottom: '10px',
        background: opt.gradient,
      }}>{opt.thumb}</div>
      
      <h4 style={{ margin: '0 0 4px', fontSize: '13px', fontWeight: 600 }}>
        {opt.title}
      </h4>
      <p style={{ margin: 0, fontSize: '11px', color: 'var(--muted)' }}>
        {opt.desc}
      </p>
    </div>
  ))}
</div>

3.2.4 克隆声音展开区域

改什么: 在选择配音的第4步,点击"克隆我的声音"选项后展开音频上传区域

{/* 克隆声音选项被选中时显示 */}
{selectedVoice === 'clone' && (
  <div id="clone-voice-expand" style={{
    marginTop: '16px',
    padding: '20px',
    background: '#fff',
    border: '1px solid var(--line)',
    borderRadius: 'var(--radius-md)',
  }}>
    <h4 style={{ margin: '0 0 12px', fontSize: '14px' }}>🎤 克隆我的声音</h4>
    <p style={{ margin: '0 0 12px', fontSize: '12px', color: 'var(--muted)' }}>
      上传你的声音,AI将用你的音色生成配音
    </p>
    
    {/* 上传区域 */}
    <div style={{
      border: '2px dashed var(--line)',
      borderRadius: 'var(--radius-sm)',
      padding: '30px',
      textAlign: 'center',
      background: '#f8fafc',
      cursor: 'pointer',
    }}>
      <div style={{ fontSize: '28px', marginBottom: '8px' }}>🎵</div>
      <p style={{ margin: 0, fontSize: '13px' }}>
        拖拽或点击上传音频文件(MP3/WAV10秒以上)
      </p>
      <p style={{ margin: '8px 0 0', fontSize: '11px', color: 'var(--muted)' }}>
        支持格式:MP3WAV · 建议时长:10~3分钟
      </p>
    </div>
    
    {/* 录音按钮 */}
    <div style={{ display: 'flex', gap: '10px', marginTop: '12px' }}>
      <button className="btn ghost" style={{ flex: 1 }}>🎙️ 直接录制</button>
    </div>
    
    {/* 提示 */}
    <p style={{
      margin: '12px 0 0',
      fontSize: '11px',
      color: 'var(--muted)',
      background: '#f8fafc',
      padding: '8px 12px',
      borderRadius: '8px',
    }}>
      💡 建议上传10~3分钟的清晰语音,环境安静、语速均匀效果最佳
    </p>
    
    {/* 已克隆音色选择 */}
    <div style={{ marginTop: '16px' }}>
      <p style={{ margin: '0 0 8px', fontSize: '12px', fontWeight: 600, color: 'var(--muted)' }}>
        或选择已克隆的音色:
      </p>
      <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
        {clonedVoices.map((voice) => (
          <VoiceSelectItem key={voice.id} voice={voice} />
        ))}
      </div>
    </div>
    
    {/* 开始克隆按钮 */}
    <button className="btn primary" style={{ width: '100%', marginTop: '16px' }}>
      🎤 开始克隆
    </button>
  </div>
)}

3.2.5 右侧预览区域

改什么: 实现 sticky 定位的视频预览、时间线预览

<div className="generate-preview" style={{
  background: '#fff',
  border: '1px solid var(--line)',
  borderRadius: 'var(--radius-md)',
  padding: '20px',
  position: 'sticky',
  top: '92px',
}}>
  {/* 视频预览 */}
  <div style={{
    aspectRatio: '9/16',
    maxHeight: '400px',
    borderRadius: 'var(--radius-md)',
    background: 'linear-gradient(135deg, #111827, #312e81)',
    display: 'grid',
    placeItems: 'center',
    color: '#fff',
    fontSize: '36px',
    position: 'relative',
    overflow: 'hidden',
    marginBottom: '16px',
  }}>
    {/* 渐变叠加 */}
    <div style={{
      position: 'absolute',
      inset: 0,
      background: 'radial-gradient(circle at 72% 28%, rgba(255,255,255,0.2), transparent 40%)',
    }} />
    {/* 播放按钮 */}
    <div style={{
      position: 'relative',
      zIndex: 1,
      width: '60px',
      height: '60px',
      borderRadius: '50%',
      background: 'rgba(255,255,255,0.2)',
      display: 'grid',
      placeItems: 'center',
      backdropFilter: 'blur(8px)',
    }}></div>
  </div>
  
  {/* 标题预览 */}
  <div style={{
    background: 'rgba(255,255,255,0.95)',
    color: 'var(--slate)',
    borderRadius: '10px',
    padding: '10px',
    textAlign: 'center',
    fontWeight: 600,
    fontSize: '14px',
    marginBottom: '14px',
  }}>
    {selectedTitle}
  </div>
  
  {/* 剪辑计划标题 */}
  <div style={{ fontWeight: 600, marginBottom: '10px' }}>剪辑计划预览</div>
  
  {/* 时间线 */}
  <div className="preview-timeline" style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
    {timeline.map((item, idx) => (
      <div key={idx} style={{
        display: 'flex',
        alignItems: 'center',
        gap: '10px',
        padding: '10px',
        background: '#f8fafc',
        borderRadius: '10px',
        fontSize: '13px',
      }}>
        <div style={{
          width: '24px',
          height: '24px',
          borderRadius: '8px',
          background: 'var(--primary-soft)',
          color: 'var(--primary)',
          display: 'grid',
          placeItems: 'center',
          fontWeight: 700,
          fontSize: '11px',
        }}>{idx + 1}</div>
        <span>{item.scene}</span>
        <span style={{ color: 'var(--muted)', marginLeft: 'auto', fontSize: '12px' }}>
          {item.time}
        </span>
      </div>
    ))}
  </div>
  
  {/* 操作按钮 */}
  <div style={{ display: 'flex', gap: '10px', marginTop: '16px' }}>
    <button className="btn ghost">重新生成</button>
    <button className="btn primary" style={{ flex: 1 }}> 确认生成</button>
  </div>
</div>

优先级: P1


3.3 标题库 (Title Library)

文件路径: pages/titles/TitleLibrary.tsx

路由: /titles

当前状态:

  • CRUD + 批量导入已实现
  • 缺失左侧分类列表(需两栏布局)
  • 缺失标题卡片网格布局
  • 缺失收藏功能(★/☆切换)

UI 差异清单:

差异项 当前状态 原型状态 优先级
左侧分类列表 两栏布局左侧 P1
标题卡片网格 Table 3列卡片网格 P1
收藏功能 ★/☆切换 P1
页面头部 - 批量导入 + 新增按钮 P2

具体修改指令:

3.3.1 两栏布局

<div className="titles-layout" style={{
  display: 'grid',
  gridTemplateColumns: '220px 1fr',
  gap: '20px',
}}>
  {/* 左侧分类列表 */}
  <div className="category-list" style={{
    display: 'flex',
    flexDirection: 'column',
    gap: '8px',
  }}>
    {categories.map((cat) => (
      <div
        key={cat.id}
        className={`category-item ${selectedCategory === cat.id ? 'active' : ''}`}
        onClick={() => setSelectedCategory(cat.id)}
        style={{
          border: `1px solid ${selectedCategory === cat.id ? 'var(--primary)' : 'var(--line)'}`,
          background: selectedCategory === cat.id ? 'var(--primary-soft)' : '#fff',
          borderRadius: 'var(--radius-sm)',
          padding: '14px',
          cursor: 'pointer',
          transition: '0.15s ease',
        }}
      >
        <h4 style={{ margin: '0 0 4px', fontSize: '14px' }}>{cat.name}</h4>
        <span style={{ fontSize: '12px', color: 'var(--muted)' }}>{cat.count} </span>
      </div>
    ))}
  </div>
  
  {/* 右侧标题卡片网格 */}
  <div className="titles-grid" style={{
    display: 'grid',
    gridTemplateColumns: 'repeat(3, 1fr)',
    gap: '14px',
  }}>
    {titles.map((title) => (
      <TitleCard key={title.id} title={title} />
    ))}
  </div>
</div>

3.3.2 标题卡片组件

interface TitleCardProps {
  title: {
    id: string;
    content: string;
    category: string;
    usageCount: number;
    isFavorited: boolean;
  };
}

const TitleCard: React.FC<TitleCardProps> = ({ title }) => {
  const [isFavorited, setIsFavorited] = useState(title.isFavorited);
  
  return (
    <div
      className="title-card"
      style={{
        background: '#fff',
        border: '1px solid var(--line)',
        borderRadius: 'var(--radius-md)',
        padding: '16px',
        cursor: 'pointer',
        transition: 'var(--transition-base)',
      }}
    >
      <div style={{
        fontSize: '14px',
        fontWeight: 600,
        marginBottom: '10px',
        lineHeight: 1.5,
      }}>
        {title.content}
      </div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <div>
          <span className="pill info" style={{ marginRight: '6px' }}>
            {title.category}
          </span>
          <span style={{ fontSize: '12px', color: 'var(--muted)' }}>
            使用 {title.usageCount} 
          </span>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
          <button className="btn ghost sm">✏️ 编辑</button>
          <button className="btn ghost sm" style={{ color: 'var(--red)' }}>🗑️</button>
          <span
            onClick={(e) => {
              e.stopPropagation();
              setIsFavorited(!isFavorited);
            }}
            style={{
              color: isFavorited ? 'var(--amber)' : 'var(--muted)',
              fontSize: '16px',
              cursor: 'pointer',
            }}
          >
            {isFavorited ? '★' : '☆'}
          </span>
        </div>
      </div>
    </div>
  );
};

优先级: P1


3.4 素材库 (Asset Library)

文件路径: pages/assets/AssetLibrary.tsx

路由: /assets

当前状态:

  • 上传功能、素材库管理已实现
  • 缺失左侧素材库列表(需两栏布局)
  • ⚠️ 素材卡片样式、状态标签、诊断按钮需对齐

UI 差异清单:

差异项 当前状态 原型状态 优先级
左侧素材库列表 260px 宽列表 P1
素材卡片样式 - 9:16 竖版缩略图 P2
状态标签 - ok/warn/bad/info P2
诊断按钮 - 卡片底部 P2

具体修改指令:

3.4.1 两栏布局

<div className="assets-layout" style={{
  display: 'grid',
  gridTemplateColumns: '260px 1fr',
  gap: '20px',
}}>
  {/* 左侧素材库列表 */}
  <div className="asset-library-list">
    {assetLibraries.map((lib) => (
      <div
        key={lib.id}
        className={`asset-library-item ${selectedLib === lib.id ? 'active' : ''}`}
        style={{
          border: `1px solid ${selectedLib === lib.id ? 'var(--primary)' : 'var(--line)'}`,
          background: selectedLib === lib.id ? 'var(--primary-soft)' : '#fff',
          borderRadius: 'var(--radius-sm)',
          padding: '14px',
          cursor: 'pointer',
        }}
      >
        <h4 style={{ margin: '0 0 4px', fontSize: '14px' }}>{lib.name}</h4>
        <span style={{ fontSize: '12px', color: 'var(--muted)' }}>
          {lib.count} 个素材 · {lib.size}
        </span>
      </div>
    ))}
  </div>
  
  {/* 右侧素材网格 */}
  <div className="asset-grid" style={{
    display: 'grid',
    gridTemplateColumns: 'repeat(4, 1fr)',
    gap: '14px',
  }}>
    {assets.map((asset) => (
      <AssetCard key={asset.id} asset={asset} />
    ))}
  </div>
</div>

3.4.2 素材卡片组件

const AssetCard: React.FC<{ asset: Asset }> = ({ asset }) => (
  <div
    className="asset-card"
    style={{
      border: '1px solid var(--line)',
      background: '#fff',
      borderRadius: 'var(--radius-md)',
      overflow: 'hidden',
      cursor: 'pointer',
      transition: 'var(--transition-base)',
    }}
  >
    {/* 缩略图 */}
    <div
      className="asset-thumb"
      style={{
        aspectRatio: '9/16',
        borderRadius: 'var(--radius-sm) var(--radius-sm) 0 0',
        position: 'relative',
        overflow: 'hidden',
        display: 'grid',
        placeItems: 'center',
        color: '#fff',
        background: asset.gradient,
      }}
    >
      <div
        className="play"
        style={{
          position: 'absolute',
          width: '40px',
          height: '40px',
          borderRadius: '50%',
          background: 'rgba(255,255,255,0.25)',
          display: 'grid',
          placeItems: 'center',
          backdropFilter: 'blur(4px)',
        }}
      >
        
      </div>
    </div>
    
    {/* 信息 */}
    <div style={{ padding: '12px' }}>
      <h4 style={{
        margin: '0 0 6px',
        fontSize: '13px',
        fontWeight: 600,
        whiteSpace: 'nowrap',
        overflow: 'hidden',
        textOverflow: 'ellipsis',
      }}>
        {asset.name}
      </h4>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: '11px', color: 'var(--muted)' }}>
        <StatusPill status={asset.status} />
        <span>{asset.duration}</span>
      </div>
      <button
        className="btn ghost sm"
        style={{ width: '100%', marginTop: '8px', padding: '6px', fontSize: '12px' }}
      >
        🔍 诊断
      </button>
    </div>
  </div>
);

优先级: P1


3.5 配音库 (Voice Library)

文件路径: pages/voices/VoiceLibrary.tsx

路由: /voices

当前状态:

  • AI生成、CRUD 功能已实现
  • 缺失左侧配音库分类列表
  • 缺失配音卡片布局(voice-card + voice-wave 波形)

UI 差异清单:

差异项 当前状态 原型状态 优先级
左侧分类列表 220px 宽 P1
配音卡片 表格 voice-card 布局 P1
波形可视化 灰色波形条 P1
页面头部 - 新建配音库+AI生成+上传 P2

具体修改指令:

3.5.1 配音库两栏布局

<div className="voices-layout" style={{
  display: 'grid',
  gridTemplateColumns: '220px 1fr',
  gap: '20px',
}}>
  {/* 左侧配音库分类 */}
  <div className="voice-library-list">
    {voiceLibraries.map((lib) => (
      <div
        key={lib.id}
        className={`asset-library-item ${selectedLib === lib.id ? 'active' : ''}`}
        style={{ /* 同素材库样式 */ }}
      >
        <h4>{lib.name}</h4>
        <span>{lib.count} 条配音</span>
      </div>
    ))}
  </div>
  
  {/* 右侧配音卡片列表 */}
  <div className="voice-list" style={{
    display: 'flex',
    flexDirection: 'column',
    gap: '10px',
  }}>
    {voices.map((voice) => (
      <VoiceCard key={voice.id} voice={voice} />
    ))}
  </div>
</div>

3.5.2 配音卡片组件

const VoiceCard: React.FC<{ voice: Voice }> = ({ voice }) => (
  <div
    className="voice-card"
    style={{
      background: '#fff',
      border: '1px solid var(--line)',
      borderRadius: 'var(--radius-md)',
      padding: '16px',
      display: 'grid',
      gridTemplateColumns: '50px 1fr auto',
      gap: '14px',
      alignItems: 'center',
      transition: 'var(--transition-base)',
    }}
  >
    {/* 头像 */}
    <div
      style={{
        width: '50px',
        height: '50px',
        borderRadius: '50%',
        display: 'grid',
        placeItems: 'center',
        color: '#fff',
        fontSize: '22px',
        background: voice.gradient,
      }}
    >
      🎙️
    </div>
    
    {/* 信息 */}
    <div>
      <h4 style={{ margin: '0 0 4px', fontSize: '14px', fontWeight: 600 }}>
        {voice.name}
      </h4>
      <div style={{ fontSize: '12px', color: 'var(--muted)' }}>
        {voice.library} · {voice.duration} · {voice.gender}
      </div>
    </div>
    
    {/* 波形 */}
    <div
      style={{
        height: '24px',
        width: '120px',
        borderRadius: '999px',
        background: 'repeating-linear-gradient(90deg, #c7d2fe 0 5px, transparent 5px 10px)',
      }}
    />
    
    {/* 操作 */}
    <div style={{ display: 'flex', gap: '8px' }}>
      <button className="btn ghost sm"> 试听</button>
    </div>
  </div>
);

优先级: P1


3.6 成片库 (Product Library)

文件路径: pages/products/ProductLibrary.tsx

路由: /products

当前状态:

  • 下载/预览/删除/查重率展示已实现
  • 缺失批量选择功能
  • 缺失批量发布/批量下载按钮
  • 缺失已发布状态角标

UI 差异清单:

差异项 当前状态 原型状态 优先级
批量选择 复选框 + 全选 P1
批量操作按钮 发布 + 下载 P1
已发布角标 右上角绿色角标 P1
选中效果 边框高亮 + 阴影 P1

具体修改指令:

3.6.1 页面头部批量操作

<div className="page-head">
  <div>
    <h2>成片库</h2>
    <p>查看和管理生成的视频成品</p>
  </div>
  <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
    {/* 全选复选框 */}
    <label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer', fontSize: '14px', fontWeight: 600, color: 'var(--muted)' }}>
      <input
        type="checkbox"
        checked={selectedAll}
        onChange={handleSelectAll}
        style={{ width: '16px', height: '16px', accentColor: 'var(--primary)' }}
      />
      全选
    </label>
    
    {/* 已选计数 */}
    <span style={{ fontSize: '13px', color: 'var(--muted)' }}>
      已选 <span style={{ color: 'var(--primary)', fontWeight: 700 }}>{selectedCount}</span> 个视频
    </span>
    
    {/* 批量发布按钮 */}
    <button
      className="btn primary"
      disabled={selectedCount === 0}
      style={{
        opacity: selectedCount === 0 ? 0.5 : 1,
        cursor: selectedCount === 0 ? 'not-allowed' : 'pointer',
      }}
    >
      📢 批量发布
    </button>
    
    {/* 批量下载 */}
    <button className="btn ghost" disabled={selectedCount === 0}>
      批量下载
    </button>
  </div>
</div>

3.6.2 成片卡片(含批量选择)

const ProductCard: React.FC<{ product: Product }> = ({ product }) => {
  const [isSelected, setIsSelected] = useState(false);
  
  return (
    <div
      className={`product-card ${isSelected ? 'selected' : ''}`}
      style={{
        background: '#fff',
        border: `1px solid ${isSelected ? 'var(--primary)' : 'var(--line)'}`,
        borderRadius: 'var(--radius-md)',
        overflow: 'hidden',
        cursor: 'pointer',
        transition: 'var(--transition-base)',
        boxShadow: isSelected ? '0 0 0 2px var(--primary-soft)' : 'none',
        opacity: product.isPublished ? 0.65 : 1,
      }}
    >
      {/* 选择复选框 */}
      <div style={{ position: 'absolute', top: '12px', left: '12px', zIndex: 10 }}>
        <input
          type="checkbox"
          checked={isSelected}
          disabled={product.isPublished}
          onChange={() => setIsSelected(!isSelected)}
          style={{
            width: '18px',
            height: '18px',
            accentColor: 'var(--primary)',
            opacity: product.isPublished ? 0.4 : 1,
          }}
        />
      </div>
      
      {/* 已发布角标 */}
      {product.isPublished && (
        <div style={{
          position: 'absolute',
          top: '12px',
          right: '12px',
          background: 'var(--green)',
          color: '#fff',
          fontSize: '11px',
          fontWeight: 700,
          padding: '3px 8px',
          borderRadius: '6px',
          zIndex: 10,
        }}>
          已发布
        </div>
      )}
      
      {/* 缩略图 */}
      <div
        className="product-thumb"
        style={{
          aspectRatio: '9/16',
          position: 'relative',
          overflow: 'hidden',
          display: 'grid',
          placeItems: 'center',
          color: '#fff',
          background: product.gradient,
        }}
      >
        <div className="play"></div>
        <span className="duration">{product.duration}</span>
      </div>
      
      {/* 信息 */}
      <div style={{ padding: '14px' }}>
        <h4 style={{ margin: '0 0 8px', fontSize: '14px', fontWeight: 600 }}>
          {product.name}
        </h4>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <StatusPill status={product.status} />
          <span style={{ fontSize: '12px', color: 'var(--muted)' }}>{product.date}</span>
        </div>
        <div style={{ display: 'flex', gap: '6px', marginTop: '8px' }}>
          <button className="btn ghost sm" style={{ flex: 1, padding: '6px 4px', fontSize: '12px' }}>
            📥 下载
          </button>
          {product.isPublished ? (
            <span className="pill ok" style={{ flex: 1, padding: '6px 4px', fontSize: '12px', textAlign: 'center' }}>
               已发布
            </span>
          ) : (
            <button className="btn primary sm" style={{ flex: 1, padding: '6px 4px', fontSize: '12px' }}>
              📢 发布
            </button>
          )}
        </div>
      </div>
    </div>
  );
};

优先级: P1


3.7 任务历史 (Task History)

文件路径: pages/history/TaskHistory.tsx

路由: /history

当前状态:

  • 状态筛选逻辑、重试功能已实现
  • ⚠️ 状态筛选 Tab 样式需调整
  • ⚠️ 任务列表需改为卡片布局

UI 差异清单:

差异项 当前状态 原型状态 优先级
状态 Tab - 圆角胶囊样式 P2
任务列表 Table 卡片式布局 P2

具体修改指令:

3.7.1 Tab 切换样式

<div className="tabs" style={{
  display: 'flex',
  gap: '6px',
  marginBottom: '20px',
  borderBottom: '1px solid var(--line)',
  paddingBottom: '12px',
}}>
  {[
    { key: 'all', label: '全部', count: 156 },
    { key: 'running', label: '进行中', count: 3 },
    { key: 'done', label: '已完成', count: 150 },
    { key: 'failed', label: '失败', count: 3 },
  ].map((tab) => (
    <div
      key={tab.key}
      className={`tab ${activeTab === tab.key ? 'active' : ''}`}
      onClick={() => setActiveTab(tab.key)}
      style={{
        padding: '8px 16px',
        borderRadius: 'var(--radius-sm)',
        cursor: 'pointer',
        fontWeight: 600,
        fontSize: '14px',
        color: activeTab === tab.key ? 'var(--primary)' : 'var(--muted)',
        background: activeTab === tab.key ? 'var(--primary-soft)' : 'transparent',
        transition: '0.15s ease',
      }}
    >
      {tab.label} ({tab.count})
    </div>
  ))}
</div>

3.7.2 任务卡片列表

<div className="task-list" style={{
  display: 'flex',
  flexDirection: 'column',
  gap: '10px',
}}>
  {tasks.map((task) => (
    <div
      key={task.id}
      className="task-item"
      style={{
        background: '#fff',
        border: '1px solid var(--line)',
        borderRadius: 'var(--radius-md)',
        padding: '16px',
        display: 'grid',
        gridTemplateColumns: '1fr auto auto auto',
        gap: '16px',
        alignItems: 'center',
        transition: '0.15s ease',
      }}
    >
      {/* 任务信息 */}
      <div>
        <h4 style={{ margin: '0 0 4px', fontSize: '14px', fontWeight: 600 }}>
          {task.name}
        </h4>
        <div style={{ fontSize: '12px', color: 'var(--muted)' }}>
          {task.type} · 模板:{task.template}
        </div>
      </div>
      
      {/* 状态 */}
      <StatusPill status={task.status} />
      
      {/* 时间 */}
      <div style={{ textAlign: 'right', fontSize: '12px', color: 'var(--muted)' }}>
        <span style={{ display: 'block', marginBottom: '2px' }}>{task.date}</span>
        {task.status === 'failed' ? '失败' : task.status === 'running' ? '进行中' : `耗时 ${task.duration}`}
      </div>
      
      {/* 操作 */}
      <button className="btn ghost sm">
        {task.status === 'failed' ? '重试' : '查看'}
      </button>
    </div>
  ))}
</div>

优先级: P2


3.8 模板库 (Template Library)

文件路径: pages/templates/TemplateLibrary.tsx

路由: /templates

当前状态:

  • 搜索/筛选/收藏逻辑已实现
  • ⚠️ 分类筛选改为按钮组
  • ⚠️ 卡片 4 列布局
  • ⚠️ 创建模板按钮位置

UI 差异清单:

差异项 当前状态 原型状态 优先级
分类筛选 下拉框 胶囊按钮组 P2
模板卡片 - 4列网格 P2
卡片样式 - 16:9 缩略图 P2
页面头部 - 标题+创建模板按钮 P2

具体修改指令:

3.8.1 工具栏(搜索 + 分类按钮组)

<div className="template-toolbar" style={{
  display: 'flex',
  justifyContent: 'space-between',
  alignItems: 'center',
  gap: '16px',
  marginBottom: '20px',
}}>
  {/* 搜索框 */}
  <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '10px',
    background: '#fff',
    border: '1px solid var(--line)',
    borderRadius: 'var(--radius-sm)',
    padding: '0 14px',
    flex: 1,
    maxWidth: '400px',
  }}>
    <span>🔍</span>
    <input
      type="text"
      placeholder="搜索模板..."
      style={{
        border: 0,
        outline: 0,
        padding: '12px 0',
        fontSize: '14px',
        width: '100%',
      }}
    />
  </div>
  
  {/* 分类按钮组 */}
  <div style={{ display: 'flex', gap: '8px' }}>
    {['全部', '口播', '种草', '产品', '品牌'].map((cat) => (
      <button
        key={cat}
        className={`btn ghost sm ${activeCategory === cat ? 'active' : ''}`}
        style={{
          background: activeCategory === cat ? 'var(--primary-soft)' : '#fff',
          color: activeCategory === cat ? 'var(--primary)' : 'var(--muted)',
          border: activeCategory === cat ? '1px solid var(--primary)' : '1px solid var(--line)',
        }}
      >
        {cat}
      </button>
    ))}
  </div>
</div>

3.8.2 模板卡片

<div className="template-grid" style={{
  display: 'grid',
  gridTemplateColumns: 'repeat(4, 1fr)',
  gap: '16px',
}}>
  {templates.map((tmpl) => (
    <div
      key={tmpl.id}
      className="template-card"
      style={{
        background: '#fff',
        border: '1px solid var(--line)',
        borderRadius: 'var(--radius-md)',
        overflow: 'hidden',
        cursor: 'pointer',
        transition: 'var(--transition-base)',
      }}
    >
      {/* 缩略图 */}
      <div
        style={{
          aspectRatio: '16/9',
          display: 'grid',
          placeItems: 'center',
          color: '#fff',
          fontSize: '28px',
          fontWeight: 700,
          position: 'relative',
          background: tmpl.gradient,
        }}
      >
        <span style={{ position: 'relative', zIndex: 1 }}>{tmpl.code}</span>
        <div style={{
          position: 'absolute',
          inset: 0,
          background: 'linear-gradient(0deg, rgba(0,0,0,0.3), transparent)',
        }} />
      </div>
      
      {/* 信息 */}
      <div style={{ padding: '14px' }}>
        <h4 style={{ margin: '0 0 6px', fontSize: '14px', fontWeight: 600 }}>
          {tmpl.name}
        </h4>
        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '12px', color: 'var(--muted)' }}>
          <CategoryPill category={tmpl.category} />
          <span>使用 {tmpl.usageCount} </span>
        </div>
        <button
          className="btn primary sm"
          style={{ width: '100%', marginTop: '10px' }}
        >
          使用此模板
        </button>
      </div>
    </div>
  ))}
</div>

优先级: P2


3.9 订阅管理 (Subscription)

文件路径: pages/subscription/SubscriptionPage.tsx

路由: /subscription

当前状态:

  • 订阅逻辑已实现
  • ⚠️ 定价卡片样式、特性列表、价格展示需对齐
  • ⚠️ 当前套餐信息卡片样式
  • 账单记录表格

UI 差异清单:

差异项 当前状态 原型状态 优先级
定价卡片样式 - 原型卡片样式 P2
特性列表 - 带勾选列表 P2
当前套餐卡片 - 标签+按钮组 P2
账单记录表格 完整表格 P1

具体修改指令:

3.9.1 账单记录表格

<div className="section">
  <div className="section-title">
    <h3>账单记录</h3>
  </div>
  <div style={{
    background: '#fff',
    border: '1px solid var(--line)',
    borderRadius: 'var(--radius-lg)',
    overflow: 'hidden',
  }}>
    <table style={{ width: '100%', borderCollapse: 'collapse' }}>
      <thead>
        <tr style={{ background: '#f8fafc' }}>
          {['订单号', '套餐', '金额', '支付方式', '时间', '状态'].map((h) => (
            <th key={h} style={{
              padding: '14px 16px',
              textAlign: 'left',
              fontSize: '13px',
              color: 'var(--muted)',
            }}>{h}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {bills.map((bill) => (
          <tr key={bill.id} style={{ borderTop: '1px solid var(--line)' }}>
            <td style={{ padding: '14px 16px', fontSize: '13px', color: 'var(--muted)' }}>
              {bill.orderNo}
            </td>
            <td style={{ padding: '14px 16px', fontSize: '13px' }}>
              {bill.plan}
            </td>
            <td style={{ padding: '14px 16px', fontSize: '13px', fontWeight: 600 }}>
              ¥{bill.amount.toLocaleString()}
            </td>
            <td style={{ padding: '14px 16px', fontSize: '13px' }}>
              {bill.paymentMethod}
            </td>
            <td style={{ padding: '14px 16px', fontSize: '13px', color: 'var(--muted)' }}>
              {bill.date}
            </td>
            <td style={{ padding: '14px 16px' }}>
              <StatusPill status="paid" />
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  </div>
</div>

优先级: P1(账单表格)、P2(其他样式)


3.10 剪辑计划编辑器 (Editing Planner)

文件路径: pages/editing-planner/EditingPlanner.tsx

路由: /editing-planner

当前状态:

  • 核心功能已实现
  • ⚠️ 模式切换按钮样式
  • ⚠️ 面板样式对齐

优先级: P2


4. 新增页面开发指令

4.1 查重检测页面组

目录: pages/duplication/

涉及文件:

  • DuplicationUpload.tsx - 上传页面
  • DuplicationResults.tsx - 结果页面
  • DuplicationDetail.tsx - 详情页面

路由配置:

{
  path: '/duplication',
  element: <AppLayout />,
  children: [
    { path: '', element: <DuplicationUpload /> },
    { path: 'results/:id', element: <DuplicationResults /> },
    { path: 'detail/:id', element: <DuplicationDetail /> },
  ]
}

4.1.1 DuplicationUpload 页面规格

页面布局: 左右分栏(1fr : 1fr

// pages/duplication/DuplicationUpload.tsx
const DuplicationUpload: React.FC = () => {
  return (
    <PageHead
      title="视频查重检测"
      description="上传视频文件,检测与现有视频的相似度"
    />
    
    <div className="duplication-layout" style={{
      display: 'grid',
      gridTemplateColumns: '1fr 1fr',
      gap: '24px',
    }}>
      {/* 左侧上传区 */}
      <div>
        <div
          className="upload-zone"
          style={{
            border: '2px dashed var(--line)',
            borderRadius: 'var(--radius-lg)',
            padding: '60px',
            textAlign: 'center',
            background: 'linear-gradient(180deg, #fff, #f8fafc)',
            transition: 'var(--transition-base)',
            cursor: 'pointer',
          }}
        >
          <div
            className="icon"
            style={{
              width: '64px',
              height: '64px',
              borderRadius: '20px',
              background: 'var(--primary-soft)',
              margin: '0 auto 16px',
              display: 'grid',
              placeItems: 'center',
              fontSize: '28px',
              color: 'var(--primary)',
            }}
          >
            📤
          </div>
          <h3 style={{ margin: '0 0 8px', fontSize: '18px' }}>
            拖拽上传视频文件
          </h3>
          <p style={{ margin: 0, color: 'var(--muted)', fontSize: '13px' }}>
            或点击选择文件上传
          </p>
          <div style={{ marginTop: '12px', fontSize: '12px', color: 'var(--muted)' }}>
            支持格式:MP4 / AVI / MOV / MKV,单文件  2GB
          </div>
        </div>
        
        <div style={{ marginTop: '16px', display: 'flex', gap: '10px' }}>
          <button className="btn primary" style={{ flex: 1 }}>
            📤 选择文件
          </button>
          <button className="btn ghost">
            📷 摄像头录制
          </button>
        </div>
      </div>
      
      {/* 右侧结果区 */}
      <ResultZone />
    </div>
  );
};

4.1.2 DuplicationResults 页面规格

// pages/duplication/DuplicationResults.tsx
const DuplicationResults: React.FC = () => {
  const { id } = useParams();
  
  return (
    <PageHead
      title="查重结果"
      description={`检测 ID: ${id}`}
      actions={
        <button className="btn ghost">📥 下载报告</button>
      }
    />
    
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '24px' }}>
      {/* 相似度圆环 */}
      <div className="card">
        <div className="result-score" style={{ textAlign: 'center', padding: '30px' }}>
          <div
            className="result-circle"
            style={{
              width: '140px',
              height: '140px',
              borderRadius: '50%',
              margin: '0 auto 16px',
              display: 'grid',
              placeItems: 'center',
              flexDirection: 'column',
              background: 'linear-gradient(135deg, #fef3c7, #fde68a)',
              color: '#92400e',
            }}
          >
            <div style={{ fontSize: '36px', fontWeight: 800 }}>12%</div>
            <div style={{ fontSize: '14px', opacity: 0.8 }}>相似度</div>
          </div>
          <p style={{ color: 'var(--muted)', fontSize: '14px', marginTop: '12px' }}>
            该视频相似度较低,原创性良好
          </p>
        </div>
      </div>
      
      {/* 匹配列表 */}
      <div className="card">
        <h3 style={{ margin: '0 0 16px' }}>匹配的视频片段</h3>
        <div className="result-list">
          {matches.map((match) => (
            <div
              key={match.id}
              style={{
                display: 'flex',
                alignItems: 'center',
                gap: '14px',
                padding: '14px',
                background: '#f8fafc',
                borderRadius: 'var(--radius-sm)',
                marginBottom: '10px',
              }}
            >
              <div
                style={{
                  width: '60px',
                  height: '80px',
                  borderRadius: '8px',
                  background: 'linear-gradient(135deg, #64748b, #334155)',
                  display: 'grid',
                  placeItems: 'center',
                  color: '#fff',
                }}
              >
                
              </div>
              <div style={{ flex: 1 }}>
                <h4 style={{ margin: '0 0 4px', fontSize: '14px', fontWeight: 600 }}>
                  {match.videoName}
                </h4>
                <p style={{ margin: 0, fontSize: '12px', color: 'var(--muted)' }}>
                  相似片段:{match.segment}
                </p>
              </div>
              <div style={{ fontWeight: 700, color: 'var(--amber)' }}>
                {match.similarity}%
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

优先级: P1


4.2 我的音色 (Voice Clone)

目录: pages/voice-clone/

文件: VoiceClone.tsx

路由: /voice-clone

4.2.1 页面规格

// pages/voice-clone/VoiceClone.tsx
const VoiceClone: React.FC = () => {
  return (
    <PageHead
      title="🎤 我的音色库"
      description="克隆和管理你的专属音色,用AI生成个性化配音"
      actions={
        <button className="btn primary" onClick={openCloneModal}>
           克隆新音色
        </button>
      }
    />
    
    {/* 音色卡片网格 */}
    <div style={{
      display: 'grid',
      gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',
      gap: '16px',
      marginTop: '20px',
    }}>
      {clonedVoices.map((voice) => (
        <VoiceCloneCard key={voice.id} voice={voice} />
      ))}
    </div>
    
    {/* 空状态 */}
    {clonedVoices.length === 0 && (
      <div style={{
        marginTop: '40px',
        textAlign: 'center',
        padding: '48px',
      }}>
        <div style={{ fontSize: '64px', marginBottom: '16px' }}>🎤</div>
        <h3 style={{ margin: '0 0 8px', fontSize: '18px' }}>
          还没有克隆音色
        </h3>
        <p style={{ margin: '0 0 20px', color: 'var(--muted)' }}>
          上传你的声音,AI将克隆你的专属音色
        </p>
        <button className="btn primary" onClick={openCloneModal}>
           立即克隆
        </button>
      </div>
    )}
  );
};

4.2.2 音色克隆卡片组件

interface VoiceCloneCardProps {
  voice: {
    id: string;
    name: string;
    duration: string;
    createdAt: string;
    status: 'ready' | 'processing';
  };
}

const VoiceCloneCard: React.FC<VoiceCloneCardProps> = ({ voice }) => (
  <div
    style={{
      background: '#fff',
      border: '1px solid var(--line)',
      borderRadius: 'var(--radius-lg)',
      padding: '20px',
      position: 'relative',
    }}
  >
    {/* 操作按钮 */}
    <div style={{ position: 'absolute', top: '16px', right: '16px', display: 'flex', gap: '4px' }}>
      <button className="btn ghost sm" style={{ padding: '4px 8px', fontSize: '11px' }}>🗑️</button>
      <button className="btn ghost sm" style={{ padding: '4px 8px', fontSize: '11px' }}>✏️</button>
    </div>
    
    {/* 头部信息 */}
    <div style={{ display: 'flex', alignItems: 'center', gap: '14px', marginBottom: '14px' }}>
      <div
        style={{
          width: '56px',
          height: '56px',
          borderRadius: '50%',
          background: 'linear-gradient(135deg, #f59e0b, #d97706)',
          display: 'grid',
          placeItems: 'center',
          color: '#fff',
          fontSize: '24px',
        }}
      >
        🎤
      </div>
      <div>
        <h4 style={{ margin: '0 0 4px', fontSize: '15px', fontWeight: 600 }}>
          {voice.name}
        </h4>
        <StatusPill status={voice.status === 'ready' ? 'ok' : 'warn'} />
      </div>
    </div>
    
    {/* 元信息 */}
    <div style={{ fontSize: '13px', color: 'var(--muted)', marginBottom: '12px' }}>
      <div style={{ marginBottom: '4px' }}>🎵 时长:{voice.duration}</div>
      <div>📅 创建于:{voice.createdAt}</div>
    </div>
    
    {/* 操作区 */}
    <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: '10px',
      padding: '10px',
      background: '#f8fafc',
      borderRadius: '10px',
    }}>
      <button className="btn ghost sm" style={{ flex: 1 }}> 试听</button>
      <button className="btn primary sm" style={{ flex: 1 }}> 使用此音色</button>
    </div>
  </div>
);

4.2.3 克隆音色 Modal

// components/modals/CloneVoiceModal.tsx
const CloneVoiceModal: React.FC<{ visible: boolean; onClose: () => void }> = ({
  visible,
  onClose,
}) => {
  const [step, setStep] = useState<'input' | 'uploading' | 'success'>('input');
  
  return (
    <Modal
      visible={visible}
      onClose={onClose}
      title="🎤 克隆新音色"
      width={520}
    >
      {/* 音色名称 */}
      <Form.Item label="音色名称">
        <Input placeholder="输入音色名称" defaultValue="我的声音 1" />
      </Form.Item>
      
      {/* 上传区域 */}
      <Form.Item label="上传音频">
        <div
          style={{
            border: '2px dashed var(--line)',
            borderRadius: 'var(--radius-md)',
            padding: '30px',
            textAlign: 'center',
            background: '#f8fafc',
            cursor: 'pointer',
          }}
        >
          <div style={{ fontSize: '36px', marginBottom: '10px' }}>🎵</div>
          <p style={{ fontWeight: 600 }}>拖拽音频文件到此处</p>
          <p style={{ color: 'var(--muted)', fontSize: '13px' }}>支持 MP3WAV 格式</p>
        </div>
      </Form.Item>
      
      {/* 或分隔 */}
      <div style={{ display: 'flex', alignItems: 'center', gap: '16px', margin: '20px 0' }}>
        <div style={{ flex: 1, height: '1px', background: 'var(--line)' }} />
        <span style={{ fontSize: '13px', color: 'var(--muted)' }}></span>
        <div style={{ flex: 1, height: '1px', background: 'var(--line)' }} />
      </div>
      
      {/* 录音 */}
      <Form.Item label="直接录制">
        <div style={{ border: '1px solid var(--line)', borderRadius: 'var(--radius-md)', padding: '24px', textAlign: 'center' }}>
          <p style={{ color: 'var(--muted)', marginBottom: '12px' }}>点击按钮开始录制你的声音</p>
          <button
            className="btn primary"
            style={{
              width: '80px',
              height: '80px',
              borderRadius: '50%',
              fontSize: '32px',
              padding: 0,
              background: 'linear-gradient(135deg, #ef4444, #dc2626)',
            }}
          >
            🎙️
          </button>
        </div>
      </Form.Item>
      
      {/* 提示 */}
      <div style={{
        padding: '12px 16px',
        background: '#fef3c7',
        borderRadius: 'var(--radius-sm)',
        marginBottom: '20px',
        fontSize: '13px',
        color: '#92400e',
      }}>
        💡 建议上传10~3分钟的清晰语音,环境安静、语速均匀效果最佳
      </div>
      
      {/* 按钮 */}
      <div style={{ display: 'flex', gap: '12px' }}>
        <button className="btn ghost" onClick={onClose} style={{ flex: 1 }}>
          取消
        </button>
        <button className="btn primary" style={{ flex: 1 }}>
          🎤 开始克隆
        </button>
      </div>
    </Modal>
  );
};

优先级: P1


4.3 账号管理 (Accounts)

目录: pages/accounts/

文件: Accounts.tsx

路由: /accounts

4.3.1 页面规格

// pages/accounts/Accounts.tsx
const Accounts: React.FC = () => {
  const platforms = [
    { id: 'douyin', name: '抖音', icon: '📱', gradient: 'linear-gradient(135deg, #fe2c55, #25f4ee)' },
    { id: 'kuaishou', name: '快手', icon: '🎬', gradient: 'linear-gradient(135deg, #ff4906, #ffba00)' },
    { id: 'xiaohongshu', name: '小红书', icon: '📕', gradient: 'linear-gradient(135deg, #ff2442, #ff6b6b)' },
    { id: 'wechat', name: '微信视频号', icon: '💬', gradient: 'linear-gradient(135deg, #07c160, #4cd964)' },
  ];
  
  return (
    <PageHead
      title="🔑 账号管理"
      description="绑定您的社交平台账号,用于视频一键发布到各平台"
    />
    
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '20px' }}>
      {platforms.map((platform) => (
        <PlatformCard key={platform.id} platform={platform} />
      ))}
    </div>
    
    {/* 底部统计 */}
    <div style={{
      marginTop: '24px',
      padding: '16px 20px',
      background: '#f8fafc',
      borderRadius: 'var(--radius-md)',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      gap: '8px',
    }}>
      <span style={{ fontSize: '16px' }}>📊</span>
      <span style={{ fontWeight: 600 }}>
        已绑定 <span style={{ color: 'var(--primary)' }}>4</span> 个账号 / 
        支持 <span style={{ color: 'var(--primary)' }}>4</span> 个平台
      </span>
    </div>
  );
};

4.3.2 平台账号卡片组件

interface PlatformCardProps {
  platform: {
    id: string;
    name: string;
    subName: string;
    icon: string;
    gradient: string;
  };
}

const PlatformCard: React.FC<PlatformCardProps> = ({ platform }) => {
  const accounts = useAccounts(platform.id);
  
  return (
    <div
      style={{
        background: '#fff',
        border: '1px solid var(--line)',
        borderRadius: 'var(--radius-lg)',
        padding: '24px',
      }}
    >
      {/* 平台头部 */}
      <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '20px' }}>
        <span style={{ fontSize: '28px' }}>{platform.icon}</span>
        <div>
          <h3 style={{ margin: 0, fontSize: '18px' }}>{platform.name}</h3>
          <p style={{ margin: '4px 0 0', fontSize: '13px', color: 'var(--muted)' }}>
            {platform.subName}
          </p>
        </div>
      </div>
      
      {/* 账号列表 */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: '10px', marginBottom: '16px' }}>
        {accounts.length > 0 ? (
          accounts.map((account) => (
            <div
              key={account.id}
              style={{
                display: 'flex',
                alignItems: 'center',
                gap: '10px',
                padding: '12px',
                background: '#f8fafc',
                borderRadius: '12px',
              }}
            >
              <div
                style={{
                  width: '36px',
                  height: '36px',
                  borderRadius: '50%',
                  background: platform.gradient,
                  display: 'grid',
                  placeItems: 'center',
                  color: '#fff',
                  fontWeight: 700,
                  fontSize: '14px',
                }}
              >
                {account.avatar || platform.icon}
              </div>
              <div style={{ flex: 1 }}>
                <div style={{ fontWeight: 600, fontSize: '14px' }}>{account.name}</div>
                <StatusPill status="ok" />
              </div>
              <button className="btn ghost sm" style={{ padding: '6px 10px', fontSize: '12px' }}>
                解绑
              </button>
            </div>
          ))
        ) : (
          <div style={{ padding: '20px', textAlign: 'center', color: 'var(--muted)', fontSize: '14px' }}>
            <div style={{ fontSize: '32px', marginBottom: '8px' }}>🔓</div>
            <p style={{ margin: 0 }}>暂未绑定账号</p>
          </div>
        )}
      </div>
      
      {/* 绑定按钮 */}
      <button className="btn ghost" style={{ width: '100%' }}>
        + 绑定新账号
      </button>
    </div>
  );
};

优先级: P1


4.4 首页 (Landing Page)

目录: pages/home/

文件: HomePage.tsx

路由: /

4.4.1 页面规格

// pages/home/HomePage.tsx
const HomePage: React.FC = () => {
  return (
    <div>
      {/* Hero 区域 */}
      <HeroSection />
      
      {/* 功能卡片 */}
      <FeatureSection />
      
      {/* 工作流程 */}
      <WorkflowSection />
      
      {/* 定价预览 */}
      <PricingSection />
      
      {/* CTA */}
      <CTASection />
    </div>
  );
};

4.4.2 Hero 区域组件

const HeroSection: React.FC = () => (
  <div className="hero" style={{
    display: 'grid',
    gridTemplateColumns: '1fr 420px',
    gap: '48px',
    alignItems: 'center',
    padding: '60px 0 50px',
    maxWidth: '1440px',
    margin: '0 auto',
    paddingLeft: '24px',
    paddingRight: '24px',
  }}>
    <div>
      <span className="tag">🦐 小虾智剪 · AI智能视频创作平台</span>
      <h1 style={{
        fontSize: '44px',
        lineHeight: 1.1,
        margin: '16px 0 14px',
        letterSpacing: '-0.04em',
      }}>
        上传素材,AI自动剪辑<br/>
        一键生成短视频
      </h1>
      <p style={{
        fontSize: '17px',
        lineHeight: 1.8,
        color: 'var(--muted)',
        margin: '0 0 22px',
      }}>
        面向口播、产品介绍、直播切片和企业宣传的智能视频创作平台。
      </p>
      <div className="hero-actions" style={{ display: 'flex', gap: '12px', marginTop: '8px' }}>
        <button className="btn primary" style={{ padding: '14px 24px', fontSize: '15px' }}>
          立即免费开始
        </button>
        <button className="btn ghost" style={{ padding: '14px 24px', fontSize: '15px' }}>
          查看定价方案
        </button>
      </div>
    </div>
    
    <div className="hero-visual">
      <div className="hero-video">
        <div className="play-btn"></div>
      </div>
      <div className="hero-info">
        <div className="hero-badge">
          <span className="demo-badge"> AI智能剪辑</span>
          <span className="demo-badge"> 30秒生成</span>
        </div>
        <span className="pill ok">可发布</span>
      </div>
    </div>
  </div>
);

优先级: P3


5. 基础组件封装规范

5.1 StatusPill 状态标签

// components/common/StatusPill.tsx
type StatusType = 'ok' | 'warn' | 'bad' | 'info' | 'muted';

interface StatusPillProps {
  status: StatusType;
  label?: string;
}

const statusConfig: Record<StatusType, { bg: string; color: string; defaultLabel: string }> = {
  ok: { bg: '#dcfce7', color: '#166534', defaultLabel: '已通过' },
  warn: { bg: '#ffedd5', color: '#c2410c', defaultLabel: '待复核' },
  bad: { bg: '#ffe4e6', color: '#be123c', defaultLabel: '高风险' },
  info: { bg: '#dbeafe', color: '#1d4ed8', defaultLabel: '视频' },
  muted: { bg: '#f1f5f9', color: '#64748b', defaultLabel: '品牌' },
};

export const StatusPill: React.FC<StatusPillProps> = ({ status, label }) => {
  const config = statusConfig[status];
  return (
    <span
      className="pill"
      style={{
        background: config.bg,
        color: config.color,
        fontSize: '11px',
        padding: '4px 8px',
        borderRadius: '999px',
        fontWeight: 700,
      }}
    >
      {label || config.defaultLabel}
    </span>
  );
};

5.2 CategoryPill 分类标签

// components/common/CategoryPill.tsx
const categoryConfig: Record<string, { bg: string; color: string }> = {
  口播: { bg: '#dbeafe', color: '#1d4ed8' },
  种草: { bg: '#dcfce7', color: '#166534' },
  产品: { bg: '#ffedd5', color: '#c2410c' },
  品牌: { bg: '#f1f5f9', color: '#64748b' },
  直播: { bg: '#fef3c7', color: '#92400e' },
  教程: { bg: '#e0e7ff', color: '#4338ca' },
};

interface CategoryPillProps {
  category: string;
}

export const CategoryPill: React.FC<CategoryPillProps> = ({ category }) => {
  const config = categoryConfig[category] || categoryConfig['口播'];
  return (
    <span
      className="pill"
      style={{
        background: config.bg,
        color: config.color,
        fontSize: '11px',
        padding: '4px 8px',
        borderRadius: '999px',
        fontWeight: 700,
      }}
    >
      {category}
    </span>
  );
};

5.3 IconBox 图标盒子

// components/common/IconBox.tsx
interface IconBoxProps {
  icon: string;
  gradient?: string;
  size?: number;
}

export const IconBox: React.FC<IconBoxProps> = ({
  icon,
  gradient = 'linear-gradient(135deg, #6366f1, #4f46e5)',
  size = 44,
}) => (
  <div
    style={{
      width: `${size}px`,
      height: `${size}px`,
      borderRadius: `${Math.floor(size * 0.36)}px`,
      display: 'grid',
      placeItems: 'center',
      fontSize: `${size * 0.45}px`,
      background: gradient,
      color: '#fff',
    }}
  >
    {icon}
  </div>
);

5.4 UploadZone 上传区域

// components/common/UploadZone.tsx
interface UploadZoneProps {
  accept?: string;
  maxSize?: number; // MB
  onUpload: (file: File) => void;
}

export const UploadZone: React.FC<UploadZoneProps> = ({
  accept = '*',
  maxSize = 2048,
  onUpload,
}) => {
  const [isDragging, setIsDragging] = useState(false);
  
  const handleDrop = (e: React.DragEvent) => {
    e.preventDefault();
    setIsDragging(false);
    const file = e.dataTransfer.files[0];
    if (file && file.size <= maxSize * 1024 * 1024) {
      onUpload(file);
    }
  };
  
  return (
    <div
      onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }}
      onDragLeave={() => setIsDragging(false)}
      onDrop={handleDrop}
      style={{
        border: `2px dashed ${isDragging ? 'var(--primary-light)' : 'var(--line)'}`,
        borderRadius: 'var(--radius-lg)',
        padding: '60px',
        textAlign: 'center',
        background: isDragging ? 'var(--primary-soft)' : 'linear-gradient(180deg, #fff, #f8fafc)',
        transition: 'var(--transition-base)',
        cursor: 'pointer',
      }}
    >
      <IconBox icon="📤" gradient="var(--primary-soft)" />
      <h3 style={{ margin: '16px 0 8px', fontSize: '18px' }}>拖拽上传文件</h3>
      <p style={{ color: 'var(--muted)', fontSize: '13px' }}>或点击选择文件</p>
    </div>
  );
};

5.5 EmptyState 空状态

// components/common/EmptyState.tsx
interface EmptyStateProps {
  icon?: string;
  title: string;
  description?: string;
  action?: {
    label: string;
    onClick: () => void;
    type?: 'primary' | 'ghost';
  };
}

export const EmptyState: React.FC<EmptyStateProps> = ({
  icon = '📭',
  title,
  description,
  action,
}) => (
  <div
    style={{
      border: '1px dashed #cbd5e1',
      background: 'linear-gradient(180deg, #fff, #f8fafc)',
      borderRadius: 'var(--radius-lg)',
      padding: '48px',
      textAlign: 'center',
      color: 'var(--muted)',
    }}
  >
    <IconBox icon={icon} size={60} />
    <h3 style={{ color: 'var(--slate)', margin: '16px 0 8px', fontSize: '16px' }}>
      {title}
    </h3>
    {description && (
      <p style={{ margin: '0 0 16px', fontSize: '14px' }}>{description}</p>
    )}
    {action && (
      <button
        className={`btn ${action.type || 'primary'}`}
        onClick={action.onClick}
      >
        {action.label}
      </button>
    )}
  </div>
);

6. 执行顺序建议

Phase 1: 基础对齐(5-7 人天)

序号 任务 文件 优先级 预估人天
1.1 设计系统 CSS 变量统一 global.css P0 0.5
1.2 主布局组件 AppLayout components/layout/AppLayout.tsx P0 1
1.3 侧边栏组件 Sidebar components/layout/Sidebar.tsx P0 1
1.4 页面头部组件 PageHead components/common/PageHead.tsx P0 0.5
1.5 基础组件封装 components/common/* P1 2

Phase 2: 核心页面改造(8-10 人天)

序号 任务 文件 优先级 预估人天
2.1 一键生成页面 pages/generate/GeneratePage.tsx P1 2
2.2 素材库页面 pages/assets/AssetLibrary.tsx P1 1.5
2.3 配音库页面 pages/voices/VoiceLibrary.tsx P1 1.5
2.4 标题库页面 pages/titles/TitleLibrary.tsx P1 1.5
2.5 成片库页面 pages/products/ProductLibrary.tsx P1 2

Phase 3: 次要页面改造(5-7 人天)

序号 任务 文件 优先级 预估人天
3.1 控制台页面 pages/dashboard/Dashboard.tsx P2 1.5
3.2 模板库页面 pages/templates/TemplateLibrary.tsx P2 1
3.3 任务历史页面 pages/history/TaskHistory.tsx P2 1
3.4 订阅管理页面 pages/subscription/SubscriptionPage.tsx P2 1.5
3.5 剪辑计划编辑器 pages/editing-planner/EditingPlanner.tsx P2 1

Phase 4: 新增页面(3-5 人天)

序号 任务 文件 优先级 预估人天
4.1 查重检测上传页 pages/duplication/DuplicationUpload.tsx P1 0.5
4.2 查重检测结果页 pages/duplication/DuplicationResults.tsx P1 0.5
4.3 查重检测详情页 pages/duplication/DuplicationDetail.tsx P1 0.5
4.4 我的音色页面 pages/voice-clone/VoiceClone.tsx P1 1
4.5 音色克隆 Modal components/modals/CloneVoiceModal.tsx P1 1
4.6 账号管理页面 pages/accounts/Accounts.tsx P1 1
4.7 首页落地页 pages/home/HomePage.tsx P3 1

附录

A. 路由配置参考

// router/index.tsx
const router = createBrowserRouter([
  // 公开页面
  { path: '/', element: <HomePage /> },
  { path: '/login', element: <LoginPage /> },
  { path: '/subscription', element: <SubscriptionPage /> },
  
  // 受保护页面(需要登录)
  {
    path: '/app',
    element: <AppLayout />,
    children: [
      { index: true, element: <Navigate to="/app/dashboard" replace /> },
      { path: 'dashboard', element: <Dashboard /> },
      { path: 'titles', element: <TitleLibrary /> },
      { path: 'assets', element: <AssetLibrary /> },
      { path: 'voices', element: <VoiceLibrary /> },
      { path: 'templates', element: <TemplateLibrary /> },
      { path: 'generate', element: <GeneratePage /> },
      { path: 'products', element: <ProductLibrary /> },
      { path: 'history', element: <TaskHistory /> },
      { path: 'editing-planner', element: <EditingPlanner /> },
      { path: 'subscription', element: <SubscriptionPage /> },
      { path: 'duplication/*', element: <DuplicationRoutes /> },
      { path: 'voice-clone', element: <VoiceClone /> },
      { path: 'accounts', element: <Accounts /> },
    ],
  },
]);

B. 样式文件结构

src/
├── styles/
│   ├── global.css          # CSS 变量、reset、通用样式
│   ├── components.css      # 组件样式(可选)
│   └── pages/
│       ├── dashboard.css
│       ├── generate.css
│       └── ...

C. 注意事项

  1. 不删除现有功能:所有已有的 API 调用逻辑、数据获取/提交逻辑必须保持不变
  2. 不改变业务逻辑:只修改 UI 展示层
  3. 保持路由一致:路由配置必须与原型保持一致
  4. 响应式适配:参考原型中的 @media 断点进行响应式处理

文档版本:V21.1
最后更新:2024年


本内容由 Coze AI 生成,请遵循相关法律法规及《人工智能生成合成内容标识办法》使用与传播。