feat(ui): V21 design system overhaul #9
@@ -1,10 +1,12 @@
|
||||
/* V21 主容器 */
|
||||
/* V21 主布局样式 */
|
||||
.xx-app-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: var(--bg, #f8fafc);
|
||||
color: var(--slate, #0f172a);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.xx-app-content {
|
||||
@@ -15,7 +17,7 @@
|
||||
padding: 38px 24px 76px;
|
||||
}
|
||||
|
||||
/* 根 CSS 变量 */
|
||||
/* V21 颜色变量 */
|
||||
:root {
|
||||
--indigo: #4f46e5;
|
||||
--slate: #0f172a;
|
||||
@@ -28,5 +30,5 @@
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
|
||||
}
|
||||
|
||||
+20
-10
@@ -1,12 +1,27 @@
|
||||
/**
|
||||
* V21 全局基础样式
|
||||
*/
|
||||
:root {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',
|
||||
'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
line-height: 1.5;
|
||||
line-height: 1.6;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
/* V21 颜色系统 */
|
||||
--indigo: #4f46e5;
|
||||
--indigo-dark: #4338ca;
|
||||
--indigo-soft: #eef2ff;
|
||||
--slate: #0f172a;
|
||||
--muted: #64748b;
|
||||
--line: #e2e8f0;
|
||||
--bg: #f8fafc;
|
||||
--green: #10b981;
|
||||
--amber: #f59e0b;
|
||||
--red: #ef4444;
|
||||
|
||||
color-scheme: light;
|
||||
color: var(--slate);
|
||||
background-color: var(--bg);
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
@@ -21,16 +36,11 @@
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
+10
-6
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 更新主入口,引入全局样式
|
||||
* V21 设计系统入口
|
||||
*/
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
@@ -17,17 +17,21 @@ const queryClient = new QueryClient({
|
||||
queries: {
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: 5 * 60 * 1000, // 5 分钟
|
||||
gcTime: 10 * 60 * 1000, // 10 分钟
|
||||
staleTime: 5 * 60 * 1000,
|
||||
gcTime: 10 * 60 * 1000,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Ant Design 主题配置
|
||||
// V21 Ant Design 主题配置
|
||||
const theme = {
|
||||
token: {
|
||||
colorPrimary: '#1890ff',
|
||||
borderRadius: 4,
|
||||
// V21 Indigo 主色
|
||||
colorPrimary: '#4f46e5',
|
||||
// V21 大圆角
|
||||
borderRadius: 14,
|
||||
borderRadiusLG: 22,
|
||||
borderRadiusSM: 12,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,38 @@
|
||||
/* V21 订阅计划 */
|
||||
/* V21 定价页面 */
|
||||
.xx-plans-page {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 60px 24px;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.xx-page-head {
|
||||
text-align: center;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.xx-page-head h2 {
|
||||
font-size: 42px;
|
||||
font-weight: 900;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 0 0 16px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.xx-page-head p {
|
||||
font-size: 18px;
|
||||
color: var(--muted, #64748b);
|
||||
margin: 0;
|
||||
max-width: 560px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* 定价卡片网格 - 4列 */
|
||||
.xx-plans-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 24px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
@@ -18,86 +45,172 @@
|
||||
@media (max-width: 768px) {
|
||||
.xx-plans-grid {
|
||||
grid-template-columns: 1fr;
|
||||
max-width: 400px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* V21 定价卡片 */
|
||||
.xx-plan-card {
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
border: 1px solid #e2e8f0;
|
||||
background: rgba(255,255,255,0.94);
|
||||
border: 1px solid rgba(226,232,240,0.95);
|
||||
border-radius: 28px;
|
||||
box-shadow: 0 24px 70px rgba(15, 23, 42, 0.09);
|
||||
padding: 28px;
|
||||
box-shadow: 0 24px 70px rgba(15,23,42,0.09);
|
||||
padding: 32px 28px;
|
||||
position: relative;
|
||||
transition: all 0.2s;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.xx-plan-card:hover {
|
||||
border-color: #4f46e5;
|
||||
box-shadow: 0 32px 90px rgba(79, 70, 229, 0.15);
|
||||
border-color: var(--indigo, #4f46e5);
|
||||
box-shadow: 0 32px 90px rgba(79,70,229,0.15);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.xx-plan-card.featured {
|
||||
border-color: #4f46e5;
|
||||
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.12), 0 32px 90px rgba(79, 70, 229, 0.15);
|
||||
border: 2px solid var(--indigo, #4f46e5);
|
||||
box-shadow: 0 0 0 4px rgba(79,70,229,0.1), 0 32px 90px rgba(79,70,229,0.15);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.xx-plan-card.featured:hover {
|
||||
transform: scale(1.02) translateY(-4px);
|
||||
}
|
||||
|
||||
/* 推荐标签 */
|
||||
.xx-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 24px;
|
||||
transform: translateY(-50%);
|
||||
background: #4f46e5;
|
||||
top: -12px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: linear-gradient(135deg, #6366f1, #4f46e5);
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
padding: 6px 16px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 850;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 企业版定制标签 */
|
||||
.xx-badge.enterprise {
|
||||
background: linear-gradient(135deg, #f59e0b, #d97706);
|
||||
}
|
||||
|
||||
/* 套餐名称 */
|
||||
.xx-plan-card h3 {
|
||||
font-size: 24px;
|
||||
margin: 0 0 16px;
|
||||
color: #0f172a;
|
||||
font-size: 22px;
|
||||
font-weight: 900;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 0 0 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 价格 */
|
||||
.xx-plan-price {
|
||||
font-size: 42px;
|
||||
text-align: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.xx-plan-price .currency {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--muted, #64748b);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.xx-plan-price .amount {
|
||||
font-size: 48px;
|
||||
font-weight: 900;
|
||||
color: #4f46e5;
|
||||
margin-bottom: 8px;
|
||||
color: var(--indigo, #4f46e5);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.xx-plan-price span {
|
||||
.xx-plan-price .period {
|
||||
font-size: 16px;
|
||||
color: #64748b;
|
||||
font-weight: 750;
|
||||
color: var(--muted, #64748b);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 企业版价格 */
|
||||
.xx-plan-price.custom .amount {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
/* 描述 */
|
||||
.xx-plan-description {
|
||||
color: #64748b;
|
||||
color: var(--muted, #64748b);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid var(--line, #e2e8f0);
|
||||
}
|
||||
|
||||
/* 功能列表 */
|
||||
.xx-features {
|
||||
margin-top: 24px;
|
||||
margin-top: 8px;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.xx-feature {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
color: #64748b;
|
||||
color: var(--muted, #64748b);
|
||||
font-size: 14px;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.xx-feature span {
|
||||
color: #10b981;
|
||||
.xx-feature span:first-child {
|
||||
color: var(--green, #10b981);
|
||||
font-weight: 900;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 订阅按钮 */
|
||||
.xx-subscribe-btn {
|
||||
width: 100%;
|
||||
margin-top: 24px;
|
||||
padding: 14px 24px !important;
|
||||
font-size: 15px !important;
|
||||
font-weight: 850 !important;
|
||||
border-radius: 14px !important;
|
||||
transition: all 0.2s !important;
|
||||
}
|
||||
|
||||
.xx-subscribe-btn.primary {
|
||||
background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
|
||||
color: white !important;
|
||||
border: none !important;
|
||||
box-shadow: 0 14px 26px rgba(79,70,229,0.22) !important;
|
||||
}
|
||||
|
||||
.xx-subscribe-btn.primary:hover {
|
||||
box-shadow: 0 18px 34px rgba(79,70,229,0.28) !important;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.xx-subscribe-btn.ghost {
|
||||
background: white !important;
|
||||
border: 1px solid var(--line, #e2e8f0) !important;
|
||||
color: var(--slate, #0f172a) !important;
|
||||
}
|
||||
|
||||
.xx-subscribe-btn.ghost:hover {
|
||||
border-color: var(--indigo, #4f46e5) !important;
|
||||
color: var(--indigo, #4f46e5) !important;
|
||||
}
|
||||
|
||||
/* 企业版按钮 */
|
||||
.xx-subscribe-btn.enterprise {
|
||||
background: linear-gradient(135deg, #f59e0b, #d97706) !important;
|
||||
color: white !important;
|
||||
border: none !important;
|
||||
box-shadow: 0 14px 26px rgba(245,158,11,0.22) !important;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,68 @@
|
||||
/**
|
||||
* 订阅/计划页面 - V21 完全对标
|
||||
* 定价页面 - V21 UI 四级定价
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { message } from 'antd';
|
||||
import './Plans.css';
|
||||
|
||||
const PLANS = [
|
||||
{ id: 'free', name: 'Free', price: 0, description: '免费试用', recommended: false, features: ['10个项目', '基础视频编辑', '社区支持'] },
|
||||
{ id: 'pro', name: 'Pro', price: 99, description: '专业用户', recommended: true, features: ['无限项目', '高级编辑', '优先支持', 'API 访问'] },
|
||||
{ id: 'enterprise', name: 'Enterprise', price: 999, description: '企业级', recommended: false, features: ['定制方案', '专属支持', '白标服务', 'SLA 保证'] },
|
||||
{
|
||||
id: 'free',
|
||||
name: '体验版',
|
||||
price: 0,
|
||||
description: '适合个人体验',
|
||||
recommended: false,
|
||||
features: [
|
||||
'3个视频成片',
|
||||
'基础标题模板',
|
||||
'基础配音音色',
|
||||
'720p导出'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'standard',
|
||||
name: '标准版',
|
||||
price: 99,
|
||||
description: '适合内容创作者',
|
||||
recommended: false,
|
||||
features: [
|
||||
'无限视频成片',
|
||||
'高级标题模板',
|
||||
'全部配音音色',
|
||||
'1080p导出',
|
||||
'API接口'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'pro',
|
||||
name: '专业版',
|
||||
price: 299,
|
||||
description: '适合专业团队',
|
||||
recommended: true,
|
||||
features: [
|
||||
'无限视频成片',
|
||||
'自定义标题',
|
||||
'全部配音音色',
|
||||
'4K导出',
|
||||
'优先渲染队列',
|
||||
'专属客服'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'enterprise',
|
||||
name: '企业版',
|
||||
price: null,
|
||||
description: '适合企业用户',
|
||||
recommended: false,
|
||||
features: [
|
||||
'私有化部署',
|
||||
'自定义品牌形象',
|
||||
' SLA服务保障',
|
||||
'技术支持',
|
||||
'团队协作',
|
||||
'数据报表'
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
const Plans: React.FC = () => {
|
||||
@@ -28,41 +82,57 @@ const Plans: React.FC = () => {
|
||||
return (
|
||||
<div className="xx-plans-page">
|
||||
<div className="xx-page-head">
|
||||
<div>
|
||||
<h2>选择订阅计划</h2>
|
||||
<p>升级您的账户享受更多功能</p>
|
||||
</div>
|
||||
<h2>选择适合您的方案</h2>
|
||||
<p>灵活定价,满足不同规模的创作需求</p>
|
||||
</div>
|
||||
|
||||
<div className="xx-plans-grid">
|
||||
{PLANS.map((plan) => (
|
||||
<div key={plan.id} className={`xx-plan-card ${plan.recommended ? 'featured' : ''}`}>
|
||||
<div
|
||||
key={plan.id}
|
||||
className={`xx-plan-card ${plan.recommended ? 'featured' : ''}`}
|
||||
>
|
||||
{plan.recommended && <div className="xx-badge">推荐</div>}
|
||||
{plan.id === 'enterprise' && <div className="xx-badge enterprise">定制</div>}
|
||||
|
||||
<h3>{plan.name}</h3>
|
||||
<div className="xx-plan-price">
|
||||
¥{plan.price}
|
||||
<span>/月</span>
|
||||
|
||||
<div className={`xx-plan-price ${plan.price === null ? 'custom' : ''}`}>
|
||||
{plan.price !== null ? (
|
||||
<>
|
||||
<span className="currency">¥</span>
|
||||
<span className="amount">{plan.price}</span>
|
||||
<span className="period">/月</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="amount">联系我们</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="xx-plan-description">{plan.description}</p>
|
||||
|
||||
<button
|
||||
className={`btn ${plan.recommended ? 'primary' : 'ghost'}`}
|
||||
onClick={() => handleSubscribe(plan.id)}
|
||||
disabled={subscribing}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{subscribing ? '处理中...' : '选择此计划'}
|
||||
</button>
|
||||
|
||||
<div className="xx-features">
|
||||
{plan.features.map((feature: string, idx: number) => (
|
||||
{plan.features.map((feature, idx) => (
|
||||
<div key={idx} className="xx-feature">
|
||||
<span>✓</span>
|
||||
{feature}
|
||||
<span>{feature}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
className={`xx-subscribe-btn ${
|
||||
plan.id === 'enterprise'
|
||||
? 'enterprise'
|
||||
: plan.recommended
|
||||
? 'primary'
|
||||
: 'ghost'
|
||||
}`}
|
||||
onClick={() => handleSubscribe(plan.id)}
|
||||
disabled={subscribing}
|
||||
>
|
||||
{subscribing ? '处理中...' : plan.price === null ? '联系我们' : '立即订阅'}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,200 +1,208 @@
|
||||
/* V21 素材库 */
|
||||
/* V21 素材库页面 */
|
||||
.xx-project-assets {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.xx-page-head {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.xx-page-head h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 850;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.xx-page-head p {
|
||||
font-size: 14px;
|
||||
color: var(--muted, #64748b);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* V21 大卡片 */
|
||||
.xx-card {
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
border: 1px solid rgba(226, 232, 240, 0.95);
|
||||
background: rgba(255,255,255,0.94);
|
||||
border: 1px solid rgba(226,232,240,0.95);
|
||||
border-radius: 28px;
|
||||
box-shadow: 0 24px 70px rgba(15, 23, 42, 0.09);
|
||||
box-shadow: 0 24px 70px rgba(15,23,42,0.09);
|
||||
padding: 28px;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 24px;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
.xx-card:hover {
|
||||
box-shadow: 0 26px 64px rgba(15,23,42,0.14);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.xx-card h3 {
|
||||
font-size: 21px;
|
||||
margin: 0 0 16px;
|
||||
color: #0f172a;
|
||||
font-size: 18px;
|
||||
font-weight: 850;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.xx-loading {
|
||||
padding: 60px 24px;
|
||||
text-align: center;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* 上传区域 */
|
||||
.xx-upload-zone {
|
||||
border: 2px dashed #e2e8f0;
|
||||
border: 2px dashed var(--line, #e2e8f0);
|
||||
border-radius: 24px;
|
||||
padding: 40px;
|
||||
padding: 48px;
|
||||
text-align: center;
|
||||
background: #f8fafc;
|
||||
background: var(--bg, #f8fafc);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.xx-upload-zone:hover {
|
||||
border-color: #4f46e5;
|
||||
background: #eef2ff;
|
||||
border-color: var(--indigo, #4f46e5);
|
||||
background: var(--indigo-soft, #eef2ff);
|
||||
}
|
||||
|
||||
.xx-upload-zone .icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.xx-upload-zone p {
|
||||
color: #64748b;
|
||||
color: var(--muted, #64748b);
|
||||
margin: 12px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.xx-video-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
/* V21 上传按钮 */
|
||||
.xx-upload-btn {
|
||||
background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
|
||||
color: white !important;
|
||||
border: none !important;
|
||||
border-radius: 14px !important;
|
||||
padding: 12px 24px !important;
|
||||
font-weight: 850 !important;
|
||||
box-shadow: 0 14px 26px rgba(79,70,229,0.22) !important;
|
||||
transition: all 0.2s !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.xx-video-grid {
|
||||
.xx-upload-btn:hover {
|
||||
box-shadow: 0 18px 34px rgba(79,70,229,0.28) !important;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 素材网格 - 9:16 竖屏卡片 */
|
||||
.xx-assets-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.xx-assets-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.xx-video-grid {
|
||||
@media (max-width: 480px) {
|
||||
.xx-assets-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.xx-video-card {
|
||||
border: 1px solid #e2e8f0;
|
||||
/* V21 素材卡片 - 大圆角 */
|
||||
.xx-asset-card {
|
||||
background: white;
|
||||
border: 1px solid var(--line, #e2e8f0);
|
||||
border-radius: 22px;
|
||||
padding: 12px;
|
||||
transition: all 0.2s;
|
||||
transition: all 0.25s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xx-video-card:hover {
|
||||
border-color: #4f46e5;
|
||||
box-shadow: 0 12px 36px rgba(79, 70, 229, 0.12);
|
||||
.xx-asset-card:hover {
|
||||
border-color: var(--indigo, #4f46e5);
|
||||
box-shadow: 0 26px 64px rgba(15,23,42,0.14);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.xx-video-thumb {
|
||||
aspect-ratio: 16 / 9;
|
||||
/* 缩略图 - 9:16 竖屏 */
|
||||
.xx-asset-thumb {
|
||||
aspect-ratio: 9 / 16;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(135deg, #0ea5e9, #312e81);
|
||||
background: linear-gradient(135deg, #6366f1, #0ea5e9, #10b981);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: white;
|
||||
margin-bottom: 10px;
|
||||
font-size: 32px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.mini-play {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: rgba(255, 255, 255, 0.22);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
backdrop-filter: blur(8px);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.xx-video-card b {
|
||||
display: block;
|
||||
color: #0f172a;
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.xx-video-card p {
|
||||
margin: 5px 0 0;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
/* 状态标签 - Pill 样式 */
|
||||
.xx-asset-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.pill {
|
||||
gap: 4px;
|
||||
font-size: 11px;
|
||||
padding: 3px 7px;
|
||||
border-radius: 999px;
|
||||
font-weight: 850;
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pill.ok {
|
||||
.xx-asset-status.ready {
|
||||
background: #dcfce7;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.pill.warn {
|
||||
.xx-asset-status.processing {
|
||||
background: #fef3c7;
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
.pill.bad {
|
||||
background: #ffe4e6;
|
||||
color: #be123c;
|
||||
.xx-asset-status.failed {
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.assist-actions {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: 0;
|
||||
border-radius: 14px;
|
||||
padding: 12px 17px;
|
||||
font-weight: 850;
|
||||
cursor: pointer;
|
||||
.xx-asset-card b {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
font-weight: 850;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 8px 0 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn.primary {
|
||||
background: #4f46e5;
|
||||
color: white;
|
||||
box-shadow: 0 16px 28px rgba(79, 70, 229, 0.24);
|
||||
}
|
||||
|
||||
.btn.primary:hover {
|
||||
background: #4338ca;
|
||||
box-shadow: 0 20px 36px rgba(79, 70, 229, 0.32);
|
||||
}
|
||||
|
||||
.btn.primary:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn.ghost {
|
||||
background: white;
|
||||
border: 1px solid #e2e8f0;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.btn.ghost:hover {
|
||||
background: #f8fafc;
|
||||
border-color: #4f46e5;
|
||||
color: #4f46e5;
|
||||
.xx-asset-card p {
|
||||
font-size: 12px;
|
||||
color: var(--muted, #64748b);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.xx-empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 24px;
|
||||
color: #64748b;
|
||||
padding: 60px 24px;
|
||||
color: var(--muted, #64748b);
|
||||
}
|
||||
|
||||
.xx-empty-state p {
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.xx-loading {
|
||||
padding: 60px 24px;
|
||||
text-align: center;
|
||||
color: var(--muted, #64748b);
|
||||
}
|
||||
|
||||
/* 隐藏文件输入 */
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 素材库页面 - V21 完全对标
|
||||
* 素材库页面 - V21 UI
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -26,15 +26,13 @@ const ProjectAssets: React.FC = () => {
|
||||
return (
|
||||
<div className="xx-project-assets">
|
||||
<div className="xx-page-head">
|
||||
<div>
|
||||
<h2>素材库</h2>
|
||||
<p>管理和组织您的视频素材</p>
|
||||
</div>
|
||||
<h2>素材管理</h2>
|
||||
<p>上传和管理您的视频素材,支持多种格式</p>
|
||||
</div>
|
||||
|
||||
{/* 上传区 */}
|
||||
{/* 上传区域 - V21 设计 */}
|
||||
<div className="xx-card">
|
||||
<h3>上传新素材</h3>
|
||||
<h3>📤 上传素材</h3>
|
||||
<div
|
||||
className="xx-upload-zone"
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
@@ -43,43 +41,46 @@ const ProjectAssets: React.FC = () => {
|
||||
if (e.dataTransfer.files) handleFileUpload(e.dataTransfer.files);
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '32px', marginBottom: '12px' }}>📁</div>
|
||||
<p>拖拽文件到这里或点击选择</p>
|
||||
<div className="icon">🎬</div>
|
||||
<p>拖拽视频文件到此处,或点击下方按钮上传</p>
|
||||
<p>支持 MP4、MOV、AVI 格式,单个文件最大 500MB</p>
|
||||
<input
|
||||
type="file"
|
||||
multiple
|
||||
style={{ display: 'none' }}
|
||||
accept="video/*"
|
||||
id="file-input"
|
||||
onChange={(e) => e.target.files && handleFileUpload(e.target.files)}
|
||||
/>
|
||||
<button
|
||||
className="btn primary"
|
||||
className="xx-upload-btn"
|
||||
onClick={() => document.getElementById('file-input')?.click()}
|
||||
disabled={uploading}
|
||||
>
|
||||
{uploading ? '上传中...' : '选择文件'}
|
||||
{uploading ? '上传中...' : '选择文件上传'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 素材列表 */}
|
||||
{/* 素材库 - V21 竖屏卡片网格 */}
|
||||
<div className="xx-card">
|
||||
<h3>已上传素材</h3>
|
||||
<h3>📁 我的素材库</h3>
|
||||
{assets.length === 0 ? (
|
||||
<div className="xx-empty-state">
|
||||
<p>还没有上传任何素材</p>
|
||||
<div style={{ fontSize: '48px', marginBottom: '16px' }}>📂</div>
|
||||
<p>还没有上传素材</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="xx-video-grid">
|
||||
<div className="xx-assets-grid">
|
||||
{assets.map((asset) => (
|
||||
<div key={asset.id} className="xx-video-card">
|
||||
<div className="xx-video-thumb">
|
||||
<div className="mini-play">▶</div>
|
||||
<div key={asset.id} className="xx-asset-card">
|
||||
<div className="xx-asset-thumb">
|
||||
<span>▶</span>
|
||||
</div>
|
||||
<div className="row">
|
||||
<b>{asset.name}</b>
|
||||
</div>
|
||||
<p>视频素材</p>
|
||||
<span className={`xx-asset-status ${asset.status || 'ready'}`}>
|
||||
{asset.status === 'processing' ? '⏳ 处理中' : '✓ 就绪'}
|
||||
</span>
|
||||
<b>{asset.name}</b>
|
||||
<p>{(asset.size / 1024 / 1024).toFixed(2)} MB</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,77 @@
|
||||
/* V21 视频剪辑 */
|
||||
/* V21 剪辑计划页面 */
|
||||
.xx-project-generation {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.xx-page-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 18px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.xx-page-head h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 850;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.xx-page-head p {
|
||||
font-size: 14px;
|
||||
color: var(--muted, #64748b);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* V21 按钮 */
|
||||
.xx-primary-btn {
|
||||
background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
|
||||
color: white !important;
|
||||
border: none !important;
|
||||
border-radius: 14px !important;
|
||||
padding: 12px 24px !important;
|
||||
font-weight: 850 !important;
|
||||
box-shadow: 0 14px 26px rgba(79,70,229,0.22) !important;
|
||||
transition: all 0.2s !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xx-primary-btn:hover {
|
||||
box-shadow: 0 18px 34px rgba(79,70,229,0.28) !important;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* V21 卡片 */
|
||||
.xx-card {
|
||||
background: rgba(255,255,255,0.94);
|
||||
border: 1px solid rgba(226,232,240,0.95);
|
||||
border-radius: 28px;
|
||||
box-shadow: 0 24px 70px rgba(15,23,42,0.09);
|
||||
padding: 28px;
|
||||
margin-bottom: 24px;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
.xx-card:hover {
|
||||
box-shadow: 0 26px 64px rgba(15,23,42,0.14);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.xx-card h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 850;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
/* 模板网格 */
|
||||
.xx-template-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 14px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
@@ -20,59 +86,141 @@
|
||||
}
|
||||
}
|
||||
|
||||
.xx-choice {
|
||||
/* V21 模板卡片 */
|
||||
.xx-choice-card {
|
||||
position: relative;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 18px;
|
||||
padding: 14px;
|
||||
border: 2px solid var(--line, #e2e8f0);
|
||||
border-radius: 20px;
|
||||
padding: 0;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: all 0.25s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.xx-choice:hover {
|
||||
border-color: #4f46e5;
|
||||
.xx-choice-card:hover {
|
||||
border-color: var(--indigo, #4f46e5);
|
||||
box-shadow: 0 12px 36px rgba(79,70,229,0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.xx-choice.selected {
|
||||
border-color: #4f46e5;
|
||||
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.16);
|
||||
.xx-choice-card.selected {
|
||||
border-color: var(--indigo, #4f46e5);
|
||||
box-shadow: 0 0 0 4px rgba(79,70,229,0.1), 0 12px 36px rgba(79,70,229,0.2);
|
||||
}
|
||||
|
||||
.check {
|
||||
/* 模板封面 */
|
||||
.xx-template-cover {
|
||||
height: 100px;
|
||||
border-radius: 18px 18px 0 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: white;
|
||||
font-size: 32px;
|
||||
margin: 8px 8px 0;
|
||||
}
|
||||
|
||||
.xx-template-cover.gradient-1 {
|
||||
background: linear-gradient(135deg, #6366f1, #4f46e5);
|
||||
}
|
||||
|
||||
.xx-template-cover.gradient-2 {
|
||||
background: linear-gradient(135deg, #f97316, #ef4444);
|
||||
}
|
||||
|
||||
.xx-template-cover.gradient-3 {
|
||||
background: linear-gradient(135deg, #10b981, #0284c7);
|
||||
}
|
||||
|
||||
.xx-template-cover.gradient-4 {
|
||||
background: linear-gradient(135deg, #8b5cf6, #ec4899);
|
||||
}
|
||||
|
||||
/* 选中标记 */
|
||||
.xx-choice-card.selected .xx-template-cover::after {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
right: -7px;
|
||||
top: -7px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: #4f46e5;
|
||||
background: var(--indigo, #4f46e5);
|
||||
color: white;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-weight: 900;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cover {
|
||||
height: 78px;
|
||||
border-radius: 14px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: white;
|
||||
font-size: 28px;
|
||||
margin-bottom: 10px;
|
||||
.xx-choice-card .xx-template-info {
|
||||
padding: 12px 16px 16px;
|
||||
}
|
||||
|
||||
.xx-choice b {
|
||||
.xx-choice-card b {
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
font-weight: 850;
|
||||
color: #0f172a;
|
||||
display: block;
|
||||
color: var(--slate, #0f172a);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.xx-choice p {
|
||||
margin: 5px 0 0;
|
||||
color: #64748b;
|
||||
.xx-choice-card p {
|
||||
font-size: 12px;
|
||||
color: var(--muted, #64748b);
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 分栏布局 */
|
||||
.xx-generation-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.xx-generation-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 计划预览 */
|
||||
.xx-preview-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.xx-preview-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: var(--bg, #f8fafc);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.xx-preview-item .icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #6366f1, #4f46e5);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.xx-preview-item h4 {
|
||||
font-size: 14px;
|
||||
font-weight: 850;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
.xx-preview-item p {
|
||||
font-size: 12px;
|
||||
color: var(--muted, #64748b);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
/**
|
||||
* 视频剪辑/生成页面 - V21 完全对标
|
||||
* 剪辑计划页面 - V21 UI
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Select, Modal, Form, message } from 'antd';
|
||||
import './ProjectGeneration.css';
|
||||
|
||||
const TITLE_OPTIONS = [
|
||||
{ id: '1', name: '产品介绍', content: '完整介绍产品功能和优势' },
|
||||
{ id: '2', name: '用户评价', content: '展示用户真实反馈和评分' },
|
||||
{ id: '3', name: '功能演示', content: '详细演示产品各项功能' },
|
||||
const TEMPLATE_OPTIONS = [
|
||||
{ id: '1', name: '知识科普', content: '适合科普、教育类内容' },
|
||||
{ id: '2', name: '产品展示', content: '适合电商、产品介绍' },
|
||||
{ id: '3', name: '生活分享', content: '适合日常Vlog、生活记录' },
|
||||
{ id: '4', name: '新闻资讯', content: '适合新闻、热点解读' },
|
||||
];
|
||||
|
||||
const ProjectGeneration: React.FC = () => {
|
||||
@@ -21,12 +22,12 @@ const ProjectGeneration: React.FC = () => {
|
||||
const handleGenerate = async () => {
|
||||
const values = await form.validateFields();
|
||||
try {
|
||||
message.success('成片生成任务已创建');
|
||||
message.success('开始生成成片');
|
||||
setGenerateOpen(false);
|
||||
form.resetFields();
|
||||
setSelectedTitle('');
|
||||
} catch (error: any) {
|
||||
message.error('创建生成任务失败');
|
||||
message.error('生成失败');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -34,44 +35,71 @@ const ProjectGeneration: React.FC = () => {
|
||||
<div className="xx-project-generation">
|
||||
<div className="xx-page-head">
|
||||
<div>
|
||||
<h2>视频剪辑</h2>
|
||||
<p>生成您的成片</p>
|
||||
<h2>剪辑计划</h2>
|
||||
<p>选择模板和标题,创建您的视频成片</p>
|
||||
</div>
|
||||
<button className="btn primary" onClick={() => setGenerateOpen(true)}>
|
||||
<button className="xx-primary-btn" onClick={() => setGenerateOpen(true)}>
|
||||
+ 开始生成
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="xx-card">
|
||||
<h3>生成计划</h3>
|
||||
<div className="xx-template-grid">
|
||||
{TITLE_OPTIONS.map((title, idx) => {
|
||||
const colors = [
|
||||
'linear-gradient(135deg,#4f46e5,#818cf8)',
|
||||
'linear-gradient(135deg,#f97316,#ef4444)',
|
||||
'linear-gradient(135deg,#10b981,#0284c7)',
|
||||
'linear-gradient(135deg,#64748b,#334155)',
|
||||
];
|
||||
return (
|
||||
<div
|
||||
key={title.id}
|
||||
className={`xx-choice ${selectedTitle === title.id ? 'selected' : ''}`}
|
||||
onClick={() => setSelectedTitle(title.id)}
|
||||
>
|
||||
<div className="cover" style={{ background: colors[idx % 4] }}>
|
||||
📹
|
||||
<div className="xx-generation-layout">
|
||||
{/* 模板选择 */}
|
||||
<div className="xx-card">
|
||||
<h3>🎨 选择模板</h3>
|
||||
<div className="xx-template-grid">
|
||||
{TEMPLATE_OPTIONS.map((title, idx) => {
|
||||
const gradients = ['gradient-1', 'gradient-2', 'gradient-3', 'gradient-4'];
|
||||
return (
|
||||
<div
|
||||
key={title.id}
|
||||
className={`xx-choice-card ${selectedTitle === title.id ? 'selected' : ''}`}
|
||||
onClick={() => setSelectedTitle(title.id)}
|
||||
>
|
||||
<div className={`xx-template-cover ${gradients[idx % 4]}`}>
|
||||
{idx + 1}
|
||||
</div>
|
||||
<div className="xx-template-info">
|
||||
<b>{title.name}</b>
|
||||
<p>{title.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
{selectedTitle === title.id && <div className="check">✓</div>}
|
||||
<b>{title.name}</b>
|
||||
<p>{title.content.substring(0, 50)}...</p>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 计划预览 */}
|
||||
<div className="xx-preview-section">
|
||||
<div className="xx-card">
|
||||
<h3>📋 计划概览</h3>
|
||||
<div className="xx-preview-item">
|
||||
<div className="icon">📹</div>
|
||||
<div>
|
||||
<h4>视频素材</h4>
|
||||
<p>已上传 0 个素材</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="xx-preview-item">
|
||||
<div className="icon">✍️</div>
|
||||
<div>
|
||||
<h4>标题模板</h4>
|
||||
<p>{selectedTitle ? TEMPLATE_OPTIONS.find(t => t.id === selectedTitle)?.name : '未选择'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="xx-preview-item">
|
||||
<div className="icon">🎙️</div>
|
||||
<div>
|
||||
<h4>配音音色</h4>
|
||||
<p>请在配音库选择</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
title="生成新成片"
|
||||
title="开始生成成片"
|
||||
open={generateOpen}
|
||||
okText="开始生成"
|
||||
cancelText="取消"
|
||||
@@ -84,9 +112,9 @@ const ProjectGeneration: React.FC = () => {
|
||||
label="选择模板"
|
||||
rules={[{ required: true, message: '请选择模板' }]}
|
||||
>
|
||||
<Select placeholder="选择剪辑模板">
|
||||
<Select placeholder="请选择模板">
|
||||
<Select.Option value="basic">基础模板</Select.Option>
|
||||
<Select.Option value="product">产品介绍</Select.Option>
|
||||
<Select.Option value="product">产品模板</Select.Option>
|
||||
<Select.Option value="lifestyle">生活方式</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
@@ -95,7 +123,7 @@ const ProjectGeneration: React.FC = () => {
|
||||
label="视频时长"
|
||||
rules={[{ required: true, message: '请选择时长' }]}
|
||||
>
|
||||
<Select placeholder="选择时长">
|
||||
<Select placeholder="请选择时长">
|
||||
<Select.Option value="15">15秒</Select.Option>
|
||||
<Select.Option value="30">30秒</Select.Option>
|
||||
<Select.Option value="60">60秒</Select.Option>
|
||||
|
||||
@@ -1,103 +1,175 @@
|
||||
/* V21 成片库 */
|
||||
/* V21 成片库页面 */
|
||||
.xx-project-results {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.xx-page-head {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.xx-page-head h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 850;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.xx-page-head p {
|
||||
font-size: 14px;
|
||||
color: var(--muted, #64748b);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* V21 竖屏成片网格 */
|
||||
.xx-vertical-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.xx-vertical-grid.compact {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.xx-vertical-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.xx-vertical-grid.compact {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.xx-vertical-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.xx-vertical-grid.compact {
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.xx-vertical-grid {
|
||||
grid-template-columns: 1fr;
|
||||
max-width: 300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* V21 竖屏成片卡片 */
|
||||
.xx-vertical-card {
|
||||
border: 1px solid #e2e8f0;
|
||||
background: white;
|
||||
border: 1px solid var(--line, #e2e8f0);
|
||||
border-radius: 24px;
|
||||
padding: 12px;
|
||||
transition: all 0.2s;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xx-vertical-card:hover {
|
||||
border-color: #4f46e5;
|
||||
box-shadow: 0 12px 36px rgba(79, 70, 229, 0.12);
|
||||
border-color: var(--indigo, #4f46e5);
|
||||
box-shadow: 0 26px 64px rgba(15,23,42,0.14);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
/* 竖屏缩略图 */
|
||||
.xx-vertical-thumb {
|
||||
aspect-ratio: 9 / 16;
|
||||
border-radius: 18px;
|
||||
background: linear-gradient(135deg, #0ea5e9, #312e81);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: white;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
font-size: 32px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.xx-vertical-thumb.orange {
|
||||
background: linear-gradient(135deg, #f97316, #ef4444);
|
||||
}
|
||||
|
||||
.xx-vertical-thumb.gray {
|
||||
background: linear-gradient(135deg, #64748b, #334155);
|
||||
.xx-vertical-thumb.indigo {
|
||||
background: linear-gradient(135deg, #6366f1, #4f46e5);
|
||||
}
|
||||
|
||||
.xx-vertical-thumb.green {
|
||||
background: linear-gradient(135deg, #10b981, #0284c7);
|
||||
}
|
||||
|
||||
.xx-vertical-thumb video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
.xx-vertical-thumb.purple {
|
||||
background: linear-gradient(135deg, #8b5cf6, #ec4899);
|
||||
}
|
||||
|
||||
.xx-vertical-thumb img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
/* 播放按钮 */
|
||||
.xx-play-btn {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.2);
|
||||
backdrop-filter: blur(4px);
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.xx-vertical-card:hover .xx-play-btn {
|
||||
background: rgba(255,255,255,0.3);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* 视频信息 */
|
||||
.xx-vertical-card b {
|
||||
display: block;
|
||||
color: #0f172a;
|
||||
font-weight: 850;
|
||||
font-size: 14px;
|
||||
font-weight: 850;
|
||||
color: var(--slate, #0f172a);
|
||||
margin-bottom: 6px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.xx-vertical-card p {
|
||||
margin: 5px 0 0;
|
||||
color: #64748b;
|
||||
.xx-vertical-card .meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: var(--muted, #64748b);
|
||||
}
|
||||
|
||||
.assist-actions {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
.xx-vertical-card .duration {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.xx-vertical-card .size {
|
||||
color: var(--green, #10b981);
|
||||
}
|
||||
|
||||
/* 状态标签 */
|
||||
.xx-status-pill {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: 850;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.xx-status-pill.ready {
|
||||
background: #dcfce7;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.xx-status-pill.processing {
|
||||
background: #fef3c7;
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.xx-empty-state {
|
||||
text-align: center;
|
||||
padding: 80px 24px;
|
||||
color: var(--muted, #64748b);
|
||||
}
|
||||
|
||||
.xx-empty-state p {
|
||||
font-size: 16px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 成片库页面 - V21 完全对标
|
||||
* 成片库页面 - V21 UI
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -9,37 +9,40 @@ const ProjectResults: React.FC = () => {
|
||||
const { id: projectId } = useParams<{ id: string }>();
|
||||
const [videos] = useState<any[]>([]);
|
||||
|
||||
const colors = ['orange', 'indigo', 'green', 'purple'];
|
||||
|
||||
return (
|
||||
<div className="xx-project-results">
|
||||
<div className="xx-page-head">
|
||||
<div>
|
||||
<h2>成片库</h2>
|
||||
<p>查看和下载生成的成片</p>
|
||||
</div>
|
||||
<h2>成片库</h2>
|
||||
<p>查看和管理已生成的视频成片</p>
|
||||
</div>
|
||||
|
||||
{videos.length === 0 ? (
|
||||
<div className="xx-empty-state">
|
||||
<div style={{ fontSize: '48px', marginBottom: '16px' }}>🎬</div>
|
||||
<p>还没有生成任何成片</p>
|
||||
<div style={{ fontSize: '64px', marginBottom: '16px' }}>🎬</div>
|
||||
<p>还没有生成的成片</p>
|
||||
<p style={{ fontSize: '14px', color: '#94a3b8' }}>
|
||||
前往剪辑计划开始生成您的第一个视频
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="xx-card">
|
||||
<h3>生成的成片</h3>
|
||||
<div className="xx-vertical-grid compact">
|
||||
{videos.map((video: any) => (
|
||||
<div key={video.id} className="xx-vertical-card">
|
||||
<div className="xx-vertical-thumb">
|
||||
<button className="mini-play">▶</button>
|
||||
</div>
|
||||
<div className="row">
|
||||
<b>{video.name}</b>
|
||||
<span className="pill ok">可发布</span>
|
||||
</div>
|
||||
<p>{video.duration || 0}s · 9:16 · {Math.round(video.file_size / 1024 / 1024)}MB</p>
|
||||
<div className="xx-vertical-grid">
|
||||
{videos.map((video, idx) => (
|
||||
<div key={video.id} className="xx-vertical-card">
|
||||
<div className={`xx-vertical-thumb ${colors[idx % 4]}`}>
|
||||
<div className="xx-play-btn">▶</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<span className={`xx-status-pill ${video.status || 'ready'}`}>
|
||||
{video.status === 'processing' ? '⏳ 生成中' : '✓ 就绪'}
|
||||
</span>
|
||||
<b>{video.name}</b>
|
||||
<div className="meta">
|
||||
<span className="duration">{video.duration || 0}s</span>
|
||||
<span className="size">{(video.file_size / 1024 / 1024).toFixed(1)} MB</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,49 +1,178 @@
|
||||
/* V21 标题库 */
|
||||
/* V21 标题库页面 */
|
||||
.xx-project-titles {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.xx-page-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 18px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.xx-page-head h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 850;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.xx-page-head p {
|
||||
font-size: 14px;
|
||||
color: var(--muted, #64748b);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* V21 按钮 */
|
||||
.xx-primary-btn {
|
||||
background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
|
||||
color: white !important;
|
||||
border: none !important;
|
||||
border-radius: 14px !important;
|
||||
padding: 12px 24px !important;
|
||||
font-weight: 850 !important;
|
||||
box-shadow: 0 14px 26px rgba(79,70,229,0.22) !important;
|
||||
transition: all 0.2s !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xx-primary-btn:hover {
|
||||
box-shadow: 0 18px 34px rgba(79,70,229,0.28) !important;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* V21 卡片 */
|
||||
.xx-card {
|
||||
background: rgba(255,255,255,0.94);
|
||||
border: 1px solid rgba(226,232,240,0.95);
|
||||
border-radius: 28px;
|
||||
box-shadow: 0 24px 70px rgba(15,23,42,0.09);
|
||||
padding: 28px;
|
||||
margin-bottom: 24px;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
.xx-card:hover {
|
||||
box-shadow: 0 26px 64px rgba(15,23,42,0.14);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 搜索栏 - V21 风格 */
|
||||
.xx-search-bar {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.xx-title-list {
|
||||
.xx-search-input {
|
||||
width: 100%;
|
||||
padding: 14px 20px;
|
||||
border: 2px solid var(--line, #e2e8f0);
|
||||
border-radius: 16px;
|
||||
font-size: 15px;
|
||||
background: white;
|
||||
transition: all 0.2s;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.xx-search-input:focus {
|
||||
border-color: var(--indigo, #4f46e5);
|
||||
box-shadow: 0 0 0 4px rgba(79,70,229,0.1);
|
||||
}
|
||||
|
||||
.xx-search-input::placeholder {
|
||||
color: var(--muted, #64748b);
|
||||
}
|
||||
|
||||
/* 标题列表网格 */
|
||||
.xx-title-grid {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* V21 标题卡片 */
|
||||
.xx-title-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
border: 1px solid #e2e8f0;
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--line, #e2e8f0);
|
||||
background: white;
|
||||
border-radius: 18px;
|
||||
padding: 13px;
|
||||
transition: all 0.2s;
|
||||
border-radius: 20px;
|
||||
transition: all 0.25s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xx-title-row:hover {
|
||||
border-color: #4f46e5;
|
||||
box-shadow: 0 8px 24px rgba(79, 70, 229, 0.08);
|
||||
border-color: var(--indigo, #4f46e5);
|
||||
box-shadow: 0 12px 36px rgba(79,70,229,0.1);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.xx-title-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.xx-title-row b {
|
||||
display: block;
|
||||
color: #0f172a;
|
||||
font-weight: 850;
|
||||
font-size: 16px;
|
||||
font-weight: 850;
|
||||
color: var(--slate, #0f172a);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.xx-title-row p {
|
||||
margin: 5px 0 0;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
color: var(--muted, #64748b);
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.xx-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.xx-action-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 12px;
|
||||
background: var(--bg, #f8fafc);
|
||||
border: 1px solid var(--line, #e2e8f0);
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 16px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.xx-action-btn:hover {
|
||||
background: var(--indigo-soft, #eef2ff);
|
||||
border-color: var(--indigo, #4f46e5);
|
||||
}
|
||||
|
||||
.xx-action-btn.copy:hover {
|
||||
color: var(--indigo, #4f46e5);
|
||||
}
|
||||
|
||||
.xx-action-btn.delete:hover {
|
||||
color: var(--red, #ef4444);
|
||||
background: #fee2e2;
|
||||
border-color: #fecaca;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.xx-empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 24px;
|
||||
color: var(--muted, #64748b);
|
||||
}
|
||||
|
||||
.xx-empty-state p {
|
||||
font-size: 16px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 标题库页面 - V21 完全对标
|
||||
* 标题库页面 - V21 UI
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -16,7 +16,7 @@ const ProjectTitles: React.FC = () => {
|
||||
const handleCreate = async () => {
|
||||
const values = await form.validateFields();
|
||||
try {
|
||||
message.success('标题创建成功');
|
||||
message.success('创建标题成功');
|
||||
setCreateOpen(false);
|
||||
form.resetFields();
|
||||
} catch (error: any) {
|
||||
@@ -34,35 +34,42 @@ const ProjectTitles: React.FC = () => {
|
||||
<div className="xx-page-head">
|
||||
<div>
|
||||
<h2>标题库</h2>
|
||||
<p>管理和搜索您的视频标题</p>
|
||||
<p>管理和创作吸引眼球的视频标题</p>
|
||||
</div>
|
||||
<button className="btn primary" onClick={() => setCreateOpen(true)}>
|
||||
<button className="xx-primary-btn" onClick={() => setCreateOpen(true)}>
|
||||
+ 创建标题
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="xx-card">
|
||||
{/* 搜索栏 */}
|
||||
<div className="xx-search-bar">
|
||||
<Input
|
||||
<input
|
||||
type="text"
|
||||
className="xx-search-input"
|
||||
placeholder="搜索标题..."
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
style={{ padding: '10px 14px', borderRadius: '12px' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{filtered.length === 0 ? (
|
||||
<div className="xx-empty-state">
|
||||
<p>{searchText ? '没有找到匹配的标题' : '还没有创建任何标题'}</p>
|
||||
<div style={{ fontSize: '48px', marginBottom: '16px' }}>✍️</div>
|
||||
<p>{searchText ? '没有找到匹配的标题' : '还没有创建标题'}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="xx-title-list">
|
||||
<div className="xx-title-grid">
|
||||
{filtered.map((title: any) => (
|
||||
<div key={title.id} className="xx-title-row">
|
||||
<div>
|
||||
<div className="xx-title-content">
|
||||
<b>{title.name}</b>
|
||||
<p>{title.content}</p>
|
||||
</div>
|
||||
<div className="xx-actions">
|
||||
<button className="xx-action-btn copy" title="复制">📋</button>
|
||||
<button className="xx-action-btn delete" title="删除">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -70,7 +77,7 @@ const ProjectTitles: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
title="创建新标题"
|
||||
title="创建标题"
|
||||
open={createOpen}
|
||||
okText="创建"
|
||||
cancelText="取消"
|
||||
@@ -80,10 +87,10 @@ const ProjectTitles: React.FC = () => {
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="标题"
|
||||
rules={[{ required: true, message: '请输入标题' }]}
|
||||
label="标题名称"
|
||||
rules={[{ required: true, message: '请输入标题名称' }]}
|
||||
>
|
||||
<Input placeholder="例如:产品介绍" />
|
||||
<Input placeholder="请输入标题名称" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="content"
|
||||
@@ -91,7 +98,7 @@ const ProjectTitles: React.FC = () => {
|
||||
rules={[{ required: true, message: '请输入标题内容' }]}
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder="例如:完整介绍产品功能和优势"
|
||||
placeholder="请输入标题内容"
|
||||
rows={4}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,105 +1,167 @@
|
||||
/* V21 配音库 */
|
||||
/* V21 配音库页面 */
|
||||
.xx-project-voices {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.xx-page-head {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.xx-page-head h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 850;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.xx-page-head p {
|
||||
font-size: 14px;
|
||||
color: var(--muted, #64748b);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* V21 卡片 */
|
||||
.xx-card {
|
||||
background: rgba(255,255,255,0.94);
|
||||
border: 1px solid rgba(226,232,240,0.95);
|
||||
border-radius: 28px;
|
||||
box-shadow: 0 24px 70px rgba(15,23,42,0.09);
|
||||
padding: 28px;
|
||||
margin-bottom: 24px;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
.xx-card h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 850;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
/* 配音网格 - 双列 */
|
||||
.xx-voice-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.xx-voice-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.xx-voice-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.xx-voice {
|
||||
text-align: center;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 18px;
|
||||
/* V21 配音卡片 */
|
||||
.xx-voice-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
border: 2px solid var(--line, #e2e8f0);
|
||||
border-radius: 20px;
|
||||
background: white;
|
||||
padding: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: all 0.25s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.xx-voice:hover {
|
||||
border-color: #4f46e5;
|
||||
.xx-voice-card:hover {
|
||||
border-color: var(--indigo, #4f46e5);
|
||||
box-shadow: 0 12px 36px rgba(79,70,229,0.12);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.xx-voice.selected {
|
||||
border-color: #4f46e5;
|
||||
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.14);
|
||||
/* 选中态 */
|
||||
.xx-voice-card.selected {
|
||||
border-color: var(--indigo, #4f46e5);
|
||||
background: var(--indigo-soft, #eef2ff);
|
||||
box-shadow: 0 0 0 4px rgba(79,70,229,0.1);
|
||||
}
|
||||
|
||||
.xx-voice.selected::after {
|
||||
/* 选中标记 */
|
||||
.xx-voice-card.selected::after {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
right: -7px;
|
||||
top: -7px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
right: -8px;
|
||||
top: -8px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: #4f46e5;
|
||||
background: var(--indigo, #4f46e5);
|
||||
color: white;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-weight: 900;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.voice-icon {
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
margin: 0 auto 9px;
|
||||
/* 波形图占位 */
|
||||
.xx-voice-waveform {
|
||||
width: 80px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #6366f1, #4f46e5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.xx-voice-waveform span {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* 音色信息 */
|
||||
.xx-voice-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.xx-voice-info h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 850;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
|
||||
.xx-voice-info p {
|
||||
font-size: 13px;
|
||||
color: var(--muted, #64748b);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 播放按钮 */
|
||||
.xx-voice-play {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
background: rgba(79,70,229,0.1);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 18px;
|
||||
color: var(--indigo, #4f46e5);
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.xx-voice-play:hover {
|
||||
background: var(--indigo, #4f46e5);
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
background: linear-gradient(135deg, #4f46e5, #6366f1);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.xx-voice div {
|
||||
font-weight: 850;
|
||||
font-size: 14px;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.xx-voice small {
|
||||
display: block;
|
||||
color: #64748b;
|
||||
margin-top: 4px;
|
||||
/* 标签 */
|
||||
.xx-voice-tag {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.xx-voice-usage-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.xx-voice-usage-row {
|
||||
border: 1px solid #e2e8f0;
|
||||
background: white;
|
||||
border-radius: 18px;
|
||||
padding: 13px;
|
||||
}
|
||||
|
||||
.xx-voice-usage-row b {
|
||||
display: block;
|
||||
color: #0f172a;
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.xx-voice-usage-row p {
|
||||
margin: 5px 0 0;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: var(--indigo-soft, #eef2ff);
|
||||
color: var(--indigo-dark, #4338ca);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
/**
|
||||
* 配音库页面 - V21 完全对标
|
||||
* 配音库页面 - V21 UI
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import './ProjectVoices.css';
|
||||
|
||||
const VOICE_OPTIONS = [
|
||||
{ id: 'female-1', name: '女声1', icon: '👩' },
|
||||
{ id: 'female-2', name: '女声2', icon: '👩' },
|
||||
{ id: 'male-1', name: '男声1', icon: '👨' },
|
||||
{ id: 'male-2', name: '男声2', icon: '👨' },
|
||||
{ id: 'neutral-1', name: '中性声1', icon: '🎤' },
|
||||
{ id: 'neutral-2', name: '中性声2', icon: '🎤' },
|
||||
{ id: 'female-youth', name: '元气少女', desc: '年轻女性,活泼开朗', tag: '热门' },
|
||||
{ id: 'female-mature', name: '知性姐姐', desc: '成熟女性,温柔亲切', tag: '推荐' },
|
||||
{ id: 'male-young', name: '阳光男孩', desc: '年轻男性,清新自然', tag: '热门' },
|
||||
{ id: 'male-mature', name: '磁性大叔', desc: '成熟男性,深沉有力', tag: '' },
|
||||
{ id: 'neutral-youth', name: '活力童声', desc: '童声配音,活泼可爱', tag: '新品' },
|
||||
{ id: 'neutral-mature', name: '专业播音', desc: '标准播音腔,端庄大气', tag: '' },
|
||||
];
|
||||
|
||||
const ProjectVoices: React.FC = () => {
|
||||
@@ -21,24 +21,28 @@ const ProjectVoices: React.FC = () => {
|
||||
return (
|
||||
<div className="xx-project-voices">
|
||||
<div className="xx-page-head">
|
||||
<div>
|
||||
<h2>配音库</h2>
|
||||
<p>选择和管理配音</p>
|
||||
</div>
|
||||
<h2>配音库</h2>
|
||||
<p>选择适合您内容的配音音色</p>
|
||||
</div>
|
||||
|
||||
<div className="xx-card">
|
||||
<h3>可用配音</h3>
|
||||
<h3>🎙️ 选择音色</h3>
|
||||
<div className="xx-voice-grid">
|
||||
{VOICE_OPTIONS.map((voice) => (
|
||||
<div
|
||||
key={voice.id}
|
||||
className={`xx-voice ${selectedVoice === voice.id ? 'selected' : ''}`}
|
||||
className={`xx-voice-card ${selectedVoice === voice.id ? 'selected' : ''}`}
|
||||
onClick={() => setSelectedVoice(voice.id)}
|
||||
>
|
||||
<div className="voice-icon">{voice.icon}</div>
|
||||
<div>{voice.name}</div>
|
||||
<small>点击预览</small>
|
||||
<div className="xx-voice-waveform">
|
||||
<span>🔊</span>
|
||||
</div>
|
||||
<div className="xx-voice-info">
|
||||
<h4>{voice.name}</h4>
|
||||
<p>{voice.desc}</p>
|
||||
{voice.tag && <span className="xx-voice-tag">{voice.tag}</span>}
|
||||
</div>
|
||||
<button className="xx-voice-play">▶</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,110 @@
|
||||
/* V21 工作空间列表 */
|
||||
/* V21 工作空间列表页面 */
|
||||
.xx-workspace-list {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Hero 区域 - V21 设计 */
|
||||
.xx-hero {
|
||||
text-align: center;
|
||||
padding: 60px 24px 80px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.xx-hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: radial-gradient(circle, rgba(79,70,229,0.15), transparent 70%);
|
||||
border-radius: 50%;
|
||||
filter: blur(40px);
|
||||
}
|
||||
|
||||
.xx-hero h1 {
|
||||
font-size: 42px;
|
||||
font-weight: 900;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 0 0 16px;
|
||||
letter-spacing: -0.02em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.xx-hero p {
|
||||
font-size: 18px;
|
||||
color: var(--muted, #64748b);
|
||||
margin: 0 0 32px;
|
||||
max-width: 560px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 功能卡片网格 - V21 */
|
||||
.xx-features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.xx-features-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.xx-features-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.xx-feature-card {
|
||||
background: rgba(255,255,255,0.94);
|
||||
border: 1px solid rgba(226,232,240,0.95);
|
||||
border-radius: 24px;
|
||||
padding: 28px;
|
||||
text-align: center;
|
||||
transition: all 0.25s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xx-feature-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 26px 64px rgba(15,23,42,0.14);
|
||||
border-color: var(--indigo, #4f46e5);
|
||||
}
|
||||
|
||||
.xx-feature-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
margin: 0 auto 16px;
|
||||
background: linear-gradient(135deg, #6366f1, #4f46e5);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.xx-feature-card h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 850;
|
||||
color: var(--slate, #0f172a);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.xx-feature-card p {
|
||||
font-size: 13px;
|
||||
color: var(--muted, #64748b);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.xx-page-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -13,19 +116,19 @@
|
||||
.xx-page-head h2 {
|
||||
font-size: 28px;
|
||||
margin: 0 0 6px;
|
||||
color: #0f172a;
|
||||
color: var(--slate, #0f172a);
|
||||
}
|
||||
|
||||
.xx-page-head p {
|
||||
margin: 0;
|
||||
color: #64748b;
|
||||
color: var(--muted, #64748b);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.xx-grid4 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
@@ -47,23 +150,24 @@
|
||||
}
|
||||
|
||||
.xx-workspace-card {
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid var(--line, #e2e8f0);
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
border-radius: 24px;
|
||||
padding: 18px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
.xx-workspace-card:hover {
|
||||
border-color: #4f46e5;
|
||||
box-shadow: 0 12px 36px rgba(79, 70, 229, 0.12);
|
||||
border-color: var(--indigo, #4f46e5);
|
||||
box-shadow: 0 26px 64px rgba(15,23,42,0.14);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.xx-workspace-name {
|
||||
font-size: 16px;
|
||||
font-weight: 850;
|
||||
color: #0f172a;
|
||||
color: var(--slate, #0f172a);
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -74,10 +178,10 @@
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: 850;
|
||||
padding: 4px 8px;
|
||||
border-radius: 8px;
|
||||
background: #eef2ff;
|
||||
color: #4338ca;
|
||||
padding: 4px 10px;
|
||||
border-radius: 10px;
|
||||
background: var(--indigo-soft, #eef2ff);
|
||||
color: var(--indigo-dark, #4338ca);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@@ -89,7 +193,7 @@
|
||||
.xx-empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 24px;
|
||||
color: #64748b;
|
||||
color: var(--muted, #64748b);
|
||||
}
|
||||
|
||||
.xx-empty-state p {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 工作空间列表页面 - V21 严格对标
|
||||
* 工作空间列表页面 - V21 UI
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Button, Modal, Form, Input, message } from 'antd';
|
||||
@@ -20,27 +20,53 @@ const WorkspaceList: React.FC = () => {
|
||||
const values = await form.validateFields();
|
||||
try {
|
||||
const workspace = await createWorkspaceMutation.mutateAsync({ name: values.name });
|
||||
message.success('工作空间创建成功');
|
||||
message.success('创建工作空间成功');
|
||||
setCreateOpen(false);
|
||||
form.resetFields();
|
||||
navigate(`/workspaces/${workspace.id}`);
|
||||
} catch (createError: any) {
|
||||
message.error(createError.response?.data?.detail || '工作空间创建失败');
|
||||
message.error(createError.response?.data?.detail || '创建失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 功能卡片数据
|
||||
const features = [
|
||||
{ id: 'assets', icon: '🎬', title: '素材管理', desc: '上传管理视频素材' },
|
||||
{ id: 'titles', icon: '✍️', title: '标题库', desc: '创作吸引眼球的标题' },
|
||||
{ id: 'voices', icon: '🎙️', title: '配音库', desc: '选择优质配音音色' },
|
||||
{ id: 'generation', icon: '✨', title: '剪辑计划', desc: 'AI智能生成成片' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="xx-workspace-list">
|
||||
<div className="xx-page-head">
|
||||
<div>
|
||||
<h2>我的工作空间</h2>
|
||||
<p>创建和管理您的视频剪辑项目</p>
|
||||
</div>
|
||||
{/* Hero 区域 - V21 设计 */}
|
||||
<div className="xx-hero">
|
||||
<h1>小虾剪辑 SaaS</h1>
|
||||
<p>专业的AI视频剪辑平台,一站式管理素材、标题、配音,轻松创作优质内容</p>
|
||||
<Button type="primary" size="large" onClick={openCreateModal}>
|
||||
+ 创建工作空间
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 功能卡片 - V21 设计 */}
|
||||
<div className="xx-features-grid">
|
||||
{features.map((feature) => (
|
||||
<div key={feature.id} className="xx-feature-card">
|
||||
<div className="xx-feature-icon">{feature.icon}</div>
|
||||
<h3>{feature.title}</h3>
|
||||
<p>{feature.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 工作空间列表 */}
|
||||
<div className="xx-page-head">
|
||||
<div>
|
||||
<h2>我的工作空间</h2>
|
||||
<p>选择或创建一个工作空间开始创作</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="xx-grid4">
|
||||
{workspaces?.map((workspace) => (
|
||||
<div
|
||||
@@ -61,10 +87,10 @@ const WorkspaceList: React.FC = () => {
|
||||
|
||||
{!isLoading && workspaces?.length === 0 && (
|
||||
<div className="xx-empty-state">
|
||||
<div style={{ fontSize: '48px', marginBottom: '16px' }}>📦</div>
|
||||
<div style={{ fontSize: '48px', marginBottom: '16px' }}>🚀</div>
|
||||
<p>还没有工作空间</p>
|
||||
<Button type="primary" onClick={openCreateModal}>
|
||||
创建第一个工作空间
|
||||
+ 创建你的第一个工作空间
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -84,10 +110,10 @@ const WorkspaceList: React.FC = () => {
|
||||
label="工作空间名称"
|
||||
rules={[
|
||||
{ required: true, message: '请输入工作空间名称' },
|
||||
{ max: 50, message: '最多 50 个字符' },
|
||||
{ max: 50, message: '名称不能超过50个字符' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="例如:我的剪辑工作室" autoFocus />
|
||||
<Input placeholder="请输入工作空间名称" autoFocus />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
+125
-28
@@ -1,29 +1,43 @@
|
||||
/**
|
||||
* 全局样式变量
|
||||
* V21 设计系统 - 全局样式
|
||||
*/
|
||||
:root {
|
||||
/* 主色调 */
|
||||
--primary-color: #1890ff;
|
||||
--primary-hover: #40a9ff;
|
||||
--primary-active: #096dd9;
|
||||
--primary-color: #4f46e5;
|
||||
--primary-hover: #6366f1;
|
||||
--primary-active: #4338ca;
|
||||
--primary-soft: #eef2ff;
|
||||
|
||||
/* 状态色 */
|
||||
--success-color: #52c41a;
|
||||
--warning-color: #faad14;
|
||||
--error-color: #ff4d4f;
|
||||
--info-color: #1890ff;
|
||||
--success-color: #10b981;
|
||||
--warning-color: #f59e0b;
|
||||
--error-color: #ef4444;
|
||||
--info-color: #4f46e5;
|
||||
|
||||
/* 文字颜色 */
|
||||
--text-primary: #262626;
|
||||
--text-secondary: #595959;
|
||||
/* 文字色 */
|
||||
--text-primary: #0f172a;
|
||||
--text-secondary: #64748b;
|
||||
--text-disabled: #bfbfbf;
|
||||
|
||||
/* 背景色 */
|
||||
--bg-primary: #ffffff;
|
||||
--bg-primary: #f8fafc;
|
||||
--bg-secondary: #fafafa;
|
||||
--bg-tertiary: #f5f5f5;
|
||||
|
||||
/* 边框和分割线 */
|
||||
/* V21 设计令牌 */
|
||||
--indigo: #4f46e5;
|
||||
--indigo-dark: #4338ca;
|
||||
--indigo-soft: #eef2ff;
|
||||
--gradient-primary: linear-gradient(135deg, #6366f1, #4f46e5);
|
||||
--slate: #0f172a;
|
||||
--muted: #64748b;
|
||||
--line: #e2e8f0;
|
||||
--bg: #f8fafc;
|
||||
--green: #10b981;
|
||||
--amber: #f59e0b;
|
||||
--red: #ef4444;
|
||||
|
||||
/* 边框色 */
|
||||
--border-color: #d9d9d9;
|
||||
--divider-color: #f0f0f0;
|
||||
|
||||
@@ -34,18 +48,102 @@
|
||||
--space-lg: 24px;
|
||||
--space-xl: 32px;
|
||||
|
||||
/* 圆角 */
|
||||
--radius-sm: 2px;
|
||||
--radius-md: 4px;
|
||||
--radius-lg: 8px;
|
||||
/* 圆角 - V21 大圆角 */
|
||||
--radius-sm: 12px;
|
||||
--radius-md: 16px;
|
||||
--radius-lg: 22px;
|
||||
--radius-xl: 28px;
|
||||
|
||||
/* 阴影 */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.08);
|
||||
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.12);
|
||||
--shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.16);
|
||||
/* 阴影 - V21 柔和大阴影 */
|
||||
--shadow-sm: 0 10px 30px rgba(15,23,42,0.06);
|
||||
--shadow-md: 0 20px 58px rgba(15,23,42,0.10);
|
||||
--shadow-lg: 0 24px 70px rgba(15,23,42,0.09);
|
||||
--shadow-hover: 0 26px 64px rgba(15,23,42,0.14);
|
||||
}
|
||||
|
||||
/* 全局样式重置 */
|
||||
/**
|
||||
* V21 按钮样式
|
||||
*/
|
||||
.btn.primary,
|
||||
.ant-btn-primary {
|
||||
background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
|
||||
color: white !important;
|
||||
border-radius: 14px !important;
|
||||
font-weight: 850 !important;
|
||||
box-shadow: 0 14px 26px rgba(79,70,229,0.22) !important;
|
||||
border: none !important;
|
||||
padding: 12px 22px !important;
|
||||
transition: all 0.2s !important;
|
||||
}
|
||||
|
||||
.btn.primary:hover,
|
||||
.ant-btn-primary:hover {
|
||||
box-shadow: 0 18px 34px rgba(79,70,229,0.28) !important;
|
||||
transform: translateY(-1px);
|
||||
background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
|
||||
}
|
||||
|
||||
.btn.ghost,
|
||||
.ant-btn-default {
|
||||
background: white !important;
|
||||
border: 1px solid #dbe3ef !important;
|
||||
border-radius: 14px !important;
|
||||
font-weight: 850 !important;
|
||||
padding: 12px 22px !important;
|
||||
transition: all 0.2s !important;
|
||||
}
|
||||
|
||||
.btn.ghost:hover,
|
||||
.ant-btn-default:hover {
|
||||
border-color: #4f46e5 !important;
|
||||
color: #4f46e5 !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* V21 卡片样式
|
||||
*/
|
||||
.xx-card {
|
||||
background: rgba(255,255,255,0.94);
|
||||
border: 1px solid rgba(226,232,240,0.95);
|
||||
border-radius: 28px;
|
||||
box-shadow: 0 24px 70px rgba(15,23,42,0.09);
|
||||
padding: 28px;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
.xx-card:hover {
|
||||
box-shadow: 0 26px 64px rgba(15,23,42,0.14);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/**
|
||||
* V21 背景装饰
|
||||
*/
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background:
|
||||
radial-gradient(circle at 18% 20%, rgba(79,70,229,0.11), transparent 30%),
|
||||
radial-gradient(circle at 86% 14%, rgba(16,185,129,0.08), transparent 28%),
|
||||
radial-gradient(circle at 48% 80%, rgba(99,102,241,0.08), transparent 34%),
|
||||
#f8fafc;
|
||||
}
|
||||
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background-image: radial-gradient(circle, rgba(79,70,229,0.1) 1px, transparent 1px);
|
||||
background-size: 38px 38px;
|
||||
opacity: 0.32;
|
||||
}
|
||||
|
||||
/* 全局重置 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -53,13 +151,12 @@
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',
|
||||
'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
/* 滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
@@ -74,7 +171,7 @@ body {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* 通用工具类 */
|
||||
/* 工具类 */
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -107,7 +204,7 @@ body {
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
/* 卡片hover效果 */
|
||||
/* 卡片悬浮效果 */
|
||||
.card-hover {
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
@@ -118,7 +215,7 @@ body {
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
/* 选中的套餐卡片 */
|
||||
/* 选中态 */
|
||||
.selected-plan {
|
||||
border: 2px solid var(--primary-color) !important;
|
||||
box-shadow: var(--shadow-md);
|
||||
|
||||
Reference in New Issue
Block a user