首页
This commit is contained in:
+98
-98
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -28,8 +28,8 @@
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="/assets/index-COTSz9Xd.js"></script>
|
<script type="module" crossorigin src="/assets/index-BpyoCjCy.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-eLRg4pQk.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-eN1cM2Wc.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import AuthorizationPage from './pages/AuthorizationPage';
|
|||||||
import MaterialListPage from './pages/MaterialListPage';
|
import MaterialListPage from './pages/MaterialListPage';
|
||||||
import RemoveInfo from './pages/RemoveInfo';
|
import RemoveInfo from './pages/RemoveInfo';
|
||||||
import RemoveRw from './pages/RemoveRw';
|
import RemoveRw from './pages/RemoveRw';
|
||||||
|
import HomePage from './pages/HomePage';
|
||||||
// import RemoveFenbu from './pages/RemoveFenbu';
|
// import RemoveFenbu from './pages/RemoveFenbu';
|
||||||
import ConsumePage from './pages/ConsumePage';
|
import ConsumePage from './pages/ConsumePage';
|
||||||
import { useAuthStore } from './store/useAuthStore';
|
import { useAuthStore } from './store/useAuthStore';
|
||||||
@@ -99,6 +100,7 @@ const App = () => {
|
|||||||
<Route path="order-records" element={<UserCenterPage />} />
|
<Route path="order-records" element={<UserCenterPage />} />
|
||||||
<Route path="user-center" element={<UserCenterPage />} />
|
<Route path="user-center" element={<UserCenterPage />} />
|
||||||
<Route path="messages" element={<MessagesPage />} />
|
<Route path="messages" element={<MessagesPage />} />
|
||||||
|
<Route path="home" element={<HomePage />} />
|
||||||
<Route path="conversation" element={<GenerateConver />} />
|
<Route path="conversation" element={<GenerateConver />} />
|
||||||
<Route path="initial" element={<InitialReplication />} />
|
<Route path="initial" element={<InitialReplication />} />
|
||||||
<Route path="initial/:creatID/initialinfo" element={<InitialInfo />} />
|
<Route path="initial/:creatID/initialinfo" element={<InitialInfo />} />
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ export async function verifySms(phone: string, code: string): Promise<{ token: s
|
|||||||
return api.post('/sms/verify', { phone, code }, false);
|
return api.post('/sms/verify', { phone, code }, false);
|
||||||
}
|
}
|
||||||
// ── Notifications ─────────────────────────────────────────
|
// ── Notifications ─────────────────────────────────────────
|
||||||
export async function getNotifications(page = 1, pageSize = 20, isRead?: boolean): Promise<{ items: AdminNotification[], total: number }> {
|
export async function getNotifications(page = 1, pageSize = 20, isRead?: boolean): Promise<{ items: AdminNotification[], total: number, credits?: { balance: number } }> {
|
||||||
if (USE_MOCK) {
|
if (USE_MOCK) {
|
||||||
const items = await mock.mockGetAdminNotifications();
|
const items = await mock.mockGetAdminNotifications();
|
||||||
return { items, total: items.length };
|
return { items, total: items.length };
|
||||||
@@ -679,3 +679,12 @@ export async function getResourcesMaterialList(params: ResourcesMaterialListPara
|
|||||||
if (params.page_size !== undefined) query.set('page_size', String(params.page_size));
|
if (params.page_size !== undefined) query.set('page_size', String(params.page_size));
|
||||||
return api.get(`/resources-material/list?${query.toString()}`);
|
return api.get(`/resources-material/list?${query.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// home 获取各模块媒体
|
||||||
|
export async function getmedit(limit:number): Promise<any> {
|
||||||
|
return api.get(`/recent-generations?limit=${limit}&modules=project&modules=chat_ai&modules=hot_opening_replicate&modules=shot_replicate`);
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.4 MiB |
@@ -413,14 +413,19 @@ const AppLayout: React.FC = () => {
|
|||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.06) 0%, rgba(139, 92, 246, 0.04) 100%)',
|
background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.06) 0%, rgba(139, 92, 246, 0.04) 100%)',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 14, cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
navigate('/home');
|
||||||
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
width: 42, height: 42, borderRadius: 14, flexShrink: 0,
|
width: 42, height: 42, borderRadius: 14, flexShrink: 0,
|
||||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a78bfa 100%)',
|
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a78bfa 100%)',
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
boxShadow: '0 4px 16px rgba(99, 102, 241, 0.35)',
|
boxShadow: '0 4px 16px rgba(99, 102, 241, 0.35)',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}
|
||||||
|
|
||||||
|
>
|
||||||
{siteLogo ? (
|
{siteLogo ? (
|
||||||
<img src={siteLogo} alt="logo" style={{ width: 28, height: 28, objectFit: 'contain' }} />
|
<img src={siteLogo} alt="logo" style={{ width: 28, height: 28, objectFit: 'contain' }} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -24,18 +24,30 @@ const NotificationPopup: React.FC = () => {
|
|||||||
const [notifications, setNotifications] = useState<Notification[]>([]);
|
const [notifications, setNotifications] = useState<Notification[]>([]);
|
||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
const refreshUser = useAuthStore((state) => state.refreshUser);
|
const refreshUser = useAuthStore((state) => state.refreshUser);
|
||||||
|
const setUserCredits = useAuthStore((state) => state.setUserCredits);
|
||||||
|
|
||||||
const fetchNotifications = useCallback(async () => {
|
const fetchNotifications = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const result = await getNotifications(1, 20, false);
|
const result = await getNotifications(1, 20, false);
|
||||||
|
// 接口返回结构:{ credits: { balance: 4317.23 }, items: [...], total: N }
|
||||||
const data = result.items || [];
|
const data = result.items || [];
|
||||||
|
|
||||||
|
// 同步积分:result.credits.balance 与当前 user.credits 不一致时,直接替换
|
||||||
|
const newBalance = result?.credits?.balance;
|
||||||
|
if (typeof newBalance === 'number') {
|
||||||
|
const currentUser = useAuthStore.getState().user;
|
||||||
|
if (currentUser && currentUser.credits !== newBalance) {
|
||||||
|
setUserCredits(newBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setNotifications(data);
|
setNotifications(data);
|
||||||
if (data.length > 0 && !visible) {
|
if (data.length > 0 && !visible) {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
setCurrentIndex(0);
|
setCurrentIndex(0);
|
||||||
}
|
}
|
||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
}, [visible]);
|
}, [visible, setUserCredits]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchNotifications();
|
fetchNotifications();
|
||||||
|
|||||||
@@ -200,6 +200,30 @@ html, body {
|
|||||||
.recharge-card .recharge-price { transition: all 0.3s ease; }
|
.recharge-card .recharge-price { transition: all 0.3s ease; }
|
||||||
.recharge-card:hover .recharge-price { transform: scale(1.08); }
|
.recharge-card:hover .recharge-price { transform: scale(1.08); }
|
||||||
|
|
||||||
|
/* ── Homepage tabs ────────────────────── */
|
||||||
|
.homepage-tabs .ant-tabs-nav {
|
||||||
|
margin-bottom: 8px !important;
|
||||||
|
}
|
||||||
|
.homepage-tabs .ant-tabs-tab {
|
||||||
|
padding: 8px 16px !important;
|
||||||
|
font-size: 13px !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
color: #6b7280 !important;
|
||||||
|
transition: all 0.25s !important;
|
||||||
|
}
|
||||||
|
.homepage-tabs .ant-tabs-tab:hover {
|
||||||
|
color: #6366f1 !important;
|
||||||
|
}
|
||||||
|
.homepage-tabs .ant-tabs-tab-active .ant-tabs-tab-btn {
|
||||||
|
color: #6366f1 !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
}
|
||||||
|
.homepage-tabs .ant-tabs-ink-bar {
|
||||||
|
background: linear-gradient(90deg, #6366f1, #a855f7) !important;
|
||||||
|
height: 3px !important;
|
||||||
|
border-radius: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Project card hover ────────────────── */
|
/* ── Project card hover ────────────────── */
|
||||||
.project-card {
|
.project-card {
|
||||||
transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||||
@@ -440,3 +464,19 @@ html, body {
|
|||||||
/* Card grid */
|
/* Card grid */
|
||||||
.card-grid { grid-template-columns: repeat(3, 1fr) !important; }
|
.card-grid { grid-template-columns: repeat(3, 1fr) !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── HomePage Tabs ────────────── */
|
||||||
|
.homepage-tabs .ant-tabs-nav { margin-bottom: 0; }
|
||||||
|
.homepage-tabs .ant-tabs-tab {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 8px 20px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
.homepage-tabs .ant-tabs-tab-active {
|
||||||
|
color: #6366f1;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.homepage-tabs .ant-tabs-ink-bar {
|
||||||
|
background: linear-gradient(135deg, #6366f1, #8b5cf6);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1603,14 +1603,15 @@ const AIChatPage: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
size="middle"
|
size="middle"
|
||||||
>
|
>
|
||||||
<Option value="image">
|
|
||||||
<PictureOutlined style={{ marginRight: 6, fontSize: 14 }} />
|
|
||||||
<span style={{ fontSize: 14, fontWeight: 500, color: '#64748b' }}>图片</span>
|
|
||||||
</Option>
|
|
||||||
<Option value="video">
|
<Option value="video">
|
||||||
<VideoCameraOutlined style={{ marginRight: 6, fontSize: 14 }} />
|
<VideoCameraOutlined style={{ marginRight: 6, fontSize: 14 }} />
|
||||||
<span style={{ fontSize: 14, fontWeight: 500, color: '#64748b' }}>视频</span>
|
<span style={{ fontSize: 14, fontWeight: 500, color: '#64748b' }}>视频</span>
|
||||||
</Option>
|
</Option>
|
||||||
|
<Option value="image">
|
||||||
|
<PictureOutlined style={{ marginRight: 6, fontSize: 14 }} />
|
||||||
|
<span style={{ fontSize: 14, fontWeight: 500, color: '#64748b' }}>图片</span>
|
||||||
|
</Option>
|
||||||
|
|
||||||
</Select>
|
</Select>
|
||||||
{/* </div> */}
|
{/* </div> */}
|
||||||
|
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ const GeneratePage: React.FC = () => {
|
|||||||
const [lastTextTokens, setLastTextTokens] = useState(0);
|
const [lastTextTokens, setLastTextTokens] = useState(0);
|
||||||
|
|
||||||
// Media type: 1 for image, 2 for video
|
// Media type: 1 for image, 2 for video
|
||||||
const [mediaType, setMediaType] = useState<any>("image");
|
const [mediaType, setMediaType] = useState<any>("video");
|
||||||
|
|
||||||
// Image parameters
|
// Image parameters
|
||||||
const [selectedRatio, setSelectedRatio] = useState<string>("1:1");
|
const [selectedRatio, setSelectedRatio] = useState<string>("1:1");
|
||||||
@@ -1400,7 +1400,14 @@ const GeneratePage: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div
|
||||||
|
style={{
|
||||||
|
margin: '-24px -32px -32px',
|
||||||
|
borderRadius: 20,
|
||||||
|
height: 'calc(100vh - 34px)',
|
||||||
|
padding:'24px'
|
||||||
|
|
||||||
|
}}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div
|
<div
|
||||||
className="gen-header animate-fadeInUp"
|
className="gen-header animate-fadeInUp"
|
||||||
@@ -1531,16 +1538,10 @@ const GeneratePage: React.FC = () => {
|
|||||||
|
|
||||||
{/* 单选按钮组 */}
|
{/* 单选按钮组 */}
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
defaultValue="image"
|
defaultValue="video"
|
||||||
buttonStyle="solid"
|
buttonStyle="solid"
|
||||||
onChange={handleMediaTypeChange}
|
onChange={handleMediaTypeChange}
|
||||||
>
|
>
|
||||||
{/* 图片选项 */}
|
|
||||||
<Radio.Button value="image" style={{ zIndex: 0 }}>
|
|
||||||
<PictureOutlined style={{ marginRight: 6, fontSize: 14 }} />{" "}
|
|
||||||
{/* 图片图标 */}
|
|
||||||
图片
|
|
||||||
</Radio.Button>
|
|
||||||
{/* 视频选项 */}
|
{/* 视频选项 */}
|
||||||
<Radio.Button value="video">
|
<Radio.Button value="video">
|
||||||
<VideoCameraOutlined
|
<VideoCameraOutlined
|
||||||
@@ -1549,6 +1550,13 @@ const GeneratePage: React.FC = () => {
|
|||||||
{/* 视频图标 */}
|
{/* 视频图标 */}
|
||||||
视频
|
视频
|
||||||
</Radio.Button>
|
</Radio.Button>
|
||||||
|
{/* 图片选项 */}
|
||||||
|
<Radio.Button value="image" style={{ zIndex: 0 }}>
|
||||||
|
<PictureOutlined style={{ marginRight: 6, fontSize: 14 }} />{" "}
|
||||||
|
{/* 图片图标 */}
|
||||||
|
图片
|
||||||
|
</Radio.Button>
|
||||||
|
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</div>
|
</div>
|
||||||
{mediaType === "video" &&
|
{mediaType === "video" &&
|
||||||
|
|||||||
@@ -0,0 +1,832 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Button, Input, Tabs, Tag, Upload, message } from 'antd';
|
||||||
|
import {
|
||||||
|
FileTextOutlined,
|
||||||
|
ScissorOutlined,
|
||||||
|
RobotOutlined,
|
||||||
|
ArrowRightOutlined,
|
||||||
|
UploadOutlined,
|
||||||
|
VideoCameraOutlined,
|
||||||
|
PictureOutlined,
|
||||||
|
ThunderboltOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { getmedit } from '../api';
|
||||||
|
|
||||||
|
import hot from '../assets/homebtn1.png';
|
||||||
|
import mashup from '../assets/homebtn2.png';
|
||||||
|
import aicreate from '../assets/homebtn3.png';
|
||||||
|
|
||||||
|
|
||||||
|
// 把 ISO 时间格式化成 MM-DD HH:mm(与图片一致)
|
||||||
|
function formatShortDate(iso?: string | null): string {
|
||||||
|
if (!iso) return '-';
|
||||||
|
let s = String(iso).trim();
|
||||||
|
if (!s.includes('T')) s = s.replace(' ', 'T');
|
||||||
|
const dotIdx = s.indexOf('.');
|
||||||
|
if (dotIdx > 0) s = s.slice(0, dotIdx);
|
||||||
|
s = s.replace(/[+-]\d{2}:?\d{0,2}$/, '').replace(/Z$/, '');
|
||||||
|
// s 形如 2026-06-18T17:17:00
|
||||||
|
const [datePart = '', timePart = ''] = s.split('T');
|
||||||
|
const [, month = '', day = ''] = datePart.split('-');
|
||||||
|
const hhmm = timePart.slice(0, 5);
|
||||||
|
if (!month || !day) return '-';
|
||||||
|
return `${month}-${day} ${hhmm}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HomePage: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [activeTab, setActiveTab] = useState('project');
|
||||||
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
const [mockVideos, setMockVideos] = useState<any[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchAll = async () => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
getmedit(5).then((res) => {
|
||||||
|
console.log('getmedit:', res);
|
||||||
|
for (let item in res) {
|
||||||
|
res[item].forEach(element => {
|
||||||
|
// 给每条 element 标记它所属的模块(item 是接口返回的 key)
|
||||||
|
element.type = item;
|
||||||
|
// 依次追加到 mockVideos(函数式更新,保证拿到最新 state)
|
||||||
|
setMockVideos(prev => [...prev, element]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 最终再打印一次汇总(state 异步更新,这里打印的是本次追加的累计长度)
|
||||||
|
setMockVideos(prev => {
|
||||||
|
console.log('mockVideos 最终条数:', prev.length);
|
||||||
|
return prev;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('HomePage fetch error:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchAll();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleTabChange = (key: string) => {
|
||||||
|
setActiveTab(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const aiEntries = [
|
||||||
|
{
|
||||||
|
icon: <FileTextOutlined style={{ fontSize: 24, color: '#6366f1' }} />,
|
||||||
|
title: '爆款开头复刻',
|
||||||
|
description: '一键复刻热门视频开篇,快速替换自有商品素材',
|
||||||
|
action: '立即创作',
|
||||||
|
path: '/initial',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <ScissorOutlined style={{ fontSize: 24, color: '#f97316' }} />,
|
||||||
|
title: '批量混剪',
|
||||||
|
description: '多素材批量自动剪辑,智能筛选高清优质镜头片段',
|
||||||
|
action: '开始混剪',
|
||||||
|
path: '/removelens',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <RobotOutlined style={{ fontSize: 24, color: '#10b981' }} />,
|
||||||
|
title: 'AI成片',
|
||||||
|
description: 'AI全自动快速出片,支持文案生成/上传参考素材制作',
|
||||||
|
action: '立即生成',
|
||||||
|
path: '/conversation',
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{ key: 'all', label: '全部' },
|
||||||
|
{ key: 'project', label: '项目媒体' },
|
||||||
|
{ key: 'chatAi', label: 'AI成片' },
|
||||||
|
{ key: 'hotOpeningReplicate', label: '爆款复刻' },
|
||||||
|
{ key: 'shotReplicate', label: '拆镜复刻' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const filteredVideos = activeTab === 'all'
|
||||||
|
? mockVideos
|
||||||
|
: mockVideos.filter(v => v.type === activeTab);
|
||||||
|
|
||||||
|
const materialCases = [
|
||||||
|
'https://trae-api-cn.mchost.guru/api/ide/v1/text_to_image?prompt=modern%20city%20skyline%20night%20view&image_size=landscape_16_9',
|
||||||
|
'https://trae-api-cn.mchost.guru/api/ide/v1/text_to_image?prompt=nature%20forest%20landscape%20sunlight&image_size=landscape_16_9',
|
||||||
|
'https://trae-api-cn.mchost.guru/api/ide/v1/text_to_image?prompt=abstract%20technology%20background%20digital&image_size=landscape_16_9',
|
||||||
|
'https://trae-api-cn.mchost.guru/api/ide/v1/text_to_image?prompt=food%20cooking%20kitchen%20delicious&image_size=landscape_16_9',
|
||||||
|
'https://trae-api-cn.mchost.guru/api/ide/v1/text_to_image?prompt=fashion%20clothing%20style%20elegant&image_size=landscape_16_9',
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
margin: '-24px -32px -32px',
|
||||||
|
borderRadius: 20,
|
||||||
|
height: 'calc(100vh - 34px)',
|
||||||
|
overflow: 'auto',
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
padding: '0 28px 32px',
|
||||||
|
}}>
|
||||||
|
{/* ========== 顶部工作台引导区域(三步流程) ========== */}
|
||||||
|
<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: '24px 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 }}>
|
||||||
|
我的项目
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div >
|
||||||
|
<span style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: 12, color: '#1a50bbff', letterSpacing: 0.3,
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
navigate('/authorization')
|
||||||
|
}}
|
||||||
|
>一键授权
|
||||||
|
<ArrowRightOutlined style={{ marginLeft: 8, transform: 'rotate(0deg)' }} />
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</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>
|
||||||
|
{/* 插图占位:项目卡(标题/描述输入框 + 行业分类 chip) */}
|
||||||
|
<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>
|
||||||
|
{/* 行业分类 chip */}
|
||||||
|
<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 style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '5px 8px', background: '#fff7ed', border: '1px solid #fed7aa', borderRadius: 5 }}>
|
||||||
|
<div style={{ width: 14, height: 14, borderRadius: '50%', background: '#f97316', color: '#fff', fontSize: 10, display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700 }}>!</div>
|
||||||
|
<div style={{ fontSize: 10, color: '#9a3412', fontWeight: 500 }}>参考素材 0/3</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',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 高光层(hover 时轻微变亮) */}
|
||||||
|
<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 stagger-children" style={{
|
||||||
|
padding: '24px 28px',
|
||||||
|
borderRadius: 16,
|
||||||
|
background: '#fff',
|
||||||
|
border: '1px solid #e2e8f0',
|
||||||
|
marginBottom: 20,
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
marginBottom: 18,
|
||||||
|
}}>
|
||||||
|
<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 }}>
|
||||||
|
AI 创作入口
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: '#94a3b8', marginLeft: 8 }}>
|
||||||
|
一键开启智能创作
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: 16 }}>
|
||||||
|
{aiEntries.map((entry, index) => {
|
||||||
|
// 三个入口用三种不同色调的渐变光晕作为视觉区分,但都保持白底卡片
|
||||||
|
const accentMap = [
|
||||||
|
{ color: '#6366f1', light: 'rgba(99,102,241,0.10)', tag: '复刻', bg: hot },
|
||||||
|
{ color: '#f97316', light: 'rgba(249,115,22,0.10)', tag: '混剪', bg: mashup },
|
||||||
|
{ color: '#10b981', light: 'rgba(16,185,129,0.10)', tag: '云创', bg: aicreate },
|
||||||
|
];
|
||||||
|
const accent = accentMap[index] || accentMap[0];
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
onClick={() => navigate(entry.path)}
|
||||||
|
className="project-card"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
padding: '20px 22px',
|
||||||
|
borderRadius: 14,
|
||||||
|
background: '#fff',
|
||||||
|
border: '1px solid #e2e8f0',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden',
|
||||||
|
backgroundImage: `url(${accent.bg})`,
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundSize: '100% 100%',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.borderColor = accent.color;
|
||||||
|
e.currentTarget.style.boxShadow = `0 12px 32px ${accent.light}`;
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.borderColor = '#e2e8f0';
|
||||||
|
e.currentTarget.style.boxShadow = 'none';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 顶部装饰光带 */}
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0, left: 0, right: 0, height: 3,
|
||||||
|
background: `linear-gradient(90deg, ${accent.color}, ${accent.color}88)`,
|
||||||
|
}} />
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
|
||||||
|
<div style={{
|
||||||
|
width: 48, height: 48,
|
||||||
|
borderRadius: 12,
|
||||||
|
background: accent.light,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}>
|
||||||
|
<span style={{ color: accent.color, fontSize: 22, display: 'flex' }}>{entry.icon}</span>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 11, color: accent.color,
|
||||||
|
padding: '2px 8px', borderRadius: 6,
|
||||||
|
background: accent.light, fontWeight: 600,
|
||||||
|
}}>{accent.tag}</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 16, fontWeight: 700, color: '#1f2937', marginBottom: 4 }}>
|
||||||
|
{entry.title}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: '#6b7280', marginBottom: 14, lineHeight: 1.5, minHeight: 36 }}>
|
||||||
|
{entry.description}
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
color: accent.color,
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 600,
|
||||||
|
}}>
|
||||||
|
{entry.action}
|
||||||
|
<ArrowRightOutlined style={{ fontSize: 12 }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ========== 近期作品区域 ========== */}
|
||||||
|
<div className="animate-fadeInUp" style={{
|
||||||
|
padding: '24px 28px',
|
||||||
|
borderRadius: 16,
|
||||||
|
background: '#fff',
|
||||||
|
border: '1px solid #e2e8f0',
|
||||||
|
marginBottom: 20,
|
||||||
|
}}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||||
|
<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 }}>
|
||||||
|
近期作品
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{/* Tab切换 */}
|
||||||
|
<div style={{ marginBottom: 20 }}>
|
||||||
|
<Tabs
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
items={tabs.map(tab => ({
|
||||||
|
key: tab.key,
|
||||||
|
label: tab.label,
|
||||||
|
}))}
|
||||||
|
className="homepage-tabs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 视频网格 */}
|
||||||
|
<div className="stagger-children" style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 16 }}>
|
||||||
|
{filteredVideos.length === 0 ? (
|
||||||
|
<div style={{
|
||||||
|
gridColumn: '1 / -1',
|
||||||
|
padding: '60px 0',
|
||||||
|
textAlign: 'center',
|
||||||
|
color: '#94a3b8',
|
||||||
|
fontSize: 14,
|
||||||
|
}}>
|
||||||
|
<PictureOutlined style={{ fontSize: 36, color: '#cbd5e1', marginBottom: 8 }} />
|
||||||
|
<div>暂无作品</div>
|
||||||
|
</div>
|
||||||
|
) : filteredVideos.map((video) => (
|
||||||
|
<div
|
||||||
|
key={video.id || `${video.type}-${Math.random()}`}
|
||||||
|
className="project-card"
|
||||||
|
onClick={() => {
|
||||||
|
// 按模块分发跳转:
|
||||||
|
// - 爆款复刻(hot)→ 复刻详情页
|
||||||
|
// - AI 成片(ai)→ 对话/生成页
|
||||||
|
// - 项目记录(project)→ 项目详情页
|
||||||
|
const id = video.moduleProjectId;
|
||||||
|
if (video.type === 'hotOpeningReplicate' && id != null) {
|
||||||
|
navigate(`/initial/${id}/initialinfo`);
|
||||||
|
} else if (video.type === 'shotReplicate' && id != null) {
|
||||||
|
navigate(`/removelens/${id}/removefenbu`);
|
||||||
|
} else if (video.type === 'chatAi') {
|
||||||
|
navigate(`/conversation`);
|
||||||
|
} else if (video.type === 'project') {
|
||||||
|
navigate(`/project`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
borderRadius: 12,
|
||||||
|
overflow: 'hidden',
|
||||||
|
cursor: 'pointer',
|
||||||
|
background: '#fff',
|
||||||
|
border: '1px solid #e2e8f0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
position: 'relative',
|
||||||
|
aspectRatio: '16/9',
|
||||||
|
// background: '#1a1a2e',
|
||||||
|
}}>
|
||||||
|
{(() => {
|
||||||
|
// 1) 读取后端 API 基础地址;环境变量未配置时降级到本地 8000
|
||||||
|
const apiBase = (import.meta.env.VITE_API_BASE as string) || 'http://localhost:8000';
|
||||||
|
|
||||||
|
// 2) 判断当前作品是否为"图片":
|
||||||
|
// - 爆款复刻(type === 'hot')始终是视频,不参与图片判断
|
||||||
|
// - 其他模块(项目记录 / AI 成片)根据 genType 判定
|
||||||
|
// - genType 可能是字符串 'image',也可能是数字 1(兼容两种后端约定)
|
||||||
|
const isImage = video.type !== 'hotOpeningReplicate'
|
||||||
|
&& (
|
||||||
|
String(video.resourceType ?? '').toLowerCase() === 'image'
|
||||||
|
|| video.resourceType === 1
|
||||||
|
|| String(video.resourceType ?? '') === '1'
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3) 根据媒体类型选择对应的资源路径:
|
||||||
|
// - 图片:后端返回的 imageUrl 已经是带签名的完整相对路径
|
||||||
|
// 形如 /static/generate/images/2026/06/26/0019f017f5924df4123.png?exp=...&sign=...&w=300&p=50
|
||||||
|
// - 视频:使用视频封面 videoCoverUrl(这是视频作品的静态缩略图)
|
||||||
|
// - 爆款复刻(type === 'hot')特殊处理:使用 finalVideoCoverUrl
|
||||||
|
let rawPath = '';
|
||||||
|
if (isImage) {
|
||||||
|
rawPath = '/static' + video.resultUrl + '&w=300&p=50' || '';
|
||||||
|
} else if (video.type === 'hot') {
|
||||||
|
rawPath = video.coverUrl || video.resultUrl || video.resultUrl || '';
|
||||||
|
} else {
|
||||||
|
rawPath = video.coverUrl || video.resultUrl || video.resultUrl || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) 拼装最终 src:
|
||||||
|
// - rawPath 为空 → 用空串(让 <img> 走 onError 兜底)
|
||||||
|
// - 已经是 http(s) 完整 URL → 直接使用(OSS / CDN 场景)
|
||||||
|
// - 否则视为后端相对路径,前面拼 apiBase
|
||||||
|
const src = rawPath
|
||||||
|
? (rawPath.startsWith('http') ? rawPath : apiBase + rawPath)
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={src}
|
||||||
|
alt={video.title || video.name || '作品'}
|
||||||
|
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
{(() => {
|
||||||
|
const isImage = String(video.resourceType ?? '').toLowerCase() === 'image';
|
||||||
|
if (isImage) return null;
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
background: 'rgba(0,0,0,0.2)',
|
||||||
|
}}>
|
||||||
|
<VideoCameraOutlined style={{ fontSize: 28, color: '#fff' }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
padding: '10px 12px',
|
||||||
|
background: '#f8fafc',
|
||||||
|
}}>
|
||||||
|
{/* <div style={{
|
||||||
|
fontSize: 13,
|
||||||
|
color: '#334155',
|
||||||
|
fontWeight: 500,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
}}>
|
||||||
|
{video.title || video.name || video.originalPrompt || video.prompt || '未命名作品'}
|
||||||
|
</div> */}
|
||||||
|
<div style={{
|
||||||
|
marginTop: 6,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#64748b',
|
||||||
|
|
||||||
|
|
||||||
|
}}>
|
||||||
|
{(() => {
|
||||||
|
// 显示所属模块,而非媒体类型
|
||||||
|
const moduleMap: Record<string, string> = {
|
||||||
|
project: '项目媒体',
|
||||||
|
chatAi: 'AI成片',
|
||||||
|
hotOpeningReplicate: '爆款复刻',
|
||||||
|
shotReplicate: '拆镜复刻',
|
||||||
|
};
|
||||||
|
const moduleLabel = moduleMap[video.type] || '其他';
|
||||||
|
return (
|
||||||
|
<span style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
padding: '1px 6px',
|
||||||
|
border: '1px solid #3b82f6',
|
||||||
|
borderRadius: 4,
|
||||||
|
color: '#3b82f6',
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 500,
|
||||||
|
background: '#fff',
|
||||||
|
lineHeight: 1.4,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}>
|
||||||
|
{moduleLabel}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
<span style={{ color: '#94a3b8' }}>·</span>
|
||||||
|
<span style={{ whiteSpace: 'nowrap' }}>{formatShortDate(video.generatedTime)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{/* ========== 素材案例区域 ========== */}
|
||||||
|
<div className="animate-fadeInUp" style={{
|
||||||
|
padding: '24px 28px',
|
||||||
|
borderRadius: 16,
|
||||||
|
background: '#fff',
|
||||||
|
border: '1px solid #e2e8f0',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: 18,
|
||||||
|
}}>
|
||||||
|
<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 }}>
|
||||||
|
素材案例
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: '#94a3b8', marginLeft: 4 }}>
|
||||||
|
精选优质作品参考
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 13, color: '#6366f1', cursor: 'pointer', fontWeight: 500,
|
||||||
|
display: 'flex', alignItems: 'center', gap: 2,
|
||||||
|
}}>
|
||||||
|
更多案例
|
||||||
|
<ArrowRightOutlined style={{ fontSize: 11 }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="stagger-children" style={{ display: 'flex', gap: 14 }}>
|
||||||
|
{materialCases.map((caseUrl, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="project-card"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
aspectRatio: '16/9',
|
||||||
|
borderRadius: 12,
|
||||||
|
overflow: 'hidden',
|
||||||
|
cursor: 'pointer',
|
||||||
|
background: '#f1f5f9',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
const overlay = e.currentTarget.querySelector('.case-overlay') as HTMLElement | null;
|
||||||
|
if (overlay) overlay.style.opacity = '1';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
const overlay = e.currentTarget.querySelector('.case-overlay') as HTMLElement | null;
|
||||||
|
if (overlay) overlay.style.opacity = '0';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={caseUrl}
|
||||||
|
alt={`素材案例 ${index + 1}`}
|
||||||
|
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||||
|
/>
|
||||||
|
{/* 案例悬停遮罩(由父级 hover 触发) */}
|
||||||
|
<div
|
||||||
|
className="case-overlay"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
background: 'linear-gradient(180deg, transparent 40%, rgba(99,102,241,0.75) 100%)',
|
||||||
|
opacity: 0,
|
||||||
|
transition: 'opacity 0.3s',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: 14,
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 600,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
案例 {index + 1}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HomePage;
|
||||||
@@ -77,7 +77,7 @@ const LoginPage: React.FC = () => {
|
|||||||
await login(values.phone, values.password, undefined, values.rememberMe);
|
await login(values.phone, values.password, undefined, values.rememberMe);
|
||||||
message.success('登录成功,欢迎回来');
|
message.success('登录成功,欢迎回来');
|
||||||
await checkAuth();
|
await checkAuth();
|
||||||
navigate('/projects');
|
navigate('/home');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const errorMsg = error?.response?.data?.detail || error?.response?.data?.message || error?.message || '登录失败';
|
const errorMsg = error?.response?.data?.detail || error?.response?.data?.message || error?.message || '登录失败';
|
||||||
message.error(errorMsg);
|
message.error(errorMsg);
|
||||||
@@ -98,7 +98,7 @@ const LoginPage: React.FC = () => {
|
|||||||
await phonelogin(values.phone, values.code);
|
await phonelogin(values.phone, values.code);
|
||||||
message.success('登录成功,欢迎回来');
|
message.success('登录成功,欢迎回来');
|
||||||
await checkAuth();
|
await checkAuth();
|
||||||
navigate('/projects');
|
navigate('/home');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const errorMsg = error?.response?.data?.detail || error?.response?.data?.message || error?.message || '登录失败';
|
const errorMsg = error?.response?.data?.detail || error?.response?.data?.message || error?.message || '登录失败';
|
||||||
message.error(errorMsg);
|
message.error(errorMsg);
|
||||||
@@ -115,7 +115,7 @@ const LoginPage: React.FC = () => {
|
|||||||
const user = await register(values.phone, values.regCode, values.password);
|
const user = await register(values.phone, values.regCode, values.password);
|
||||||
message.success('注册成功');
|
message.success('注册成功');
|
||||||
await checkAuth();
|
await checkAuth();
|
||||||
navigate('/projects');
|
navigate('/home');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const errorMsg = error?.response?.data?.detail || error?.response?.data?.message || error?.message || '注册失败';
|
const errorMsg = error?.response?.data?.detail || error?.response?.data?.message || error?.message || '注册失败';
|
||||||
message.error(errorMsg);
|
message.error(errorMsg);
|
||||||
|
|||||||
@@ -97,7 +97,15 @@ const ProjectsPage: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
margin: '-24px -32px -32px',
|
||||||
|
borderRadius: 20,
|
||||||
|
height: 'calc(100vh - 34px)',
|
||||||
|
padding:'24px'
|
||||||
|
}
|
||||||
|
}>
|
||||||
{/* Header banner */}
|
{/* Header banner */}
|
||||||
<div className="animate-fadeInUp" style={{
|
<div className="animate-fadeInUp" style={{
|
||||||
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
||||||
@@ -116,6 +124,10 @@ const ProjectsPage: React.FC = () => {
|
|||||||
background: 'rgba(255,255,255,0.15)', border: '1px solid rgba(255,255,255,0.3)',
|
background: 'rgba(255,255,255,0.15)', border: '1px solid rgba(255,255,255,0.3)',
|
||||||
color: '#fff', fontWeight: 600, backdropFilter: 'blur(10px)', borderRadius: 10, height: 42,
|
color: '#fff', fontWeight: 600, backdropFilter: 'blur(10px)', borderRadius: 10, height: 42,
|
||||||
}}>新建项目</Button>
|
}}>新建项目</Button>
|
||||||
|
<Button icon={<PlusOutlined />} onClick={() => navigate('/records')} size="large" style={{
|
||||||
|
background: 'rgba(255,255,255,0.15)', border: '1px solid rgba(255,255,255,0.3)',
|
||||||
|
color: '#fff', fontWeight: 600, backdropFilter: 'blur(10px)', borderRadius: 10, height: 42,
|
||||||
|
}}>项目记录</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{projects.length === 0 ? (
|
{projects.length === 0 ? (
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const useAppStore = create<AppState>((set, get) => ({
|
|||||||
loading: false,
|
loading: false,
|
||||||
|
|
||||||
// 生成配置状态初始值
|
// 生成配置状态初始值
|
||||||
mediaType: 'image',
|
mediaType: 'video',
|
||||||
countType: '请选择',
|
countType: '请选择',
|
||||||
selectedRatio: '1:1',
|
selectedRatio: '1:1',
|
||||||
selectedResolution: '2K',
|
selectedResolution: '2K',
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface AuthState {
|
|||||||
checkAuth: () => Promise<void>;
|
checkAuth: () => Promise<void>;
|
||||||
changePassword: (oldPwd: string, newPwd: string) => Promise<void>;
|
changePassword: (oldPwd: string, newPwd: string) => Promise<void>;
|
||||||
refreshUser: () => Promise<void>;
|
refreshUser: () => Promise<void>;
|
||||||
|
setUserCredits: (credits: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAuthStore = create<AuthState>((set) => ({
|
export const useAuthStore = create<AuthState>((set) => ({
|
||||||
@@ -52,4 +53,10 @@ export const useAuthStore = create<AuthState>((set) => ({
|
|||||||
console.error('Failed to refresh user:', error);
|
console.error('Failed to refresh user:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 用通知接口返回的 credits.balance 直接覆盖当前 user.credits
|
||||||
|
// 避免再请求一次 getUser 接口
|
||||||
|
setUserCredits: (credits: number) => {
|
||||||
|
set((state) => (state.user ? { user: { ...state.user, credits } } : {}));
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user