79 KiB
AIGC
| AIGC | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
V21 UI 前端开发指令文档
版本:V21 UI 1:1 还原开发指南
更新时间:2024年
技术栈:React 18 + TypeScript + Ant Design 5 + Tailwind CSS + React Query
目录
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/WAV,10秒以上)
</p>
<p style={{ margin: '8px 0 0', fontSize: '11px', color: 'var(--muted)' }}>
支持格式:MP3、WAV · 建议时长: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' }}>支持 MP3、WAV 格式</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. 注意事项
- 不删除现有功能:所有已有的 API 调用逻辑、数据获取/提交逻辑必须保持不变
- 不改变业务逻辑:只修改 UI 展示层
- 保持路由一致:路由配置必须与原型保持一致
- 响应式适配:参考原型中的
@media断点进行响应式处理
文档版本:V21.1
最后更新:2024年
本内容由 Coze AI 生成,请遵循相关法律法规及《人工智能生成合成内容标识办法》使用与传播。