积分充值修改
This commit is contained in:
@@ -1 +1,5 @@
|
||||
{}
|
||||
{
|
||||
"src/pages/CreativePlazaPage.tsx": {
|
||||
"description": "创意广场"
|
||||
}
|
||||
}
|
||||
@@ -131,6 +131,17 @@
|
||||
box-shadow: 0 8px 30px rgba(99, 102, 241, 0.5);
|
||||
}
|
||||
|
||||
.recharge-drawer .ant-drawer-content-wrapper {
|
||||
border-top-left-radius: 20px !important;
|
||||
border-top-right-radius: 20px !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.recharge-drawer .ant-drawer-content {
|
||||
border-top-left-radius: 20px !important;
|
||||
border-top-right-radius: 20px !important;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.desktop-sidebar {
|
||||
display: none !important;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState, useCallback, useRef } from 'react';
|
||||
import { Layout, Avatar, Dropdown, Space, Modal, Form, Input, message, Tooltip, Tag, Button, Typography, Radio, Drawer, Tabs } from 'antd';
|
||||
import { Layout, Avatar, Dropdown, Space, Modal, Form, Input, message, Tooltip, Tag, Button, Typography, Radio, Drawer, Tabs, Collapse } from 'antd';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
PlayCircleOutlined,
|
||||
@@ -20,7 +20,9 @@ import {
|
||||
StarFilled,
|
||||
FireFilled,
|
||||
CrownFilled,
|
||||
CrownOutlined,
|
||||
BankFilled,
|
||||
CheckCircleFilled,
|
||||
CloseOutlined,
|
||||
WechatOutlined,
|
||||
AlipayCircleOutlined,
|
||||
@@ -310,6 +312,72 @@ const GRADIENTS = [
|
||||
{ gradient: 'linear-gradient(135deg, #5a67d8, #434190)', shadow: 'rgba(90,103,216,0.25)', icon: <BankFilled /> },
|
||||
];
|
||||
|
||||
const MEMBERSHIP_GRADIENTS = [
|
||||
{ gradient: 'linear-gradient(135deg, #94a3b8, #64748b)', shadow: 'rgba(148,163,184,0.25)', icon: <StarFilled /> },
|
||||
{ gradient: 'linear-gradient(135deg, #c9a96e, #a67c52)', shadow: 'rgba(201,169,110,0.25)', icon: <StarFilled /> },
|
||||
{ gradient: 'linear-gradient(135deg, #718096, #4a5568)', shadow: 'rgba(113,128,150,0.25)', icon: <CrownFilled /> },
|
||||
{ gradient: 'linear-gradient(135deg, #5a67d8, #434190)', shadow: 'rgba(90,103,216,0.25)', icon: <BankFilled /> },
|
||||
];
|
||||
|
||||
const MEMBERSHIP_FEATURES = [
|
||||
'每日赠送积分 当日清零',
|
||||
'可充值更多积分',
|
||||
'基础加速通道',
|
||||
'作品去除水印',
|
||||
'可使用网页版和APP上全部AI功能',
|
||||
'高分辨率',
|
||||
'批量创作 更多同时生成任务',
|
||||
];
|
||||
|
||||
const MEMBERSHIP_PLANS = [
|
||||
{
|
||||
tier: '基础会员',
|
||||
desc: '开启创作之旅',
|
||||
monthlyPrice: 79,
|
||||
monthlyCredits: 1000,
|
||||
onlyMonthly: true,
|
||||
features: MEMBERSHIP_FEATURES.map((f) => f.replace('基础加速通道', '基础加速通道')),
|
||||
speedLabel: '基础加速通道',
|
||||
},
|
||||
{
|
||||
tier: '标准会员',
|
||||
desc: '畅享基础创作权益',
|
||||
monthlyPrice: 154,
|
||||
monthlyCredits: 2000,
|
||||
quarterlyPrice: 1029,
|
||||
quarterlyCredits: 5000,
|
||||
yearlyPrice: 7308,
|
||||
yearlyCredits: 10000,
|
||||
features: MEMBERSHIP_FEATURES,
|
||||
speedLabel: '标准加速通道',
|
||||
},
|
||||
{
|
||||
tier: '高级会员',
|
||||
desc: '解锁高级创作权益',
|
||||
monthlyPrice: 221,
|
||||
monthlyCredits: 3000,
|
||||
quarterlyPrice: 1197,
|
||||
quarterlyCredits: 6000,
|
||||
yearlyPrice: 10710,
|
||||
yearlyCredits: 15000,
|
||||
features: MEMBERSHIP_FEATURES,
|
||||
speedLabel: '高级加速通道',
|
||||
},
|
||||
{
|
||||
tier: '超级会员',
|
||||
desc: '释放无限创作生产力',
|
||||
monthlyPrice: 280,
|
||||
monthlyCredits: 4000,
|
||||
quarterlyPrice: 1367,
|
||||
quarterlyCredits: 7000,
|
||||
yearlyPrice: 13440,
|
||||
yearlyCredits: 20000,
|
||||
features: MEMBERSHIP_FEATURES,
|
||||
speedLabel: '高级加速通道',
|
||||
hot: true,
|
||||
},
|
||||
];
|
||||
|
||||
const AppLayout: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
@@ -322,6 +390,9 @@ const AppLayout: React.FC = () => {
|
||||
const [selectedPlan, setSelectedPlan] = useState<number | null>(null);
|
||||
const [menuItems, setMenuItems] = useState<MenuConfig[]>([]);
|
||||
const [rechargeOptions, setRechargeOptions] = useState<any[]>([]);
|
||||
const [creditsModalOpen, setCreditsModalOpen] = useState(false);
|
||||
const [selectedTierIndex, setSelectedTierIndex] = useState<number | null>(null);
|
||||
const [selectedPeriod, setSelectedPeriod] = useState<'monthly' | 'quarterly' | 'yearly'>('monthly');
|
||||
const [unreadCount, setUnreadCount] = useState(0);
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const [contactHovered, setContactHovered] = useState(false);
|
||||
@@ -1357,72 +1428,274 @@ const AppLayout: React.FC = () => {
|
||||
</Tabs>
|
||||
</Modal>
|
||||
|
||||
<Modal title={<Space><GiftOutlined />积分充值</Space>} open={rechargeModalOpen}
|
||||
onCancel={() => { setRechargeModalOpen(false); setSelectedPlan(null); }}
|
||||
width={680}
|
||||
className="recharge-modal"
|
||||
<Drawer
|
||||
title={null}
|
||||
placement="bottom"
|
||||
open={rechargeModalOpen}
|
||||
onClose={() => { setRechargeModalOpen(false); setSelectedPlan(null); setSelectedTierIndex(null); }}
|
||||
height="95vh"
|
||||
className="recharge-drawer"
|
||||
styles={{
|
||||
header: { display: 'none' },
|
||||
body: { padding: '20px 24px 0', overflowY: 'auto', position: 'relative' },
|
||||
}}
|
||||
footer={
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', padding: '12px 0 0', borderTop: '1px solid #f0f0f0' }}>
|
||||
<Button size="large" onClick={() => { setRechargeModalOpen(false); setSelectedPlan(null); }} style={{ borderRadius: 10, marginRight: 12 }}>取消</Button>
|
||||
<Button type="primary" size="large" disabled={!selectedPlan || (!enabledMethods.alipay && !enabledMethods.wechat)} loading={paying}
|
||||
onClick={async () => {
|
||||
const plan = rechargeOptions.find((opt: any) => opt.id === selectedPlan);
|
||||
if (!plan) return;
|
||||
const totalCredits = (plan.credits || 0) + (plan.bonus_credits || plan.bonusCredits || 0);
|
||||
try {
|
||||
setPaying(true);
|
||||
const order = await createRechargeOrder(plan.id, paymentMethod);
|
||||
const qrCode = order.qrUrl || order.codeUrl || order.qr_code || order.code_url;
|
||||
if ((order.paymentMethod === 'alipay' || order.paymentMethod === 'wechat') && qrCode) {
|
||||
const paymentInfo = {
|
||||
price: plan.price,
|
||||
credits: totalCredits,
|
||||
qrCode: qrCode,
|
||||
method: order.paymentMethod,
|
||||
};
|
||||
setCurrentPaymentInfo(paymentInfo);
|
||||
setRechargeModalOpen(false);
|
||||
setQrCodeModalOpen(true);
|
||||
currentOrderNoRef.current = order.orderNo;
|
||||
|
||||
localStorage.setItem(PENDING_ORDER_KEY, JSON.stringify({
|
||||
orderNo: order.orderNo,
|
||||
price: plan.price,
|
||||
credits: totalCredits,
|
||||
qrCode: qrCode,
|
||||
method: order.paymentMethod,
|
||||
createdAt: order.createdAt || new Date().toISOString(),
|
||||
timeoutSeconds: 180,
|
||||
}));
|
||||
|
||||
startPolling(order.orderNo);
|
||||
} else {
|
||||
message.success('充值成功!积分已到账');
|
||||
useAuthStore.getState().refreshUser();
|
||||
setRechargeModalOpen(false);
|
||||
setSelectedPlan(null);
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '12px 0', borderTop: '1px solid #f0f0f0' }}>
|
||||
<Space>
|
||||
<WalletOutlined style={{ color: '#6366f1' }} />
|
||||
<Typography.Text style={{ color: '#64748b' }}>当前积分余额</Typography.Text>
|
||||
<Typography.Text strong style={{ color: '#6366f1', fontSize: 18 }}>{user?.credits ?? 0}</Typography.Text>
|
||||
</Space>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<Button size="large" onClick={() => { setRechargeModalOpen(false); setSelectedPlan(null); setSelectedTierIndex(null); }} style={{ borderRadius: 10 }}>取消</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
disabled={!selectedPlan && selectedTierIndex === null}
|
||||
loading={paying}
|
||||
onClick={async () => {
|
||||
if (selectedPlan) {
|
||||
const plan = rechargeOptions.find((opt: any) => opt.id === selectedPlan);
|
||||
if (!plan) return;
|
||||
const totalCredits = (plan.credits || 0) + (plan.bonus_credits || plan.bonusCredits || 0);
|
||||
try {
|
||||
setPaying(true);
|
||||
const order = await createRechargeOrder(plan.id, paymentMethod);
|
||||
const qrCode = order.qrUrl || order.codeUrl || order.qr_code || order.code_url;
|
||||
if ((order.paymentMethod === 'alipay' || order.paymentMethod === 'wechat') && qrCode) {
|
||||
const paymentInfo = {
|
||||
price: plan.price,
|
||||
credits: totalCredits,
|
||||
qrCode: qrCode,
|
||||
method: order.paymentMethod,
|
||||
};
|
||||
setCurrentPaymentInfo(paymentInfo);
|
||||
setRechargeModalOpen(false);
|
||||
setQrCodeModalOpen(true);
|
||||
currentOrderNoRef.current = order.orderNo;
|
||||
localStorage.setItem(PENDING_ORDER_KEY, JSON.stringify({
|
||||
orderNo: order.orderNo,
|
||||
price: plan.price,
|
||||
credits: totalCredits,
|
||||
qrCode: qrCode,
|
||||
method: order.paymentMethod,
|
||||
createdAt: order.createdAt || new Date().toISOString(),
|
||||
timeoutSeconds: 180,
|
||||
}));
|
||||
startPolling(order.orderNo);
|
||||
} else {
|
||||
message.success('充值成功!积分已到账');
|
||||
useAuthStore.getState().refreshUser();
|
||||
setRechargeModalOpen(false);
|
||||
setSelectedPlan(null);
|
||||
}
|
||||
} catch (err: any) {
|
||||
message.error(err?.message || '创建订单失败,请重试');
|
||||
} finally {
|
||||
setPaying(false);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
message.error(err?.message || '创建订单失败,请重试');
|
||||
} finally {
|
||||
setPaying(false);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
borderRadius: 10, fontWeight: 600,
|
||||
background: selectedPlan ? 'linear-gradient(135deg, #6366f1, #8b5cf6)' : '#d1d5db',
|
||||
border: 'none', boxShadow: selectedPlan ? '0 8px 24px rgba(99,102,241,0.3)' : 'none',
|
||||
}}>
|
||||
确认充值
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<div style={{ position: 'absolute', top: 8, right: 16, zIndex: 10 }}>
|
||||
<CloseOutlined
|
||||
style={{ fontSize: 18, color: '#94a3b8', cursor: 'pointer', padding: 8, borderRadius: '50%', transition: 'color 0.2s' }}
|
||||
onClick={() => { setRechargeModalOpen(false); setSelectedPlan(null); setSelectedTierIndex(null); }}
|
||||
onMouseEnter={(e) => { (e.target as HTMLElement).style.color = '#6366f1'; }}
|
||||
onMouseLeave={(e) => { (e.target as HTMLElement).style.color = '#94a3b8'; }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ textAlign: 'center', marginBottom: 20, marginTop: 16 }}>
|
||||
<Typography.Title level={4} style={{ margin: 0, fontSize: 20, fontWeight: 700, color: '#1a1a2e' }}>
|
||||
选择合适的计划,助力业务提升
|
||||
</Typography.Title>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 12, marginTop: 12 }}>
|
||||
<div style={{
|
||||
display: 'inline-flex', background: '#f5f5fa', borderRadius: 10, padding: 4,
|
||||
gap: 4,
|
||||
}}>
|
||||
{[
|
||||
{ key: 'yearly', label: '连续包年 8折' },
|
||||
{ key: 'quarterly', label: '连续包季 8折' },
|
||||
{ key: 'monthly', label: '连续包月' },
|
||||
].map((p) => (
|
||||
<div
|
||||
key={p.key}
|
||||
onClick={() => {
|
||||
const period = p.key as 'monthly' | 'quarterly' | 'yearly';
|
||||
setSelectedPeriod(period);
|
||||
if (period !== 'monthly' && selectedTierIndex !== null && MEMBERSHIP_PLANS[selectedTierIndex]?.onlyMonthly) {
|
||||
setSelectedTierIndex(null);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
padding: '6px 16px', borderRadius: 8, cursor: 'pointer', fontSize: 13, fontWeight: 500,
|
||||
background: selectedPeriod === p.key ? 'linear-gradient(135deg, #6366f1, #8b5cf6)' : 'transparent',
|
||||
color: selectedPeriod === p.key ? '#fff' : '#64748b',
|
||||
transition: 'all 0.2s', whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{p.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* <Tag
|
||||
style={{
|
||||
borderRadius: 8, fontSize: 12, padding: '4px 12px', cursor: 'pointer',
|
||||
background: '#f0f0ff', color: '#6366f1', border: '1px solid #e0e0ff',
|
||||
}}
|
||||
>
|
||||
API服务
|
||||
</Tag> */}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ position: 'absolute', top: 12, right: 48, zIndex: 10 }}>
|
||||
<div
|
||||
onClick={() => setCreditsModalOpen(true)}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer',
|
||||
padding: '6px 14px', borderRadius: 20,
|
||||
background: 'linear-gradient(135deg, rgba(99,102,241,0.08), rgba(139,92,246,0.08))',
|
||||
border: '1px solid rgba(99,102,241,0.2)',
|
||||
transition: 'all 0.2s',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = 'linear-gradient(135deg, rgba(99,102,241,0.15), rgba(139,92,246,0.15))';
|
||||
e.currentTarget.style.borderColor = 'rgba(99,102,241,0.4)';
|
||||
}}
|
||||
style={{
|
||||
borderRadius: 10, fontWeight: 600,
|
||||
background: selectedPlan ? 'linear-gradient(135deg, #6366f1, #8b5cf6)' : '#d1d5db',
|
||||
border: 'none', boxShadow: selectedPlan ? '0 8px 24px rgba(99,102,241,0.3)' : 'none',
|
||||
}}>
|
||||
确认充值
|
||||
</Button>
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = 'linear-gradient(135deg, rgba(99,102,241,0.08), rgba(139,92,246,0.08))';
|
||||
e.currentTarget.style.borderColor = 'rgba(99,102,241,0.2)';
|
||||
}}
|
||||
>
|
||||
<WalletOutlined style={{ color: '#6366f1', fontSize: 14 }} />
|
||||
<span style={{ fontSize: 13, fontWeight: 600, color: '#6366f1' }}>积分充值</span>
|
||||
{/* <Tag color="purple" style={{ marginLeft: 2, borderRadius: 6, fontSize: 10, margin: 0, padding: '0 6px', lineHeight: '18px' }}>8折</Tag> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 12, marginBottom: 20 }}>
|
||||
{MEMBERSHIP_PLANS
|
||||
.map((plan, idx) => ({ plan, idx }))
|
||||
.filter(({ plan }) => selectedPeriod === 'monthly' || !plan.onlyMonthly)
|
||||
.map(({ plan, idx }) => {
|
||||
const g = MEMBERSHIP_GRADIENTS[idx % MEMBERSHIP_GRADIENTS.length];
|
||||
const price = selectedPeriod === 'monthly' ? plan.monthlyPrice
|
||||
: selectedPeriod === 'quarterly' ? plan.quarterlyPrice : plan.yearlyPrice;
|
||||
const credits = selectedPeriod === 'monthly' ? plan.monthlyCredits
|
||||
: selectedPeriod === 'quarterly' ? plan.quarterlyCredits : plan.yearlyCredits;
|
||||
const creditsPer10 = Math.round((credits / price) * 10);
|
||||
const isSelected = selectedTierIndex === idx;
|
||||
const isHot = plan.hot;
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
onClick={() => setSelectedTierIndex(idx)}
|
||||
style={{
|
||||
flex: 1,
|
||||
borderRadius: 16,
|
||||
padding: '20px 16px 16px',
|
||||
background: isHot
|
||||
? 'linear-gradient(180deg, #f0edff 0%, #fafbff 30%)'
|
||||
: '#fafbff',
|
||||
border: isSelected
|
||||
? '2px solid #6366f1'
|
||||
: isHot
|
||||
? '2px solid #8b5cf6'
|
||||
: '1px solid #f0f0f5',
|
||||
cursor: 'pointer',
|
||||
position: 'relative',
|
||||
transition: 'all 0.2s',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
}}
|
||||
>
|
||||
{isHot && (
|
||||
<div style={{
|
||||
position: 'absolute', top: -10, right: 12,
|
||||
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)', color: '#fff',
|
||||
fontSize: 11, padding: '3px 10px', borderRadius: 8, fontWeight: 600,
|
||||
}}>最热销</div>
|
||||
)}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<Typography.Text strong style={{ fontSize: 15, color: '#1a1a2e' }}>{plan.tier}</Typography.Text>
|
||||
<Typography.Text style={{ fontSize: 12, color: '#94a3b8' }}>{plan.desc}</Typography.Text>
|
||||
</div>
|
||||
<div style={{ marginTop: 8, marginBottom: 4 }}>
|
||||
<span style={{
|
||||
fontSize: 28, fontWeight: 800,
|
||||
background: g.gradient, WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
|
||||
}}>¥{price}</span>
|
||||
<span style={{ fontSize: 12, color: '#94a3b8', marginLeft: 4 }}>/月</span>
|
||||
</div>
|
||||
<Typography.Text style={{ fontSize: 11, color: '#94a3b8', marginBottom: 8 }}>
|
||||
自动续订,可随时取消。
|
||||
</Typography.Text>
|
||||
<div style={{
|
||||
display: 'flex', gap: 8, marginBottom: 12, flexWrap: 'wrap',
|
||||
}}>
|
||||
<div style={{
|
||||
fontSize: 11, color: '#6366f1', background: 'rgba(99,102,241,0.08)',
|
||||
padding: '3px 8px', borderRadius: 6, fontWeight: 600,
|
||||
}}>
|
||||
每月 {credits.toLocaleString()} 积分
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: 11, color: '#94a3b8', background: '#f5f5fa',
|
||||
padding: '3px 8px', borderRadius: 6,
|
||||
}}>
|
||||
¥10 = {creditsPer10} 积分
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
block
|
||||
size="small"
|
||||
onClick={(e) => { e.stopPropagation(); setSelectedTierIndex(idx); }}
|
||||
style={{
|
||||
borderRadius: 10, marginBottom: 12, fontWeight: 600, height: 36,
|
||||
background: isSelected
|
||||
? 'linear-gradient(135deg, #6366f1, #8b5cf6)'
|
||||
: isHot
|
||||
? 'linear-gradient(135deg, #6366f1, #8b5cf6)'
|
||||
: '#1a1a2e',
|
||||
border: 'none', color: '#fff', fontSize: 13,
|
||||
}}
|
||||
>
|
||||
订阅月卡{plan.tier}
|
||||
</Button>
|
||||
<div style={{
|
||||
borderTop: '1px solid #f0f0f5', paddingTop: 10,
|
||||
display: 'flex', flexDirection: 'column', gap: 6,
|
||||
}}>
|
||||
{plan.features.map((feature: string, fi: number) => (
|
||||
<div key={fi} style={{ display: 'flex', alignItems: 'flex-start', gap: 6 }}>
|
||||
<CheckCircleFilled style={{ fontSize: 12, color: '#6366f1', flexShrink: 0, marginTop: 2 }} />
|
||||
<Typography.Text style={{ fontSize: 11, color: '#64748b', lineHeight: 1.5 }}>{feature}</Typography.Text>
|
||||
</div>
|
||||
))}
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 6 }}>
|
||||
<CheckCircleFilled style={{ fontSize: 12, color: '#6366f1', flexShrink: 0, marginTop: 2 }} />
|
||||
<Typography.Text style={{ fontSize: 11, color: '#64748b', lineHeight: 1.5 }}>
|
||||
解锁抢先购
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
}>
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Space style={{ marginBottom: 16 }}>
|
||||
<WalletOutlined style={{ color: '#6366f1' }} />
|
||||
<Typography.Text style={{ color: '#64748b', letterSpacing: 0 }}>当前积分余额</Typography.Text>
|
||||
<Typography.Text strong style={{ color: '#6366f1', fontSize: 20, fontWeight: 600 }}>{user?.credits ?? 0}</Typography.Text>
|
||||
</Space>
|
||||
|
||||
{(!enabledMethods.alipay && !enabledMethods.wechat) ? (
|
||||
<div style={{ marginBottom: 16, padding: 16, background: '#fef2f2', borderRadius: 12, border: '1px solid #fecaca' }}>
|
||||
@@ -1481,13 +1754,121 @@ const AppLayout: React.FC = () => {
|
||||
>联系我们</Typography.Text>
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
{/* 积分充值独立弹窗 */}
|
||||
<Modal
|
||||
title={<Space><WalletOutlined style={{ color: '#6366f1' }} />积分充值</Space>}
|
||||
open={creditsModalOpen}
|
||||
onCancel={() => { setCreditsModalOpen(false); setSelectedPlan(null); }}
|
||||
width={560}
|
||||
footer={
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', padding: '12px 0 0', borderTop: '1px solid #f0f0f0' }}>
|
||||
<Button size="large" onClick={() => { setCreditsModalOpen(false); setSelectedPlan(null); }} style={{ borderRadius: 10, marginRight: 12 }}>取消</Button>
|
||||
<Button type="primary" size="large" disabled={!selectedPlan || (!enabledMethods.alipay && !enabledMethods.wechat)} loading={paying}
|
||||
onClick={async () => {
|
||||
const plan = rechargeOptions.find((opt: any) => opt.id === selectedPlan);
|
||||
if (!plan) return;
|
||||
const totalCredits = (plan.credits || 0) + (plan.bonus_credits || plan.bonusCredits || 0);
|
||||
try {
|
||||
setPaying(true);
|
||||
const order = await createRechargeOrder(plan.id, paymentMethod);
|
||||
const qrCode = order.qrUrl || order.codeUrl || order.qr_code || order.code_url;
|
||||
if ((order.paymentMethod === 'alipay' || order.paymentMethod === 'wechat') && qrCode) {
|
||||
const paymentInfo = {
|
||||
price: plan.price,
|
||||
credits: totalCredits,
|
||||
qrCode: qrCode,
|
||||
method: order.paymentMethod,
|
||||
};
|
||||
setCurrentPaymentInfo(paymentInfo);
|
||||
setCreditsModalOpen(false);
|
||||
setQrCodeModalOpen(true);
|
||||
currentOrderNoRef.current = order.orderNo;
|
||||
|
||||
localStorage.setItem(PENDING_ORDER_KEY, JSON.stringify({
|
||||
orderNo: order.orderNo,
|
||||
price: plan.price,
|
||||
credits: totalCredits,
|
||||
qrCode: qrCode,
|
||||
method: order.paymentMethod,
|
||||
createdAt: order.createdAt || new Date().toISOString(),
|
||||
timeoutSeconds: 180,
|
||||
}));
|
||||
|
||||
startPolling(order.orderNo);
|
||||
} else {
|
||||
message.success('充值成功!积分已到账');
|
||||
useAuthStore.getState().refreshUser();
|
||||
setCreditsModalOpen(false);
|
||||
setSelectedPlan(null);
|
||||
}
|
||||
} catch (err: any) {
|
||||
message.error(err?.message || '创建订单失败,请重试');
|
||||
} finally {
|
||||
setPaying(false);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
borderRadius: 10, fontWeight: 600,
|
||||
background: selectedPlan ? 'linear-gradient(135deg, #6366f1, #8b5cf6)' : '#d1d5db',
|
||||
border: 'none', boxShadow: selectedPlan ? '0 8px 24px rgba(99,102,241,0.3)' : 'none',
|
||||
}}>
|
||||
确认充值
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Space style={{ marginBottom: 16 }}>
|
||||
<WalletOutlined style={{ color: '#6366f1' }} />
|
||||
<Typography.Text style={{ color: '#64748b', letterSpacing: 0 }}>当前积分余额</Typography.Text>
|
||||
<Typography.Text strong style={{ color: '#6366f1', fontSize: 20, fontWeight: 600 }}>{user?.credits ?? 0}</Typography.Text>
|
||||
</Space>
|
||||
|
||||
{(!enabledMethods.alipay && !enabledMethods.wechat) ? (
|
||||
<div style={{ marginBottom: 16, padding: 16, background: '#fef2f2', borderRadius: 12, border: '1px solid #fecaca' }}>
|
||||
<Typography.Text style={{ color: '#dc2626', fontSize: 13 }}>
|
||||
⚠️ 暂无可用的支付方式,请联系管理员开启支付功能
|
||||
</Typography.Text>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Typography.Text style={{ color: '#64748b', fontSize: 13, marginBottom: 8, display: 'block' }}>选择支付方式</Typography.Text>
|
||||
<Radio.Group value={paymentMethod} onChange={(e) => setPaymentMethod(e.target.value)}
|
||||
style={{ display: 'flex', gap: 12 }}>
|
||||
{enabledMethods.alipay && (
|
||||
<Radio.Button value="alipay" style={{
|
||||
flex: 1, textAlign: 'center', borderRadius: 10, height: 44, lineHeight: '42px',
|
||||
borderColor: paymentMethod === 'alipay' ? '#1677ff' : undefined,
|
||||
color: paymentMethod === 'alipay' ? '#1677ff' : undefined,
|
||||
}}>
|
||||
<AlipayCircleOutlined style={{ fontSize: 16, marginRight: 6 }} />
|
||||
支付宝
|
||||
</Radio.Button>
|
||||
)}
|
||||
{enabledMethods.wechat && (
|
||||
<Radio.Button value="wechat" style={{
|
||||
flex: 1, textAlign: 'center', borderRadius: 10, height: 44, lineHeight: '42px',
|
||||
borderColor: paymentMethod === 'wechat' ? '#07c160' : undefined,
|
||||
color: paymentMethod === 'wechat' ? '#07c160' : undefined,
|
||||
}}>
|
||||
<WechatOutlined style={{ fontSize: 16, marginRight: 6 }} />
|
||||
微信支付
|
||||
</Radio.Button>
|
||||
)}
|
||||
</Radio.Group>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
|
||||
{rechargeOptions.map((opt, idx) => {
|
||||
const g = GRADIENTS[idx % GRADIENTS.length];
|
||||
const totalCredits = (opt.credits || 0) + (opt.bonus_credits || opt.bonusCredits || 0);
|
||||
return (
|
||||
<div key={opt.id} onClick={() => setSelectedPlan(opt.id)} style={{
|
||||
flex: '1 1 45%', minWidth: 200, borderRadius: 16, padding: '20px 16px',
|
||||
flex: '1 1 45%', minWidth: 180, borderRadius: 16, padding: '18px 14px',
|
||||
background: selectedPlan === opt.id ? 'rgba(99,102,241,0.04)' : '#fafbff',
|
||||
border: selectedPlan === opt.id ? '2px solid #6366f1' : '1px solid #f0f0f5',
|
||||
cursor: 'pointer', position: 'relative', transition: 'all 0.2s',
|
||||
@@ -1495,19 +1876,19 @@ const AppLayout: React.FC = () => {
|
||||
{opt.description && (
|
||||
<Tag color="purple" style={{ position: 'absolute', top: -10, left: '50%', transform: 'translateX(-50%)', borderRadius: 8, fontSize: 11 }}>{opt.description}</Tag>
|
||||
)}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<div style={{
|
||||
width: 44, height: 44, borderRadius: 12, flexShrink: 0,
|
||||
width: 40, height: 40, borderRadius: 12, flexShrink: 0,
|
||||
background: g.gradient, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: 18, color: '#fff', boxShadow: `0 6px 16px ${g.shadow}`,
|
||||
fontSize: 16, color: '#fff', boxShadow: `0 6px 16px ${g.shadow}`,
|
||||
}}>{g.icon}</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
|
||||
<Typography.Text strong style={{ fontSize: 15 }}>{opt.name}</Typography.Text>
|
||||
<Typography.Text style={{ fontSize: 14, fontWeight: 600, color: '#6366f1' }}>{totalCredits.toLocaleString()} 积分</Typography.Text>
|
||||
<Typography.Text strong style={{ fontSize: 14 }}>{opt.name}</Typography.Text>
|
||||
<Typography.Text style={{ fontSize: 13, fontWeight: 600, color: '#6366f1' }}>{totalCredits.toLocaleString()} 积分</Typography.Text>
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: 22, fontWeight: 800, marginTop: 4,
|
||||
fontSize: 20, fontWeight: 800, marginTop: 2,
|
||||
background: g.gradient, WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
|
||||
}}>¥{opt.price}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,56 +1,184 @@
|
||||
import React from 'react';
|
||||
import { Button, Card, message, Typography } from 'antd';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { Button, Typography, Modal, Input, Select } from 'antd';
|
||||
import {
|
||||
StarOutlined,
|
||||
LockOutlined,
|
||||
GiftOutlined,
|
||||
ThunderboltOutlined,
|
||||
PlayCircleOutlined,
|
||||
CheckCircleOutlined,
|
||||
HeartOutlined,
|
||||
EyeOutlined,
|
||||
ArrowRightOutlined,
|
||||
CrownOutlined,
|
||||
DownloadOutlined,
|
||||
VideoCameraOutlined,
|
||||
PictureOutlined,
|
||||
PlayCircleOutlined,
|
||||
SearchOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
|
||||
const CreativePlazaPage: React.FC = () => {
|
||||
const handleUnlock = () => {
|
||||
message.info('请联系客服开通会员');
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
const [previewUrl, setPreviewUrl] = useState('');
|
||||
const [previewType, setPreviewType] = useState<'image' | 'video'>('image');
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [selectedIndustry, setSelectedIndustry] = useState<string>('all');
|
||||
|
||||
const openPreview = (url: string, type: 'image' | 'video') => {
|
||||
setPreviewUrl(url);
|
||||
setPreviewType(type);
|
||||
setPreviewVisible(true);
|
||||
};
|
||||
|
||||
// 顶部特性卡片
|
||||
const features = [
|
||||
const handleDownload = (url: string, filename: string) => {
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 行业选项
|
||||
const industryOptions = [
|
||||
{ value: 'all', label: '全部行业' },
|
||||
{ value: 'fashion', label: '时尚穿搭' },
|
||||
{ value: 'food', label: '美食探店' },
|
||||
{ value: 'travel', label: '旅行风景' },
|
||||
{ value: 'tech', label: '数码科技' },
|
||||
{ value: 'beauty', label: '美妆护肤' },
|
||||
{ value: 'home', label: '家居生活' },
|
||||
{ value: '母婴', label: '母婴亲子' },
|
||||
];
|
||||
|
||||
// 瀑布流数据 - 包含图片和视频
|
||||
const waterfallItems = [
|
||||
{
|
||||
icon: <ThunderboltOutlined style={{ fontSize: 26, color: '#8b5cf6' }} />,
|
||||
title: 'AI 智能生成',
|
||||
description: '先进 AI 技术一键产出高质量视频内容',
|
||||
id: 1,
|
||||
type: 'image' as const,
|
||||
url: 'https://images.unsplash.com/photo-1522337360788-8b13dee7a37e?w=400&q=80',
|
||||
title: '时尚街拍',
|
||||
tag: '热门',
|
||||
industry: 'fashion',
|
||||
aspectRatio: 1.3,
|
||||
},
|
||||
{
|
||||
icon: <PlayCircleOutlined style={{ fontSize: 26, color: '#ec4899' }} />,
|
||||
title: '多格式输出',
|
||||
description: '支持多种视频格式,满足不同场景需求',
|
||||
id: 2,
|
||||
type: 'video' as const,
|
||||
url: 'https://www.w3schools.com/html/mov_bbb.mp4',
|
||||
poster: 'https://images.unsplash.com/photo-1492691527719-9d1e07e534b4?w=400&q=80',
|
||||
title: '自然风景',
|
||||
tag: '推荐',
|
||||
industry: 'travel',
|
||||
aspectRatio: 1.5,
|
||||
},
|
||||
{
|
||||
icon: <CheckCircleOutlined style={{ fontSize: 26, color: '#10b981' }} />,
|
||||
title: '品质保证',
|
||||
description: '专业级画质,细节清晰,色彩鲜艳',
|
||||
id: 3,
|
||||
type: 'image' as const,
|
||||
url: 'https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=400&q=80',
|
||||
title: '湖光山色',
|
||||
tag: '热门',
|
||||
industry: 'travel',
|
||||
aspectRatio: 0.75,
|
||||
},
|
||||
{
|
||||
icon: <HeartOutlined style={{ fontSize: 26, color: '#f59e0b' }} />,
|
||||
title: '创意无限',
|
||||
description: '丰富模板与风格,激发创作灵感',
|
||||
id: 4,
|
||||
type: 'image' as const,
|
||||
url: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&q=80',
|
||||
title: '山脉风光',
|
||||
tag: '新品',
|
||||
industry: 'travel',
|
||||
aspectRatio: 1.4,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'video' as const,
|
||||
url: 'https://www.w3schools.com/html/movie.mp4',
|
||||
poster: 'https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=400&q=80',
|
||||
title: '森林探索',
|
||||
tag: '推荐',
|
||||
industry: 'travel',
|
||||
aspectRatio: 0.67,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
type: 'image' as const,
|
||||
url: 'https://images.unsplash.com/photo-1470770841072-f978cf4d019e?w=400&q=80',
|
||||
title: '山间湖泊',
|
||||
tag: '热门',
|
||||
industry: 'travel',
|
||||
aspectRatio: 1.7,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
type: 'image' as const,
|
||||
url: 'https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=400&q=80',
|
||||
title: '海岸风光',
|
||||
tag: '推荐',
|
||||
industry: 'travel',
|
||||
aspectRatio: 0.8,
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
type: 'video' as const,
|
||||
url: 'https://www.w3schools.com/html/mov_bbb.mp4',
|
||||
poster: 'https://images.unsplash.com/photo-1472214103451-9374bd1c798e?w=400&q=80',
|
||||
title: '城市夜景',
|
||||
tag: '新品',
|
||||
industry: 'travel',
|
||||
aspectRatio: 1.33,
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
type: 'image' as const,
|
||||
url: 'https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=400&q=80',
|
||||
title: '森林小径',
|
||||
tag: '热门',
|
||||
industry: 'travel',
|
||||
aspectRatio: 1.5,
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
type: 'image' as const,
|
||||
url: 'https://images.unsplash.com/photo-1504674900247-0877df9cc836?w=400&q=80',
|
||||
title: '美食摆盘',
|
||||
tag: '热门',
|
||||
industry: 'food',
|
||||
aspectRatio: 1.2,
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
type: 'image' as const,
|
||||
url: 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=400&q=80',
|
||||
title: '时尚穿搭',
|
||||
tag: '推荐',
|
||||
industry: 'fashion',
|
||||
aspectRatio: 1.0,
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
type: 'video' as const,
|
||||
url: 'https://www.w3schools.com/html/mov_bbb.mp4',
|
||||
poster: 'https://images.unsplash.com/photo-1498050108023-c5249f4df085?w=400&q=80',
|
||||
title: '数码产品',
|
||||
tag: '新品',
|
||||
industry: 'tech',
|
||||
aspectRatio: 1.4,
|
||||
},
|
||||
];
|
||||
|
||||
// 精选案例占位
|
||||
const caseList = [
|
||||
{ title: '美妆直播切片', tag: '热门', tagColor: '#ef4444' },
|
||||
{ title: '3C 数码测评', tag: '推荐', tagColor: '#6366f1' },
|
||||
{ title: '美食探店 vlog', tag: '推荐', tagColor: '#6366f1' },
|
||||
{ title: '服饰穿搭合集', tag: '热门', tagColor: '#ef4444' },
|
||||
{ title: '家居场景展示', tag: '推荐', tagColor: '#6366f1' },
|
||||
{ title: '母婴亲子内容', tag: '热门', tagColor: '#ef4444' },
|
||||
];
|
||||
// 筛选后的数据
|
||||
const filteredItems = useMemo(() => {
|
||||
return waterfallItems.filter((item) => {
|
||||
const matchSearch = searchText
|
||||
? item.title.toLowerCase().includes(searchText.toLowerCase())
|
||||
: true;
|
||||
const matchIndustry = selectedIndustry === 'all' ? true : item.industry === selectedIndustry;
|
||||
return matchSearch && matchIndustry;
|
||||
});
|
||||
}, [searchText, selectedIndustry]);
|
||||
|
||||
const handleItemClick = (item: typeof waterfallItems[0]) => {
|
||||
const previewUrl = item.type === 'image' ? item.url : item.url;
|
||||
openPreview(previewUrl, item.type);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="content_box">
|
||||
@@ -100,54 +228,7 @@ const CreativePlazaPage: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* 特性卡片 */}
|
||||
{/* <div
|
||||
className="animate-fadeInUp stagger-children"
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||
gap: 16,
|
||||
marginBottom: 24,
|
||||
}}
|
||||
>
|
||||
{features.map((feature, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
bordered={false}
|
||||
hoverable
|
||||
className="project-card"
|
||||
style={{
|
||||
borderRadius: 16,
|
||||
background: '#fff',
|
||||
border: '1px solid #e2e8f0',
|
||||
padding: '4px 0',
|
||||
}}
|
||||
styles={{ body: { padding: 20 } }}
|
||||
>
|
||||
<div style={{ textAlign: 'left' }}>
|
||||
<div
|
||||
style={{
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 12,
|
||||
background: 'linear-gradient(135deg, rgba(139, 92, 246, 0.08), rgba(99, 102, 241, 0.08))',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: 14,
|
||||
}}
|
||||
>
|
||||
{feature.icon}
|
||||
</div>
|
||||
<div style={{ fontSize: 15, fontWeight: 600, color: '#1e293b', marginBottom: 6 }}>
|
||||
{feature.title}
|
||||
</div>
|
||||
<div style={{ fontSize: 13, color: '#64748b', lineHeight: 1.6 }}>
|
||||
{feature.description}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div> */}
|
||||
|
||||
|
||||
{/* 精选案例展示区域 */}
|
||||
<div
|
||||
@@ -182,150 +263,272 @@ const CreativePlazaPage: React.FC = () => {
|
||||
<span style={{ fontSize: 17, fontWeight: 600, color: '#1f2937' }}>精选案例展示</span>
|
||||
|
||||
</div>
|
||||
{/* <Button
|
||||
type="primary"
|
||||
size="middle"
|
||||
onClick={handleUnlock}
|
||||
style={{
|
||||
borderRadius: 10,
|
||||
fontWeight: 500,
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
border: 'none',
|
||||
boxShadow: '0 4px 14px rgba(99, 102, 241, 0.28)',
|
||||
}}
|
||||
>
|
||||
<LockOutlined style={{ marginRight: 6 }} />
|
||||
解锁全部案例
|
||||
<ArrowRightOutlined style={{ marginLeft: 4, fontSize: 12 }} />
|
||||
</Button> */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<Select
|
||||
value={selectedIndustry}
|
||||
onChange={(value) => setSelectedIndustry(value)}
|
||||
style={{ width: 140 }}
|
||||
options={industryOptions}
|
||||
/>
|
||||
<Input.Search
|
||||
placeholder="搜索行业案例"
|
||||
allowClear
|
||||
enterButton={<SearchOutlined />}
|
||||
size="middle"
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
onSearch={(value) => setSearchText(value)}
|
||||
style={{ width: 240 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 案例网格 */}
|
||||
{/* 统计信息 */}
|
||||
<div style={{ marginBottom: 16, color: '#64748b', fontSize: 13 }}>
|
||||
共找到 <span style={{ color: '#6366f1', fontWeight: 600 }}>{filteredItems.length}</span> 个案例
|
||||
</div>
|
||||
|
||||
{/* 瀑布流展示 */}
|
||||
<div
|
||||
className="stagger-children"
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||
gap: 16,
|
||||
columnCount: 5,
|
||||
columnGap: 12,
|
||||
marginBottom: 16,
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
zIndex: 1,
|
||||
background: 'rgb(255,255,255)',
|
||||
// background:
|
||||
// 'linear-gradient(180deg, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.55) 100%)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
borderRadius: 14,
|
||||
// boxShadow: '0px 4px 16px rgba(0, 0, 0, 0.08)',
|
||||
gap: 4,
|
||||
color: '#fff',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
size="middle"
|
||||
onClick={handleUnlock}
|
||||
style={{
|
||||
borderRadius: 10,
|
||||
fontWeight: 500,
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
border: 'none',
|
||||
boxShadow: '0 4px 14px rgba(99, 102, 241, 0.28)',
|
||||
}}
|
||||
>
|
||||
<LockOutlined style={{ marginRight: 6 }} />
|
||||
解锁全部案例
|
||||
<ArrowRightOutlined style={{ marginLeft: 4, fontSize: 12 }} />
|
||||
</Button>
|
||||
{/* <LockOutlined style={{ fontSize: 22, color: '#94a3b8', cursor: 'pointer' }}
|
||||
onClick={handleUnlock}
|
||||
/>
|
||||
<span style={{ fontSize: 16, color: '#1f2937', opacity: 0.9 }}>会员可查看</span> */}
|
||||
</div>
|
||||
|
||||
{caseList.map((item, index) => (
|
||||
{filteredItems.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="project-card animate-fadeInUp"
|
||||
key={item.id}
|
||||
onClick={() => handleItemClick(item)}
|
||||
style={{
|
||||
position: 'relative',
|
||||
borderRadius: 14,
|
||||
breakInside: 'avoid',
|
||||
marginBottom: 12,
|
||||
borderRadius: 10,
|
||||
overflow: 'hidden',
|
||||
cursor: 'pointer',
|
||||
border: '1px solid #e2e8f0',
|
||||
background: '#fff',
|
||||
position: 'relative',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)',
|
||||
transition: 'transform 0.3s ease, box-shadow 0.3s ease',
|
||||
animation: `fadeInUp 0.5s ease ${index * 0.1}s both`,
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
(e.currentTarget as HTMLElement).style.transform = 'translateY(-4px)';
|
||||
(e.currentTarget as HTMLElement).style.boxShadow = '0 8px 24px rgba(0, 0, 0, 0.15)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
(e.currentTarget as HTMLElement).style.transform = 'translateY(0)';
|
||||
(e.currentTarget as HTMLElement).style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.08)';
|
||||
}}
|
||||
>
|
||||
{/* 图片或视频 */}
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
aspectRatio: '16/9',
|
||||
background:
|
||||
'linear-gradient(135deg, #1e1b4b 0%, #312e81 50%, #4c1d95 100%)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
paddingBottom: `${(1 / item.aspectRatio) * 100}%`,
|
||||
backgroundColor: '#f3f4f6',
|
||||
}}
|
||||
>
|
||||
{/* 模糊视频缩略图占位 */}
|
||||
<PlayCircleOutlined style={{ fontSize: 36, color: 'rgba(255,255,255,0.6)' }} />
|
||||
{/* 标签 */}
|
||||
{item.type === 'image' ? (
|
||||
<img
|
||||
src={item.url}
|
||||
alt={item.title}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<img
|
||||
src={item.poster}
|
||||
alt={item.title}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: '50%',
|
||||
background: 'rgba(0, 0, 0, 0.6)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<PlayCircleOutlined style={{ fontSize: 32, color: '#fff' }} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 类型标签 */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
left: 10,
|
||||
padding: '2px 8px',
|
||||
top: 12,
|
||||
left: 12,
|
||||
padding: '4px 8px',
|
||||
borderRadius: 6,
|
||||
background: item.tagColor,
|
||||
color: '#fff',
|
||||
fontSize: 11,
|
||||
fontWeight: 500,
|
||||
background: 'rgba(0, 0, 0, 0.6)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
{item.tag}
|
||||
{item.type === 'image' ? (
|
||||
<PictureOutlined style={{ fontSize: 12, color: '#fff' }} />
|
||||
) : (
|
||||
<VideoCameraOutlined style={{ fontSize: 12, color: '#fff' }} />
|
||||
)}
|
||||
<span style={{ fontSize: 11, color: '#fff', fontWeight: 500 }}>
|
||||
{item.type === 'image' ? '图片' : '视频'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* 底部信息 */}
|
||||
<div
|
||||
style={{
|
||||
padding: '10px 14px',
|
||||
background: '#f8fafc',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: 12,
|
||||
background: '#fff',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
<div
|
||||
style={{
|
||||
fontSize: 13,
|
||||
color: '#334155',
|
||||
fontWeight: 500,
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</span>
|
||||
<EyeOutlined style={{ fontSize: 13, color: '#94a3b8' }} />
|
||||
<span
|
||||
style={{
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
color: '#1f2937',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
flex: 1,
|
||||
marginRight: 8,
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 11,
|
||||
color: '#fff',
|
||||
padding: '2px 8px',
|
||||
borderRadius: 4,
|
||||
background: item.tag === '热门' ? '#ef4444' : item.tag === '推荐' ? '#6366f1' : '#f59e0b',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{item.tag}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* 锁定遮罩 */}
|
||||
|
||||
|
||||
{/* 空状态 */}
|
||||
{filteredItems.length === 0 && (
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
padding: '60px 20px',
|
||||
color: '#94a3b8',
|
||||
}}
|
||||
>
|
||||
<SearchOutlined style={{ fontSize: 48, marginBottom: 16, opacity: 0.5 }} />
|
||||
<div style={{ fontSize: 15 }}>暂无符合条件的案例</div>
|
||||
<div style={{ fontSize: 13, marginTop: 8 }}>请尝试调整搜索关键词或行业筛选</div>
|
||||
<Button
|
||||
style={{ marginTop: 16, borderRadius: 8 }}
|
||||
onClick={() => {
|
||||
setSearchText('');
|
||||
setSelectedIndustry('all');
|
||||
}}
|
||||
>
|
||||
重置筛选
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{/* 预览弹窗 */}
|
||||
<Modal
|
||||
open={previewVisible}
|
||||
onCancel={() => setPreviewVisible(false)}
|
||||
footer={[
|
||||
<Button
|
||||
key="download"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={() => handleDownload(previewUrl, previewType === 'image' ? 'image.jpg' : 'video.mp4')}
|
||||
type="primary"
|
||||
style={{ borderRadius: 8 }}
|
||||
>
|
||||
下载
|
||||
</Button>,
|
||||
]}
|
||||
width={800}
|
||||
centered
|
||||
styles={{ body: { padding: 0 } }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: 400,
|
||||
// background: '#1a1a2e',
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{previewType === 'image' ? (
|
||||
<img
|
||||
src={previewUrl}
|
||||
alt="预览"
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '60vh',
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<video
|
||||
src={previewUrl}
|
||||
controls
|
||||
autoPlay
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '60vh',
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,10 +18,6 @@ import {
|
||||
App,
|
||||
} from 'antd';
|
||||
|
||||
import bg1 from '../assets/bg1.png';
|
||||
import bg2 from '../assets/bg2.png';
|
||||
import bg3 from '../assets/bg3.png';
|
||||
import text from '../assets/testb.png';
|
||||
|
||||
import UploadSelector from '../components/UploadSelector';
|
||||
import GenerationTaskResourceGrid from '../components/generation/GenerationTaskResourceGrid';
|
||||
@@ -313,8 +309,7 @@ const AIChatPage: React.FC = () => {
|
||||
}
|
||||
}, [previewVisible, previewType]);
|
||||
|
||||
// 提示词展开状态
|
||||
const [expandedPrompts, setExpandedPrompts] = useState<Set<string>>(new Set());
|
||||
|
||||
|
||||
// 从URL中提取exp时间戳(支持相对路径和完整URL)
|
||||
const extractExpTimestamp = (url: string): number | null => {
|
||||
@@ -947,56 +942,6 @@ const AIChatPage: React.FC = () => {
|
||||
setLastFrame(null);
|
||||
};
|
||||
|
||||
// ==================== 事处理函数 ====================
|
||||
|
||||
const handleNewChat = () => {
|
||||
// 生成唯一ID(使用时间戳)
|
||||
const newId = Date.now().toString();
|
||||
const newConversation: Conversation = {
|
||||
id: newId,
|
||||
title: '新对话',
|
||||
lastMessage: '',
|
||||
timestamp: new Date().toLocaleString('zh-CN'),
|
||||
messages: [],
|
||||
};
|
||||
|
||||
// 将新对话插入到列表顶部
|
||||
setConversations((prev) => [newConversation, ...prev]);
|
||||
// 切换到新对话
|
||||
setCurrentConversationId(newId);
|
||||
// 清空输入框和已上传媒体
|
||||
setInputValue('');
|
||||
setCurrentMedia([]);
|
||||
setFirstFrame(null);
|
||||
setLastFrame(null);
|
||||
// 显示提示消息
|
||||
antdMessage.info('已开启新对话');
|
||||
|
||||
};
|
||||
|
||||
|
||||
const handleDeleteChat = (conversationId: string) => {
|
||||
// 过滤掉要删除的对话
|
||||
setConversations((prev) => prev.filter((c) => c.id !== conversationId));
|
||||
// 如果删除的是当前选中的对话,切换到其他对话
|
||||
if (currentConversationId === conversationId) {
|
||||
setCurrentConversationId(
|
||||
conversations[0]?.id === conversationId
|
||||
? conversations[1]?.id || null
|
||||
: conversations[0]?.id || null
|
||||
);
|
||||
}
|
||||
// 显示成功提示
|
||||
antdMessage.success('对话已删除');
|
||||
};
|
||||
|
||||
|
||||
const handleSelectChat = (conversationId: string) => {
|
||||
setCurrentConversationId(conversationId);
|
||||
setCurrentMedia([]);
|
||||
setFirstFrame(null);
|
||||
setLastFrame(null);
|
||||
};
|
||||
|
||||
|
||||
const handleSend = async () => {
|
||||
@@ -1873,12 +1818,60 @@ const AIChatPage: React.FC = () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
// 检测文本是否包含中文字符
|
||||
const isChineseText = (text: string): boolean => {
|
||||
return /[\u4e00-\u9fa5]/.test(text);
|
||||
};
|
||||
|
||||
// 统计字数:中文按字符计数,英文按单词计数
|
||||
const countInput = (text: string): number => {
|
||||
if (!text.trim()) return 0;
|
||||
if (isChineseText(text)) {
|
||||
// 中文:按字符数(不含空白)
|
||||
return text.replace(/\s/g, '').length;
|
||||
} else {
|
||||
// 英文:按单词数
|
||||
const words = text.trim().split(/\s+/);
|
||||
return words[0] === '' ? 0 : words.length;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取最大限制,默认显示500
|
||||
const getMaxLimit = (text: string): number => {
|
||||
if (!text.trim()) return 500;
|
||||
return isChineseText(text) ? 500 : 1000;
|
||||
};
|
||||
|
||||
// 输入框变化处理
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const value = e.target.value;
|
||||
setInputValue(value);
|
||||
const textarea = e.target;
|
||||
checkMention(textarea, value);
|
||||
const maxLimit = getMaxLimit(value);
|
||||
const count = countInput(value);
|
||||
if (count <= maxLimit) {
|
||||
setInputValue(value);
|
||||
checkMention(e.target, value);
|
||||
} else {
|
||||
// 截断到限制内
|
||||
if (isChineseText(value)) {
|
||||
let trimmed = value;
|
||||
let cjkCount = 0;
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (/\s/.test(value[i])) continue;
|
||||
cjkCount++;
|
||||
if (cjkCount > maxLimit) {
|
||||
trimmed = value.slice(0, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
setInputValue(trimmed);
|
||||
checkMention(e.target, trimmed);
|
||||
} else {
|
||||
const words = value.trim().split(/\s+/);
|
||||
const limited = words.slice(0, maxLimit).join(' ');
|
||||
setInputValue(limited);
|
||||
checkMention(e.target, limited);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 键盘事件处理(ESC 关闭提及、Tab/Enter 选择)
|
||||
@@ -2414,60 +2407,7 @@ const AIChatPage: React.FC = () => {
|
||||
</Popconfirm>
|
||||
</div>
|
||||
|
||||
{/* 文本内容 */}
|
||||
{/* <div
|
||||
style={{
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
margin: '8px 0',
|
||||
padding: '12px 16px',
|
||||
backgroundColor: 'rgba(139, 92, 246, 0.04)',
|
||||
borderRadius: 10,
|
||||
border: '1px solid rgba(139, 92, 246, 0.08)',
|
||||
fontSize: 13,
|
||||
color: '#475467',
|
||||
lineHeight: 1.6,
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
boxShadow: '0 1px 3px rgba(47, 52, 64, 0.035)',
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setExpandedPrompts(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.add(msg.id);
|
||||
return newSet;
|
||||
});
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setExpandedPrompts(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(msg.id);
|
||||
return newSet;
|
||||
});
|
||||
}}
|
||||
>
|
||||
默认显示:一行省略
|
||||
<div style={{
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
display: expandedPrompts.has(msg.id) ? 'none' : 'block',
|
||||
}}>
|
||||
{msg.originalPrompt}
|
||||
</div>
|
||||
|
||||
鼠标移入显示:完整内容
|
||||
<div style={{
|
||||
width: '100%',
|
||||
maxHeight: 200,
|
||||
overflowY: 'auto',
|
||||
display: expandedPrompts.has(msg.id) ? 'block' : 'none',
|
||||
wordBreak: 'break-word',
|
||||
}}>
|
||||
{msg.originalPrompt}
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
|
||||
{/* 根据 status 显示不同内容 */}
|
||||
{(msg.status === 'generating' || msg.status === 'failed' || msg.status === 'completed') && (
|
||||
@@ -3304,6 +3244,19 @@ const AIChatPage: React.FC = () => {
|
||||
disabled={loading}
|
||||
/>
|
||||
|
||||
{/* 字数统计 */}
|
||||
<div style={{
|
||||
display: 'flex', justifyContent: 'flex-end',
|
||||
paddingRight: 10, marginTop: 2,
|
||||
}}>
|
||||
<span style={{
|
||||
fontSize: 11,
|
||||
color: countInput(inputValue) > getMaxLimit(inputValue) ? '#ef4444' : '#9ca3af',
|
||||
}}>
|
||||
{countInput(inputValue)} / {getMaxLimit(inputValue)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* @ 提及下拉列表 */}
|
||||
{mentionVisible && currentMedia.length > 0 && referenceMode === 'universal' && (
|
||||
<div
|
||||
@@ -4736,7 +4689,7 @@ const AIChatPage: React.FC = () => {
|
||||
width={800}
|
||||
footer={
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, padding: '16px 24px', background: 'rgba(255,255,255,0.6)', borderTop: '1px solid rgba(139, 92, 246, 0.08)' }}>
|
||||
<Button
|
||||
{/* <Button
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={() => {
|
||||
@@ -4751,7 +4704,7 @@ const AIChatPage: React.FC = () => {
|
||||
style={{ borderRadius: 10, background: 'linear-gradient(135deg, #8b5cf6 0%, #ddd6fe 100%)', border: 'none', boxShadow: '0 8px 18px rgba(47, 52, 64, 0.15)' }}
|
||||
>
|
||||
下载
|
||||
</Button>
|
||||
</Button> */}
|
||||
</div>
|
||||
}
|
||||
centered
|
||||
@@ -4769,7 +4722,7 @@ const AIChatPage: React.FC = () => {
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%', height: '100%' }}>
|
||||
{attachmentPreviewType === 'image' ? (
|
||||
<img
|
||||
src={buildPreviewUrl(attachmentPreviewUrl)}
|
||||
src={buildPreviewUrl(`/static${attachmentPreviewUrl}?w=300&q=50`)}
|
||||
alt="预览"
|
||||
style={{ width: '100%', maxHeight: '400px', objectFit: 'contain' }}
|
||||
/>
|
||||
|
||||
@@ -83,82 +83,7 @@ const CaseAssetCard: React.FC<CaseAssetCardProps> = ({ asset, index, onPreview }
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
)}
|
||||
{/* <div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
background: 'rgba(0,0,0,0.6)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
opacity: isHovered ? 1 : 0,
|
||||
transition: 'opacity 0.4s ease',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
padding: '24px 36px',
|
||||
background: 'linear-gradient(135deg, rgba(6,182,212,0.2), rgba(99,102,241,0.2))',
|
||||
borderRadius: 16,
|
||||
backdropFilter: 'blur(20px)',
|
||||
border: '1px solid rgba(6,182,212,0.3)',
|
||||
boxShadow: isHovered ? '0 0 30px rgba(6,182,212,0.4), 0 0 60px rgba(99,102,241,0.2)' : 'none',
|
||||
transform: isHovered ? 'scale(1) translateY(0)' : 'scale(0.95) translateY(10px)',
|
||||
transition: 'transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.4s ease',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: '50%',
|
||||
background: 'linear-gradient(135deg, #06b6d4, #6366f1)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
animation: isHovered ? 'pulse-glow 2s ease-in-out infinite' : 'none',
|
||||
}}
|
||||
>
|
||||
<CopyOutlined style={{ fontSize: 28, color: '#fff' }} />
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
inset: '-4px',
|
||||
borderRadius: '50%',
|
||||
background: 'linear-gradient(135deg, #06b6d4, #6366f1)',
|
||||
opacity: 0.3,
|
||||
animation: isHovered ? 'ripple 2s ease-out infinite' : 'none',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 14,
|
||||
color: '#fff',
|
||||
fontWeight: 600,
|
||||
letterSpacing: 1,
|
||||
textShadow: '0 0 10px rgba(6,182,212,0.5)',
|
||||
}}
|
||||
>
|
||||
点击预览
|
||||
</span>
|
||||
<style>{`
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% { box-shadow: 0 0 20px rgba(6,182,212,0.5); }
|
||||
50% { box-shadow: 0 0 40px rgba(99,102,241,0.6), 0 0 60px rgba(6,182,212,0.3); }
|
||||
}
|
||||
@keyframes ripple {
|
||||
0% { transform: scale(1); opacity: 0.6; }
|
||||
100% { transform: scale(1.8); opacity: 0; }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -416,273 +341,7 @@ const HomePage: React.FC = () => {
|
||||
|
||||
</p>
|
||||
</div>
|
||||
{/* ========== 顶部工作台引导区域(三步流程) ========== */}
|
||||
{/* <div className="animate-fadeInUp" style={{
|
||||
padding: '24px 28px',
|
||||
borderRadius: 16,
|
||||
background: 'linear-gradient(135deg, #f0f9ff 0%, #faf5ff 50%, #fef3c7 100%)',
|
||||
border: '1px solid rgba(99,102,241,0.10)',
|
||||
margin: '0 0 20px',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 18 }}>
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<div style={{
|
||||
width: 4, height: 18, borderRadius: 2,
|
||||
background: 'linear-gradient(180deg, #6366f1, #a855f7)',
|
||||
}} />
|
||||
<div style={{ fontSize: 17, fontWeight: 700, color: '#1f2937', letterSpacing: 0.3 }}>
|
||||
我的项目<span style={{ color: '#a8a8a8ff', fontSize: 12 }}>(操作步骤)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
}}>
|
||||
<div style={{
|
||||
flex: 1,
|
||||
background: '#fff',
|
||||
borderRadius: 14,
|
||||
padding: '20px 18px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
boxShadow: '0 2px 8px rgba(15, 23, 42, 0.04)',
|
||||
}}>
|
||||
<div style={{ textAlign: 'center', fontSize: 20, fontWeight: 700, color: '#1e293b', marginBottom: 14, letterSpacing: 1 }}>
|
||||
第1步
|
||||
</div>
|
||||
<div style={{
|
||||
flex: 1,
|
||||
minHeight: 140,
|
||||
border: '1px dashed #c7d2fe',
|
||||
borderRadius: 8,
|
||||
background: '#fafbff',
|
||||
padding: 10,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 8,
|
||||
marginBottom: 12,
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<div style={{ width: 8, height: 8, borderRadius: 2, background: '#6366f1' }} />
|
||||
<div style={{ flex: 1, height: 22, background: '#fff', border: '1px solid #e2e8f0', borderRadius: 4, display: 'flex', alignItems: 'center', padding: '0 8px', fontSize: 10, color: '#94a3b8' }}>
|
||||
项目名称...
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
|
||||
<div style={{ padding: '2px 8px', background: '#eef2ff', color: '#6366f1', borderRadius: 10, fontSize: 10, fontWeight: 500, border: '1px solid #c7d2fe' }}>美妆</div>
|
||||
<div style={{ padding: '2px 8px', background: '#fff7ed', color: '#f97316', borderRadius: 10, fontSize: 10, fontWeight: 500, border: '1px solid #fed7aa' }}>美食</div>
|
||||
<div style={{ padding: '2px 8px', background: '#ecfdf5', color: '#10b981', borderRadius: 10, fontSize: 10, fontWeight: 500, border: '1px solid #a7f3d0' }}>3C数码</div>
|
||||
<div style={{ padding: '2px 8px', background: '#f5f3ff', color: '#8b5cf6', borderRadius: 10, fontSize: 10, fontWeight: 500, border: '1px solid #ddd6fe' }}>服饰</div>
|
||||
</div>
|
||||
<div style={{ height: 16, background: '#fff', border: '1px solid #e2e8f0', borderRadius: 4 }} />
|
||||
<div style={{ height: 16, width: '70%', background: '#fff', border: '1px solid #e2e8f0', borderRadius: 4 }} />
|
||||
</div>
|
||||
<div style={{
|
||||
background: '#f1f5f9',
|
||||
borderRadius: 10,
|
||||
padding: '14px 12px',
|
||||
textAlign: 'center',
|
||||
fontSize: 13,
|
||||
color: '#475569',
|
||||
lineHeight: 1.6,
|
||||
}}>
|
||||
创建新项目,自定义项目名称并选择对应行业分类
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', color: '#94a3b8', fontSize: 22 }}>
|
||||
<ArrowRightOutlined style={{ transform: 'rotate(0deg)' }} />
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
flex: 1,
|
||||
background: '#fff',
|
||||
borderRadius: 14,
|
||||
padding: '20px 18px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
boxShadow: '0 2px 8px rgba(15, 23, 42, 0.04)',
|
||||
}}>
|
||||
<div style={{ textAlign: 'center', fontSize: 20, fontWeight: 700, color: '#1e293b', marginBottom: 14, letterSpacing: 1 }}>
|
||||
第2步
|
||||
</div>
|
||||
<div style={{
|
||||
flex: 1,
|
||||
minHeight: 140,
|
||||
border: '1px solid #e2e8f0',
|
||||
borderRadius: 8,
|
||||
background: '#fafafa',
|
||||
padding: 10,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 8,
|
||||
marginBottom: 12,
|
||||
}}>
|
||||
<div style={{ display: 'flex', background: '#f1f5f9', borderRadius: 6, padding: 2, gap: 2 }}>
|
||||
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4, padding: '5px 0', background: '#fff', borderRadius: 4, fontSize: 11, fontWeight: 600, color: '#6366f1', boxShadow: '0 1px 3px rgba(99,102,241,0.15)' }}>
|
||||
<VideoCameraOutlined style={{ fontSize: 11 }} />视频
|
||||
</div>
|
||||
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4, padding: '5px 0', fontSize: 11, color: '#94a3b8' }}>
|
||||
<PictureOutlined style={{ fontSize: 11 }} /> 图片
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 9, color: '#94a3b8', marginBottom: 3 }}>尺寸比例</div>
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
<div style={{ flex: 1, textAlign: 'center', padding: '4px 0', background: '#eef2ff', color: '#6366f1', border: '1px solid #c7d2fe', borderRadius: 4, fontSize: 10, fontWeight: 600 }}>9:16</div>
|
||||
<div style={{ flex: 1, textAlign: 'center', padding: '4px 0', background: '#fff', color: '#64748b', border: '1px solid #e2e8f0', borderRadius: 4, fontSize: 10 }}>16:9</div>
|
||||
<div style={{ flex: 1, textAlign: 'center', padding: '4px 0', background: '#fff', color: '#64748b', border: '1px solid #e2e8f0', borderRadius: 4, fontSize: 10 }}>1:1</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 9, color: '#94a3b8', marginBottom: 3 }}>时长</div>
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
<div style={{ flex: 1, textAlign: 'center', padding: '4px 0', background: '#fff', color: '#64748b', border: '1px solid #e2e8f0', borderRadius: 4, fontSize: 10 }}>5s</div>
|
||||
<div style={{ flex: 1, textAlign: 'center', padding: '4px 0', background: '#fff7ed', color: '#f97316', border: '1px solid #fed7aa', borderRadius: 4, fontSize: 10, fontWeight: 600 }}>10s</div>
|
||||
<div style={{ flex: 1, textAlign: 'center', padding: '4px 0', background: '#fff', color: '#64748b', border: '1px solid #e2e8f0', borderRadius: 4, fontSize: 10 }}>15s</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{
|
||||
background: '#f1f5f9',
|
||||
borderRadius: 10,
|
||||
padding: '14px 12px',
|
||||
textAlign: 'center',
|
||||
fontSize: 13,
|
||||
color: '#475569',
|
||||
lineHeight: 1.6,
|
||||
}}>
|
||||
选择生成图片或视频,设置尺寸、时长等参数,点击生成即可一键优化提示词
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', color: '#94a3b8', fontSize: 22 }}>
|
||||
<ArrowRightOutlined />
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
flex: 1,
|
||||
background: '#fff',
|
||||
borderRadius: 14,
|
||||
padding: '20px 18px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
boxShadow: '0 2px 8px rgba(15, 23, 42, 0.04)',
|
||||
}}>
|
||||
<div style={{ textAlign: 'center', fontSize: 20, fontWeight: 700, color: '#1e293b', marginBottom: 14, letterSpacing: 1 }}>
|
||||
第3步
|
||||
</div>
|
||||
<div style={{
|
||||
flex: 1,
|
||||
minHeight: 140,
|
||||
border: '1px solid #e2e8f0',
|
||||
borderRadius: 8,
|
||||
background: '#fafafa',
|
||||
padding: 10,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 6,
|
||||
marginBottom: 12,
|
||||
justifyContent: 'space-between',
|
||||
}}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '5px 8px', background: '#f0fdf4', border: '1px solid #bbf7d0', borderRadius: 5 }}>
|
||||
<div style={{ width: 14, height: 14, borderRadius: '50%', background: '#10b981', color: '#fff', fontSize: 10, display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700 }}>✓</div>
|
||||
<div style={{ fontSize: 10, color: '#065f46', fontWeight: 500 }}>提示词已智能优化</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '5px 8px', background: '#f0fdf4', border: '1px solid #bbf7d0', borderRadius: 5 }}>
|
||||
<div style={{ width: 14, height: 14, borderRadius: '50%', background: '#10b981', color: '#fff', fontSize: 10, display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700 }}>✓</div>
|
||||
<div style={{ fontSize: 10, color: '#065f46', fontWeight: 500 }}>尺寸 9:16 · 时长 5s</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 4,
|
||||
padding: '6px 0',
|
||||
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
||||
color: '#fff',
|
||||
borderRadius: 5,
|
||||
fontSize: 11,
|
||||
fontWeight: 600,
|
||||
boxShadow: '0 2px 6px rgba(99,102,241,0.25)',
|
||||
}}>
|
||||
<ThunderboltOutlined style={{ fontSize: 11 }} />
|
||||
确认并生成素材
|
||||
</div>
|
||||
</div>
|
||||
<div style={{
|
||||
background: '#f1f5f9',
|
||||
borderRadius: 10,
|
||||
padding: '14px 12px',
|
||||
textAlign: 'center',
|
||||
fontSize: 13,
|
||||
color: '#475569',
|
||||
lineHeight: 1.6,
|
||||
}}>
|
||||
核对图片 / 视频参数与优化后的提示词,确认后一键生成素材
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => navigate('/projects')}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-2px) scale(1.05)';
|
||||
e.currentTarget.style.boxShadow = '0 14px 32px rgba(99,102,241,0.45)';
|
||||
const arrow = e.currentTarget.querySelector('.round-arrow') as HTMLElement | null;
|
||||
if (arrow) arrow.style.transform = 'translateX(3px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(0) scale(1)';
|
||||
e.currentTarget.style.boxShadow = '0 8px 20px rgba(99,102,241,0.30)';
|
||||
const arrow = e.currentTarget.querySelector('.round-arrow') as HTMLElement | null;
|
||||
if (arrow) arrow.style.transform = 'translateX(0)';
|
||||
}}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 4,
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: '50%',
|
||||
textAlign: 'center',
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%)',
|
||||
color: '#fff',
|
||||
fontSize: 14,
|
||||
fontWeight: 600,
|
||||
letterSpacing: 0.5,
|
||||
boxShadow: '0 8px 20px rgba(99,102,241,0.30)',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
userSelect: 'none',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<span style={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
borderRadius: '50%',
|
||||
background: 'radial-gradient(circle at 30% 25%, rgba(255,255,255,0.35) 0%, transparent 55%)',
|
||||
pointerEvents: 'none',
|
||||
}} />
|
||||
前往
|
||||
<ArrowRightOutlined
|
||||
className="round-arrow"
|
||||
style={{ fontSize: 13, transition: 'transform 0.3s ease' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> */}
|
||||
|
||||
|
||||
{/* ========== AI 创作入口区域 ========== */}
|
||||
<div className="animate-fadeInUp" style={{
|
||||
|
||||
Reference in New Issue
Block a user