资源存储

This commit is contained in:
孙佳艺
2026-06-30 09:10:27 +08:00
parent 92fb64aa99
commit 66c3328e23
10 changed files with 1723 additions and 333 deletions
+12
View File
@@ -27,6 +27,18 @@ const MOCK_USER: User = {
username: 'videomaker',
email: 'demo@videogen.ai',
credits: 2680,
resourceCapacity: {
enabled: true,
source: 'user',
hasUserConfig: true,
usedBytes: 298161654,
availableBytes: 300349549066,
totalBytes: 300647710720,
usagePercent: 0.1,
exceeded: false,
limitValue: '280.000',
limitUnit: 'GB',
},
};
const MOCK_CREDIT_RECORDS: CreditRecord[] = [
@@ -61,10 +61,146 @@ import {
} from '@ant-design/icons';
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
import { useAuthStore } from '../../store/useAuthStore';
import { getMenuConfigs, getRechargePackages, getPaymentMethods, createRechargeOrder, getPaymentOrder, cancelPaymentOrder, getSiteInfo, getUnreadCount, createContactRequest } from '../../api';
import { getMenuConfigs, getRechargePackages, getPaymentMethods, createRechargeOrder, getPaymentOrder, cancelPaymentOrder, getSiteInfo, getUnreadCount, createContactRequest, getUser } from '../../api';
import NotificationPopup from '../NotificationPopup';
import './AppLayout.css';
// ── ResourceCapacity 类型(与 src/types/index.ts 保持一致) ──
type ResourceCapacityData = {
enabled: boolean;
usedBytes: number;
totalBytes: number;
availableBytes: number;
usagePercent: number;
exceeded: boolean;
limitValue: string;
limitUnit: string;
};
/**
* 资源存储展示卡片
* - enabled === true:显示「已用 / 总额」+ 进度条(超额时变红并提示)
* - enabled === false:按 usedBytes 大小自动选 KB / MB / GB / TB 单位显示「当前使用」
*/
const StorageCard: React.FC<{ data: ResourceCapacityData | null }> = ({ data }) => {
if (!data) return null;
// 单位:后端返回的 limitUnitKB / MB / GB
const unit = (data.limitUnit || 'GB').toUpperCase();
const divisor = unit === 'GB' ? 1024 ** 3 : unit === 'MB' ? 1024 ** 2 : 1024;
// 百分比(可能 > 100
const rawPercent = Number(data.usagePercent) || 0;
// 进度条宽度最多 100%
const barPercent = Math.min(100, Math.max(0, rawPercent));
// 总额:优先用后端 limitValue,兜底 totalBytes / divisor
const total = data.limitValue
? parseFloat(data.limitValue)
: data.totalBytes / divisor;
// 已用 = 总额 × 百分比 / 100(保证数字与 percent 自洽)
const used = (total * rawPercent) / 100;
// 剩余 = 总额 - 已用
const available = total - used;
// 超额判断
const isOver = data.exceeded || rawPercent >= 100;
// enabled === false 时按 usedBytes 自动选单位
const KB = 1024;
const MB = 1024 ** 2;
const GB = 1024 ** 3;
const TB = 1024 ** 4;
const formatUsedAuto = (bytes: number) => {
const b = Number(bytes) || 0;
if (b >= TB) return { val: (b / TB).toFixed(2), unit: 'TB' };
if (b >= GB) return { val: (b / GB).toFixed(2), unit: 'GB' };
if (b >= MB) return { val: (b / MB).toFixed(2), unit: 'MB' };
return { val: (b / KB).toFixed(2), unit: 'KB' };
};
const usedAuto = formatUsedAuto(data.usedBytes);
return (
<div
style={{
marginTop: 12,
padding: '10px 12px',
borderRadius: 10,
background: 'linear-gradient(135deg, rgba(248, 250, 252, 0.9) 0%, rgba(241, 245, 249, 0.9) 100%)',
border: `1px solid ${isOver ? 'rgba(239, 68, 68, 0.25)' : 'rgba(99, 102, 241, 0.15)'}`,
boxShadow: '0 2px 12px rgba(0, 0, 0, 0.04)',
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 6,
fontSize: 12,
color: '#475569',
}}
>
<span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<DatabaseOutlined style={{ fontSize: 12, color: isOver ? '#ef4444' : '#6366f1' }} />
</span>
{data.enabled ? (
<span style={{ fontWeight: 500, color: isOver ? '#ef4444' : '#1e293b' }}>
{used.toFixed(2)} / {total.toFixed(2)} {unit}
</span>
) : (
<span style={{ fontWeight: 500, color: '#1e293b' }}>
使 {usedAuto.val} {usedAuto.unit}
</span>
)}
</div>
{data.enabled && (
<>
<div
style={{
width: '100%',
height: 6,
background: '#e2e8f0',
borderRadius: 3,
overflow: 'hidden',
}}
>
<div
style={{
width: `${barPercent}%`,
height: '100%',
background: isOver
? 'linear-gradient(90deg, #ef4444, #dc2626)'
: 'linear-gradient(90deg, #6366f1, #8b5cf6)',
borderRadius: 3,
transition: 'width 0.4s ease',
}}
/>
</div>
<div
style={{
marginTop: 4,
display: 'flex',
justifyContent: 'space-between',
fontSize: 11,
color: isOver ? '#ef4444' : '#94a3b8',
}}
>
<span>
{isOver
? `存储超额 · 超用 ${Math.abs(available).toFixed(2)} ${unit}`
: `剩余 ${available.toFixed(2)} ${unit}`}
</span>
<span>{rawPercent.toFixed(1)}%</span>
</div>
</>
)}
</div>
);
};
interface MenuConfig {
id: string;
key?: string;
@@ -179,6 +315,18 @@ const AppLayout: React.FC = () => {
const [contactHovered, setContactHovered] = useState(false);
const [submittingContact, setSubmittingContact] = useState(false);
// 资源存储容量(从 getUser().resource_capacity 获取)
const [resourceCapacity, setResourceCapacity] = useState<{
enabled: boolean;
usedBytes: number;
totalBytes: number;
availableBytes: number;
usagePercent: number;
exceeded: boolean;
limitValue: string;
limitUnit: string;
} | null>(null);
const PENDING_ORDER_KEY = 'pending_payment_order';
useEffect(() => {
@@ -207,6 +355,31 @@ const AppLayout: React.FC = () => {
}).catch(() => { });
}, []);
// 拉取用户资源容量信息
useEffect(() => {
getUser().then((res: any) => {
console.log('[Storage] getUser 返回:', res);
const rc = res?.resourceCapacity;
if (rc) {
setResourceCapacity({
enabled: !!rc.enabled,
usedBytes: Number(rc.usedBytes) || 0,
totalBytes: Number(rc.totalBytes) || 0,
availableBytes: Number(rc.availableBytes) || 0,
usagePercent: Number(rc.usagePercent) || 0,
exceeded: !!rc.exceeded,
limitValue: rc.limitValue ?? '',
limitUnit: rc.limitUnit || 'GB',
});
}
// 没数据时不显示
}).catch((err: any) => {
console.error('[Storage] getUser 失败:', err);
// 接口失败也不显示
});
}, []);
const loadUnreadCount = () => {
getUnreadCount().then(count => {
setUnreadCount(count);
@@ -620,6 +793,9 @@ const AppLayout: React.FC = () => {
</div>
</div>
</Dropdown>
{/* 资源存储容量展示(来自 getUser.resourceCapacity */}
<StorageCard data={resourceCapacity} />
</div>
</div>
@@ -714,6 +890,9 @@ const AppLayout: React.FC = () => {
</div>
</div>
{/* 资源存储容量展示(与桌面端共用 StorageCard */}
<StorageCard data={resourceCapacity} />
{topLevelItems.map(item => {
const menuType = item.menu_type ?? item.menuType;
const hasChildren = childMap[item.id] && childMap[item.id].length > 0;
+564 -158
View File
@@ -3,20 +3,26 @@
:root {
--font-sans: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--nav-bg: #f8f9fb;
--nav-surface: rgba(0,0,0,0.02);
--nav-surface: rgba(0, 0, 0, 0.02);
--nav-border: #e5e7eb;
--nav-text: #1f2937;
--nav-text-muted: #6b7280;
--nav-hover: rgba(0,0,0,0.04);
--nav-active: rgba(99,102,241,0.08);
--nav-hover: rgba(0, 0, 0, 0.04);
--nav-active: rgba(99, 102, 241, 0.08);
}
*,
*::before,
*::after { box-sizing: border-box; }
*::after {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html, body {
margin: 0; padding: 0;
html,
body {
margin: 0;
padding: 0;
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -24,23 +30,68 @@ html, body {
color: #1a1a2e;
letter-spacing: -0.01em;
}
#root { min-height: 100vh; }
#root {
min-height: 100vh;
}
/* ── Scrollbar ─────────────────────────── */
::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #9ca3af; }
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #d1d5db;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #9ca3af;
}
/* ── Electric Border Animation ─────────── */
@keyframes electric-pulse {
0%, 100% { opacity: 0.4; filter: blur(1px); }
50% { opacity: 1; filter: blur(0); }
0%,
100% {
opacity: 0.4;
filter: blur(1px);
}
50% {
opacity: 1;
filter: blur(0);
}
}
@keyframes electric-flow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.content_box {
margin: -24px -32px -32px;
border-radius: 0 0 16px 16px;
height: calc(100vh - 34px);
overflow: auto;
scrollbar-width: none;
padding: 20px;
}
.electric-border-card {
@@ -51,16 +102,14 @@ html, body {
position: absolute;
inset: -2px;
border-radius: 16px;
background: linear-gradient(
90deg,
#6366f1,
#8b5cf6,
#a855f7,
#6366f1,
#8b5cf6,
#a855f7,
#6366f1
);
background: linear-gradient(90deg,
#6366f1,
#8b5cf6,
#a855f7,
#6366f1,
#8b5cf6,
#a855f7,
#6366f1);
background-size: 300% 100%;
animation: electric-flow 3s linear infinite, electric-pulse 2s ease-in-out infinite;
z-index: 0;
@@ -85,6 +134,7 @@ html, body {
0% {
background-position: -200% center;
}
100% {
background-position: 200% center;
}
@@ -97,16 +147,14 @@ html, body {
.shiny-text {
font-size: 24px;
font-weight: 700;
background: linear-gradient(
90deg,
#6366f1 0%,
#8b5cf6 20%,
#a855f7 40%,
#c084fc 50%,
#a855f7 60%,
#8b5cf6 80%,
#6366f1 100%
);
background: linear-gradient(90deg,
#6366f1 0%,
#8b5cf6 20%,
#a855f7 40%,
#c084fc 50%,
#a855f7 60%,
#8b5cf6 80%,
#6366f1 100%);
background-size: 200% auto;
-webkit-background-clip: text;
background-clip: text;
@@ -118,92 +166,225 @@ html, body {
/* ── Keyframe Animations ───────────────── */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInLeft {
from { opacity: 0; transform: translateX(-20px); }
to { opacity: 1; transform: translateX(0); }
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes fadeInRight {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes float1 {
0%, 100% { transform: translate(0, 0) scale(1); }
50% { transform: translate(20px, -30px) scale(1.05); }
0%,
100% {
transform: translate(0, 0) scale(1);
}
50% {
transform: translate(20px, -30px) scale(1.05);
}
}
@keyframes float2 {
0%, 100% { transform: translate(0, 0) scale(1); }
50% { transform: translate(-15px, 20px) scale(0.95); }
0%,
100% {
transform: translate(0, 0) scale(1);
}
50% {
transform: translate(-15px, 20px) scale(0.95);
}
}
@keyframes float3 {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
50% { transform: translate(10px, -15px) rotate(3deg); }
0%,
100% {
transform: translate(0, 0) rotate(0deg);
}
50% {
transform: translate(10px, -15px) rotate(3deg);
}
}
@keyframes slideInCard {
from { opacity: 0; transform: translateY(14px) scale(0.97); }
to { opacity: 1; transform: translateY(0) scale(1); }
from {
opacity: 0;
transform: translateY(14px) scale(0.97);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes spinSlow {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.animate-fadeInUp { animation: fadeInUp 0.45s ease-out both; }
.animate-fadeInLeft { animation: fadeInLeft 0.55s ease-out both; }
.animate-fadeInRight { animation: fadeInRight 0.55s ease-out both; }
.animate-slideInCard { animation: slideInCard 0.4s ease-out both; }
.animate-float1 { animation: float1 8s ease-in-out infinite; }
.animate-float2 { animation: float2 10s ease-in-out infinite; }
.animate-float3 { animation: float3 12s ease-in-out infinite; }
.animate-fadeInUp {
animation: fadeInUp 0.45s ease-out both;
}
.ref-thumb:hover .ref-delete { opacity: 1 !important; }
.animate-fadeInLeft {
animation: fadeInLeft 0.55s ease-out both;
}
.stagger-children > *:nth-child(1) { animation-delay: 0.04s; }
.stagger-children > *:nth-child(2) { animation-delay: 0.10s; }
.stagger-children > *:nth-child(3) { animation-delay: 0.16s; }
.stagger-children > *:nth-child(4) { animation-delay: 0.22s; }
.stagger-children > *:nth-child(5) { animation-delay: 0.28s; }
.stagger-children > *:nth-child(6) { animation-delay: 0.34s; }
.animate-fadeInRight {
animation: fadeInRight 0.55s ease-out both;
}
.animate-slideInCard {
animation: slideInCard 0.4s ease-out both;
}
.animate-float1 {
animation: float1 8s ease-in-out infinite;
}
.animate-float2 {
animation: float2 10s ease-in-out infinite;
}
.animate-float3 {
animation: float3 12s ease-in-out infinite;
}
.ref-thumb:hover .ref-delete {
opacity: 1 !important;
}
.stagger-children>*:nth-child(1) {
animation-delay: 0.04s;
}
.stagger-children>*:nth-child(2) {
animation-delay: 0.10s;
}
.stagger-children>*:nth-child(3) {
animation-delay: 0.16s;
}
.stagger-children>*:nth-child(4) {
animation-delay: 0.22s;
}
.stagger-children>*:nth-child(5) {
animation-delay: 0.28s;
}
.stagger-children>*:nth-child(6) {
animation-delay: 0.34s;
}
/* ── Ant Design Overrides ──────────────── */
.ant-input, .ant-input-affix-wrapper, .ant-select-selector, .ant-input-number {
.ant-input,
.ant-input-affix-wrapper,
.ant-select-selector,
.ant-input-number {
border-radius: 10px !important;
transition: all 0.25s ease !important;
}
.ant-input:focus, .ant-input-affix-wrapper:focus, .ant-input-affix-wrapper-focused,
.ant-input:focus,
.ant-input-affix-wrapper:focus,
.ant-input-affix-wrapper-focused,
.ant-select-focused .ant-select-selector {
box-shadow: 0 0 0 3px rgba(99,102,241,0.12) !important;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.12) !important;
border-color: #6366f1 !important;
}
.ant-btn {
border-radius: 10px !important;
transition: all 0.25s ease !important;
font-weight: 500 !important;
}
.ant-btn:hover { transform: none; }
.ant-btn:active { transform: translateY(0); }
.ant-card { border-radius: 16px !important; transition: all 0.3s ease !important; }
.ant-modal-content { border-radius: 20px !important; overflow: hidden; }
.ant-tag { font-size: 12px; border-radius: 8px !important; }
.ant-btn:hover {
transform: none;
}
.ant-btn:active {
transform: translateY(0);
}
.ant-card {
border-radius: 16px !important;
transition: all 0.3s ease !important;
}
.ant-modal-content {
border-radius: 20px !important;
overflow: hidden;
}
.ant-tag {
font-size: 12px;
border-radius: 8px !important;
}
/* ── Recharge card hover ───────────────── */
.recharge-card {
transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1) !important;
cursor: pointer;
}
.recharge-card:hover {
transform: translateY(-8px) scale(1.02) !important;
box-shadow: 0 20px 40px rgba(99,102,241,0.15) !important;
box-shadow: 0 20px 40px rgba(99, 102, 241, 0.15) !important;
}
.recharge-card .recharge-price {
transition: all 0.3s ease;
}
.recharge-card:hover .recharge-price {
transform: scale(1.08);
}
.recharge-card .recharge-price { transition: all 0.3s ease; }
.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;
@@ -211,13 +392,16 @@ html, body {
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;
@@ -230,108 +414,249 @@ html, body {
position: relative;
overflow: visible;
}
.project-card:hover {
transform: translateY(-4px) !important;
box-shadow: 0 16px 40px rgba(99,102,241,0.12) !important;
box-shadow: 0 16px 40px rgba(99, 102, 241, 0.12) !important;
border-color: #6366f1 !important;
}
/* ── Mobile Bottom Nav ─────────────────── */
.mobile-bottom-nav {
display: none;
position: fixed; bottom: 0; left: 0; right: 0; z-index: 200;
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 200;
height: 60px;
background: rgba(255,255,255,0.95);
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(24px);
border-top: 1px solid rgba(0,0,0,0.05);
box-shadow: 0 -4px 20px rgba(0,0,0,0.06);
border-top: 1px solid rgba(0, 0, 0, 0.05);
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.06);
}
.mobile-bottom-nav .nav-item {
flex: 1; display: flex; flex-direction: column;
align-items: center; justify-content: center; gap: 2px;
cursor: pointer; transition: all 0.2s;
color: #8b8fa3; font-size: 9px; font-weight: 400;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
cursor: pointer;
transition: all 0.2s;
color: #8b8fa3;
font-size: 9px;
font-weight: 400;
letter-spacing: 0;
}
.mobile-bottom-nav .nav-item.active {
color: #08080c;
}
.mobile-bottom-nav .nav-item .nav-icon {
font-size: 18px; transition: transform 0.2s;
font-size: 18px;
transition: transform 0.2s;
}
.mobile-bottom-nav .nav-item.active .nav-icon {
transform: scale(1.05);
}
/* ── Responsive Breakpoints ────────────── */
@media (max-width: 768px) {
.desktop-sidebar { display: none !important; }
.desktop-content { margin-left: 0 !important; padding: 16px !important; padding-bottom: 80px !important; overflow-x: hidden !important; }
.desktop-sidebar-toggle-zone { display: none !important; }
.mobile-bottom-nav { display: flex !important; }
.desktop-sidebar {
display: none !important;
}
.desktop-content {
margin-left: 0 !important;
padding: 16px !important;
padding-bottom: 80px !important;
overflow-x: hidden !important;
}
.desktop-sidebar-toggle-zone {
display: none !important;
}
.mobile-bottom-nav {
display: flex !important;
}
/* Prevent horizontal overflow globally */
html, body { overflow-x: hidden !important; }
html,
body {
overflow-x: hidden !important;
}
.mobile-hide { display: none !important; }
.mobile-full { width: 100% !important; max-width: 100% !important; }
.mobile-stack { flex-direction: column !important; }
.mobile-p-16 { padding: 16px !important; }
.mobile-gap-12 { gap: 12px !important; }
.mobile-hide {
display: none !important;
}
.mobile-full {
width: 100% !important;
max-width: 100% !important;
}
.mobile-stack {
flex-direction: column !important;
}
.mobile-p-16 {
padding: 16px !important;
}
.mobile-gap-12 {
gap: 12px !important;
}
/* Login page */
.login-page { flex-direction: column !important; }
.login-left { display: none !important; }
.login-right { padding: 24px 16px !important; }
.login-card { width: 100% !important; }
.login-page {
flex-direction: column !important;
}
.login-left {
display: none !important;
}
.login-right {
padding: 24px 16px !important;
}
.login-card {
width: 100% !important;
}
/* Project list */
.project-item { flex-direction: column !important; align-items: flex-start !important; }
.project-item .project-actions { margin-left: 0 !important; margin-top: 12px; width: 100%; justify-content: flex-end; }
.project-item {
flex-direction: column !important;
align-items: flex-start !important;
}
.project-item .project-actions {
margin-left: 0 !important;
margin-top: 12px;
width: 100%;
justify-content: flex-end;
}
/* Generate page header */
.gen-header { flex-direction: column !important; gap: 12px !important; padding: 16px 18px !important; }
.gen-header {
flex-direction: column !important;
gap: 12px !important;
padding: 16px 18px !important;
}
/* Operation flow bar */
.gen-flow-bar { flex-direction: column !important; gap: 14px !important; padding: 16px !important; }
.gen-flow-divider { display: none !important; }
.gen-flow-bar {
flex-direction: column !important;
gap: 14px !important;
padding: 16px !important;
}
.gen-flow-divider {
display: none !important;
}
/* Config form params row */
.gen-form-row { flex-direction: column !important; gap: 0 !important; }
.gen-form-row {
flex-direction: column !important;
gap: 0 !important;
}
/* Credits estimate bar */
.gen-credits-bar { flex-direction: column !important; gap: 8px !important; text-align: center; }
.gen-credits-bar {
flex-direction: column !important;
gap: 8px !important;
text-align: center;
}
/* Prompt rows */
.gen-prompt-row { flex-direction: column !important; }
.gen-prompt-row > * { width: 100% !important; }
.gen-params { flex-wrap: wrap !important; gap: 8px !important; }
.gen-params > div { flex: 1 1 45% !important; min-width: 0; }
.gen-prompt-row {
flex-direction: column !important;
}
.gen-prompt-row>* {
width: 100% !important;
}
.gen-params {
flex-wrap: wrap !important;
gap: 8px !important;
}
.gen-params>div {
flex: 1 1 45% !important;
min-width: 0;
}
/* Expanded detail left+right stacks */
.gen-detail-row { flex-direction: column !important; }
.gen-detail-row > * { width: 100% !important; min-width: 0 !important; flex: 1 1 auto !important; }
.gen-detail-row {
flex-direction: column !important;
}
.gen-detail-row>* {
width: 100% !important;
min-width: 0 !important;
flex: 1 1 auto !important;
}
/* Record filter bar stacks */
.record-filter-bar { flex-direction: column !important; }
.record-filter-bar .ant-space { flex-wrap: wrap !important; }
.record-filter-bar .ant-select { width: 100% !important; }
.record-filter-bar {
flex-direction: column !important;
}
.record-filter-bar .ant-space {
flex-wrap: wrap !important;
}
.record-filter-bar .ant-select {
width: 100% !important;
}
/* Record summary row stacks */
.record-item { flex-direction: column !important; align-items: flex-start !important; gap: 8px !important; overflow: hidden !important; }
.record-item .record-actions { width: 100% !important; justify-content: flex-end; }
.record-item > span[style*="textOverflow"] { white-space: normal !important; overflow: visible !important; text-overflow: unset !important; }
.record-item {
flex-direction: column !important;
align-items: flex-start !important;
gap: 8px !important;
overflow: hidden !important;
}
.record-item .record-actions {
width: 100% !important;
justify-content: flex-end;
}
.record-item>span[style*="textOverflow"] {
white-space: normal !important;
overflow: visible !important;
text-overflow: unset !important;
}
/* Hide less important meta on mobile */
.mobile-meta { display: none !important; }
.mobile-meta {
display: none !important;
}
/* Credits page */
.credits-balance { flex-direction: column !important; }
.recharge-grid { flex-direction: column !important; }
.recharge-grid > * { width: 100% !important; }
.credits-balance {
flex-direction: column !important;
}
.recharge-grid {
flex-direction: column !important;
}
.recharge-grid>* {
width: 100% !important;
}
/* Video player fits mobile */
video { max-width: 100% !important; }
video {
max-width: 100% !important;
}
/* InitialReplication page */
.replication-container {
@@ -340,17 +665,20 @@ html, body {
padding: 16px !important;
height: calc(100vh - 64px) !important;
}
.replication-preview {
width: 100% !important;
height: 30vh !important;
border-radius: 12px !important;
}
.replication-form {
width: 100% !important;
height: auto !important;
max-height: calc(70vh - 32px) !important;
border-radius: 12px !important;
}
.replication-form-content {
padding: 16px !important;
max-height: calc(70vh - 64px) !important;
@@ -358,56 +686,92 @@ html, body {
}
/* ── Date Display ──────────────────────── */
.date-display { font-family: 'SF Mono', 'Menlo', 'Consolas', monospace; }
.date-display {
font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
}
/* ── Small Mobile Breakpoint (max-width: 480px) ────────────── */
@media (max-width: 480px) {
/* Base styles */
html, body { font-size: 14px !important; }
html,
body {
font-size: 14px !important;
}
/* Login page */
.login-card { width: 100% !important; }
.login-right { padding: 16px !important; }
.login-card {
width: 100% !important;
}
.login-right {
padding: 16px !important;
}
/* Header adjustments */
.gen-header { padding: 12px 14px !important; }
.gen-header {
padding: 12px 14px !important;
}
/* Form elements */
.gen-form-row { gap: 8px !important; }
.gen-form-row {
gap: 8px !important;
}
/* Button adjustments */
.ant-btn { height: 36px !important; font-size: 13px !important; padding: 0 12px !important; }
.ant-btn {
height: 36px !important;
font-size: 13px !important;
padding: 0 12px !important;
}
/* Input adjustments */
.ant-input, .ant-input-affix-wrapper, .ant-select-selector {
height: 36px !important;
.ant-input,
.ant-input-affix-wrapper,
.ant-select-selector {
height: 36px !important;
font-size: 13px !important;
}
/* Card padding */
.ant-card { padding: 12px !important; }
.ant-card {
padding: 12px !important;
}
/* Mobile bottom nav adjustments */
.mobile-bottom-nav { height: 56px !important; }
.mobile-bottom-nav .nav-item .nav-icon { font-size: 18px !important; }
.mobile-bottom-nav .nav-item { font-size: 9px !important; gap: 2px !important; }
.mobile-bottom-nav {
height: 56px !important;
}
.mobile-bottom-nav .nav-item .nav-icon {
font-size: 18px !important;
}
.mobile-bottom-nav .nav-item {
font-size: 9px !important;
gap: 2px !important;
}
/* InitialReplication page - Small Mobile */
.replication-container {
padding: 12px !important;
gap: 12px !important;
}
.replication-preview {
height: 25vh !important;
border-radius: 10px !important;
}
.replication-form-content {
padding: 12px !important;
}
.replication-form-content .ant-btn {
height: 36px !important;
font-size: 13px !important;
}
.replication-form-content .ant-input,
.replication-form-content .ant-input-affix-wrapper {
height: 36px !important;
@@ -415,30 +779,56 @@ html, body {
}
/* Video and image containers */
video, img {
max-height: 160px !important;
video,
img {
max-height: 160px !important;
border-radius: 6px !important;
}
/* Upload areas */
.upload-area { padding: 12px !important; }
.upload-area {
padding: 12px !important;
}
/* Spacing adjustments */
.mobile-p-16 { padding: 12px !important; }
.mobile-gap-12 { gap: 8px !important; }
.mobile-p-16 {
padding: 12px !important;
}
.mobile-gap-12 {
gap: 8px !important;
}
/* Font size adjustments */
h1 { font-size: 18px !important; }
h2 { font-size: 16px !important; }
h3 { font-size: 14px !important; }
p { font-size: 12px !important; }
h1 {
font-size: 18px !important;
}
h2 {
font-size: 16px !important;
}
h3 {
font-size: 14px !important;
}
p {
font-size: 12px !important;
}
/* Button text */
.btn-text-sm { font-size: 12px !important; }
.btn-text-sm {
font-size: 12px !important;
}
/* Margin adjustments */
.mobile-mb-8 { margin-bottom: 8px !important; }
.mobile-mb-12 { margin-bottom: 12px !important; }
.mobile-mb-8 {
margin-bottom: 8px !important;
}
.mobile-mb-12 {
margin-bottom: 12px !important;
}
}
/* ── Tablet Breakpoint (769px to 1024px) ────────────── */
@@ -450,33 +840,49 @@ html, body {
*/
/* Card max-width */
.ant-card { max-width: calc(50% - 8px) !important; }
.ant-card {
max-width: calc(50% - 8px) !important;
}
/* Font adjustments */
html, body { font-size: 15px !important; }
html,
body {
font-size: 15px !important;
}
}
/* ── Large Desktop Breakpoint (min-width: 1200px) ────────────── */
@media (min-width: 1200px) {
/* Max width container */
.max-w-container { max-width: 1400px; margin: 0 auto; }
.max-w-container {
max-width: 1400px;
margin: 0 auto;
}
/* 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-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);
}
}
+271 -72
View File
@@ -8,127 +8,326 @@ import {
PlayCircleOutlined,
CheckCircleOutlined,
HeartOutlined,
EyeOutlined,
ArrowRightOutlined,
CrownOutlined,
} from '@ant-design/icons';
const CreativePlazaPage: React.FC = () => {
const handleUnlock = () => {
message.info('请联系客服开通');
message.info('请联系客服开通会员');
};
// 顶部特性卡片
const features = [
{
icon: <ThunderboltOutlined style={{ fontSize: 28, color: '#8b5cf6' }} />,
title: 'AI智能生成',
description: '先进AI技术一键生成高质量视频内容',
icon: <ThunderboltOutlined style={{ fontSize: 26, color: '#8b5cf6' }} />,
title: 'AI 智能生成',
description: '先进 AI 技术一键产出高质量视频内容',
},
{
icon: <PlayCircleOutlined style={{ fontSize: 28, color: '#ec4899' }} />,
icon: <PlayCircleOutlined style={{ fontSize: 26, color: '#ec4899' }} />,
title: '多格式输出',
description: '支持多种视频格式满足不同场景需求',
description: '支持多种视频格式,满足不同场景需求',
},
{
icon: <CheckCircleOutlined style={{ fontSize: 28, color: '#10b981' }} />,
icon: <CheckCircleOutlined style={{ fontSize: 26, color: '#10b981' }} />,
title: '品质保证',
description: '专业级画质细节清晰色彩鲜艳',
description: '专业级画质,细节清晰,色彩鲜艳',
},
{
icon: <HeartOutlined style={{ fontSize: 28, color: '#f59e0b' }} />,
icon: <HeartOutlined style={{ fontSize: 26, color: '#f59e0b' }} />,
title: '创意无限',
description: '丰富模板风格激发创作灵感',
description: '丰富模板风格,激发创作灵感',
},
];
// 精选案例占位
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' },
];
return (
<div style={{ padding: '20px 0' }}>
<div style={{ marginBottom: 32 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 }}>
<div style={{
width: 48,
height: 48,
borderRadius: 14,
background: 'linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 4px 16px rgba(139,92,246,0.3)',
}}>
<GiftOutlined style={{ fontSize: 22, color: '#fff' }} />
<div className="content_box">
{/* 顶部 Hero 区域 */}
<div
className="animate-fadeInUp"
style={{
padding: '28px 32px',
borderRadius: 16,
background: 'linear-gradient(135deg, #f5f3ff 0%, #ede9fe 50%, #fce7f3 100%)',
border: '1px solid rgba(139, 92, 246, 0.12)',
marginBottom: 24,
position: 'relative',
overflow: 'hidden',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 12 }}>
<div
style={{
width: 52,
height: 52,
borderRadius: 14,
background: 'linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 6px 20px rgba(139, 92, 246, 0.3)',
}}
>
<GiftOutlined style={{ fontSize: 24, color: '#fff' }} />
</div>
<div>
<Typography.Title level={2} style={{ margin: 0, color: '#1e293b', fontWeight: 700 }}>
<span style={{ color: '#6366f1', fontSize: 14, fontWeight: 400, marginLeft: 8 }}>
AI创作无限可能
<div className="shiny-text-container" style={{ marginBottom: 4 }}>
<span className="shiny-text" style={{ fontSize: 26, fontWeight: 700 }}>
</span>
</Typography.Title>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, color: '#7c3aed' }}>
<CrownOutlined style={{ fontSize: 12 }} />
<span> AI </span>
</div>
</div>
</div>
<Typography.Text style={{ color: '#64748b' }}>
AI生成的优秀素材案例AI创作的无限潜力与创意灵感
<Typography.Text style={{ color: '#64748b', fontSize: 14, lineHeight: 1.7 }}>
AI , AI
</Typography.Text>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 20, marginBottom: 32 }}>
{/* 特性卡片 */}
{/* <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: 'linear-gradient(135deg, rgba(139,92,246,0.04) 0%, rgba(99,102,241,0.04) 100%)',
border: '1px solid rgba(139,92,246,0.1)',
padding: 20,
background: '#fff',
border: '1px solid #e2e8f0',
padding: '4px 0',
}}
styles={{ body: { padding: 20 } }}
>
<div style={{ textAlign: 'center' }}>
<div style={{ marginBottom: 16 }}>{feature.icon}</div>
<Typography.Title level={4} style={{ marginBottom: 8, color: '#1e293b' }}>
<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}
</Typography.Title>
<Typography.Text style={{ color: '#64748b', fontSize: 13 }}>
</div>
<div style={{ fontSize: 13, color: '#64748b', lineHeight: 1.6 }}>
{feature.description}
</Typography.Text>
</div>
</div>
</Card>
))}
</div>
</div> */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
<Typography.Title level={4} style={{ margin: 0, color: '#1e293b' }}>
<StarOutlined style={{ marginRight: 8, color: '#f59e0b' }} />
</Typography.Title>
<Button
type="primary"
size="middle"
onClick={handleUnlock}
style={{
borderRadius: 10,
background: 'linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%)',
border: 'none',
}}
>
<LockOutlined style={{ marginRight: 6 }} />
</Button>
</div>
<Card
bordered={false}
{/* 精选案例展示区域 */}
<div
className="animate-fadeInUp"
style={{
padding: '24px 28px',
borderRadius: 16,
background: '#fafafa',
padding: 40,
textAlign: 'center',
background: '#fff',
// border: '1px solid #e2e8f0',
position: 'relative',
}}
>
<div style={{ color: '#94a3b8', fontSize: 14 }}>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 20,
}}
>
<div style={{ display: 'flex', alignItems: 'center' }}>
<div
style={{
width: 4,
height: 18,
background: 'linear-gradient(180deg, #6366f1, #8b5cf6)',
borderRadius: 2,
marginRight: 10,
}}
/>
<StarOutlined style={{ marginRight: 8, color: '#f59e0b', fontSize: 16 }} />
<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>
</Card>
{/* 案例网格 */}
<div
className="stagger-children"
style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: 16,
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) => (
<div
key={index}
className="project-card animate-fadeInUp"
style={{
position: 'relative',
borderRadius: 14,
overflow: 'hidden',
cursor: 'pointer',
border: '1px solid #e2e8f0',
background: '#fff',
}}
>
<div
style={{
position: 'relative',
aspectRatio: '16/9',
background:
'linear-gradient(135deg, #1e1b4b 0%, #312e81 50%, #4c1d95 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{/* 模糊视频缩略图占位 */}
<PlayCircleOutlined style={{ fontSize: 36, color: 'rgba(255,255,255,0.6)' }} />
{/* 标签 */}
<div
style={{
position: 'absolute',
top: 10,
left: 10,
padding: '2px 8px',
borderRadius: 6,
background: item.tagColor,
color: '#fff',
fontSize: 11,
fontWeight: 500,
}}
>
{item.tag}
</div>
</div>
<div
style={{
padding: '10px 14px',
background: '#f8fafc',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<span
style={{
fontSize: 13,
color: '#334155',
fontWeight: 500,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{item.title}
</span>
<EyeOutlined style={{ fontSize: 13, color: '#94a3b8' }} />
</div>
</div>
))}
</div>
{/* 锁定遮罩 */}
</div>
</div>
);
};
export default CreativePlazaPage;
export default CreativePlazaPage;
+39 -17
View File
@@ -72,7 +72,7 @@ const HomePage: React.FC = () => {
setActiveTab(key);
};
const aiEntries = [
{
@@ -120,21 +120,43 @@ const HomePage: React.FC = () => {
];
return (
<div style={{
margin: '-24px -32px -32px',
borderRadius: 20,
height: 'calc(100vh - 34px)',
overflow: 'auto',
scrollbarWidth: 'none',
padding: '0 28px 32px',
}}>
<div className="content_box">
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
<h3></h3>
<p
style={{
cursor: 'pointer',
fontSize: 12, letterSpacing: 0.3,
padding: '10px 16px',
backgroundColor: '#ffffffff',
borderRadius: 30,
boxShadow: '0px 4px 12px 0px rgba(0, 0, 0, 0.1)',
}}
>
<span
style={{
color: '#1a50bbff',
}}
onClick={() => {
navigate('/authorization')
}}>
<ArrowRightOutlined style={{ marginLeft: 8, transform: 'rotate(0deg)' }} />
</span>
</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: '24px 0 20px',
margin: '0 0 20px',
position: 'relative',
overflow: 'hidden',
}}>
@@ -151,7 +173,7 @@ const HomePage: React.FC = () => {
</div>
</div>
</div>
<div >
{/* <div >
<span style={{
cursor: 'pointer',
fontSize: 12, color: '#1a50bbff', letterSpacing: 0.3,
@@ -159,11 +181,11 @@ const HomePage: React.FC = () => {
onClick={() => {
navigate('/authorization')
}}
>
>如需进行账户素材推送 一键推送
<ArrowRightOutlined style={{ marginLeft: 8, transform: 'rotate(0deg)' }} />
</span>
</div>
</div> */}
</div>
@@ -553,7 +575,7 @@ const HomePage: React.FC = () => {
</div>
</div>
</div>
{/* Tab切换 */}
<div style={{ marginBottom: 20 }}>
@@ -739,7 +761,7 @@ const HomePage: React.FC = () => {
{/* ========== 素材案例区域 ========== */}
<div className="animate-fadeInUp" style={{
{/* <div className="animate-fadeInUp" style={{
padding: '24px 28px',
borderRadius: 16,
background: '#fff',
@@ -800,7 +822,7 @@ const HomePage: React.FC = () => {
alt={`素材案例 ${index + 1}`}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
{/* 案例悬停遮罩(由父级 hover 触发) */}
案例悬停遮罩(由父级 hover 触发)
<div
className="case-overlay"
style={{
@@ -824,7 +846,7 @@ const HomePage: React.FC = () => {
</div>
))}
</div>
</div>
</div> */}
</div>
);
};
+8 -2
View File
@@ -31,6 +31,8 @@ function InitialInfo() {
// 引擎和视频参数相关状态
const [enginesele, setEnginesele] = useState<any>({});
const [countType, setCountType] = useState('');
// 图片引擎 id:取自 getEngine().engine.image[0].id
const [imageEngineId, setImageEngineId] = useState<string>('');
const [showEngineModal, setShowEngineModal] = useState(false);
const [showVideoSettingsModal, setShowVideoSettingsModal] = useState(false);
const [videoDuration, setVideoDuration] = useState(5);
@@ -160,6 +162,10 @@ function InitialInfo() {
durations: firstEngine.supportedDurations || [5, 8, 10, 12, 15],
});
}
// 默认选中第一个图片引擎
if (data.engine?.image && data.engine.image.length > 0) {
setImageEngineId(data.engine.image[0].id);
}
})
.catch((error: any) => {
});
@@ -335,7 +341,7 @@ function InitialInfo() {
const createimage = (stepId: number) => {
let params = {
engine_id: "0019e3dac0b795b925b",
engine_id: imageEngineId,
image_proportion: "1:1",
image_px: "2048x2048",
image_size: "2K"
@@ -349,7 +355,7 @@ function InitialInfo() {
const newcreateimage = () => {
// console.log('下一步:', stepId);
let params = {
engine_id: "0019e3dac0b795b925b",
engine_id: imageEngineId,
image_proportion: "1:1",
image_px: "2048x2048",
image_size: "2K"
@@ -850,6 +850,7 @@ const GenerateConver: React.FC = () => {
</div>
) : (
<Upload
style={{ width: '100%' }}
beforeUpload={beforeVideoUpload}
showUploadList={false}
accept="video/mp4,video/quicktime,.mp4,.mov"
@@ -965,6 +966,8 @@ const GenerateConver: React.FC = () => {
</div>
) : (
<Upload
style={{ width: '100%' }}
beforeUpload={beforeImageUpload}
showUploadList={false}
accept="image/jpeg,image/jpg,image/png,.jpg,.jpeg,.png"
+621 -81
View File
@@ -1,115 +1,655 @@
import React from 'react';
import { Button, Card, message, Typography } from 'antd';
import { Button, message, Typography } from 'antd';
import {
StarOutlined,
CrownOutlined,
LockOutlined,
ThunderboltOutlined,
GiftOutlined,
BarChartOutlined,
FireOutlined,
TrophyOutlined,
LockOutlined,
ArrowRightOutlined,
PlayCircleOutlined,
EyeOutlined,
LikeOutlined,
CommentOutlined,
ShareAltOutlined,
RiseOutlined,
ThunderboltOutlined,
} from '@ant-design/icons';
const PopularPage: React.FC = () => {
const handleUnlock = () => {
message.info('请联系客服开通');
message.info('请联系客服开通会员');
};
// 顶部统计 KPI
const stats = [
{
icon: <FireOutlined style={{ fontSize: 22, color: '#f59e0b' }} />,
label: '今日爆款',
value: '1,286',
unit: '条',
trend: '+12.5%',
color: '#f59e0b',
},
{
icon: <EyeOutlined style={{ fontSize: 22, color: '#6366f1' }} />,
label: '总浏览量',
value: '428',
unit: '万',
trend: '+8.2%',
color: '#6366f1',
},
{
icon: <LikeOutlined style={{ fontSize: 22, color: '#ec4899' }} />,
label: '总互动数',
value: '76.4',
unit: '万',
trend: '+15.7%',
color: '#ec4899',
},
{
icon: <ThunderboltOutlined style={{ fontSize: 22, color: '#10b981' }} />,
label: '上升最快',
value: '美妆',
unit: '赛道',
trend: '+28.3%',
color: '#10b981',
},
];
// 行业热度排行(假数据)
const industryRank = [
{ rank: 1, name: '美妆个护', hot: 98.5, change: 'up' as const, value: 98234 },
{ rank: 2, name: '服饰穿搭', hot: 95.2, change: 'up' as const, value: 87643 },
{ rank: 3, name: '美食探店', hot: 91.7, change: 'up' as const, value: 78921 },
{ rank: 4, name: '3C 数码', hot: 87.3, change: 'down' as const, value: 65432 },
{ rank: 5, name: '家居生活', hot: 82.6, change: 'up' as const, value: 54321 },
{ rank: 6, name: '母婴亲子', hot: 78.1, change: 'flat' as const, value: 43210 },
{ rank: 7, name: '运动健身', hot: 72.4, change: 'up' as const, value: 38765 },
];
// 爆款素材榜单
const popularList = [
{
rank: 1,
title: '夏日美妆教程:3 步打造奶油肌',
author: '美妆博主·小雅',
views: '128.4w',
likes: '12.6w',
comments: '8,234',
tag: 'NO.1',
tagColor: '#f59e0b',
},
{
rank: 2,
title: '平价穿搭 | 学生党一周不重样',
author: '穿搭达人·Miki',
views: '96.2w',
likes: '9.8w',
comments: '6,128',
tag: 'NO.2',
tagColor: '#a78bfa',
},
{
rank: 3,
title: '探店 vlog | 网红咖啡馆测评',
author: '美食探店·阿伦',
views: '78.5w',
likes: '7.4w',
comments: '4,892',
tag: 'NO.3',
tagColor: '#6366f1',
},
{
rank: 4,
title: 'iPhone 16 Pro 一周深度体验',
author: '数码评测·老王',
views: '65.7w',
likes: '6.1w',
comments: '3,847',
tag: 'NO.4',
tagColor: '#94a3b8',
},
];
return (
<div style={{ padding: '20px 0' }}>
<div style={{ marginBottom: 32 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 }}>
<div style={{
width: 48,
height: 48,
borderRadius: 14,
background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 4px 16px rgba(245,158,11,0.3)',
}}>
<BarChartOutlined style={{ fontSize: 22, color: '#fff' }} />
<div className="content_box">
{/* Hero 区域 - 橙金主题(区别创意素材案例的紫粉) */}
<div
className="animate-fadeInUp"
style={{
padding: '28px 32px',
borderRadius: 16,
background: 'linear-gradient(135deg, #fffbeb 0%, #fef3c7 50%, #fed7aa 100%)',
border: '1px solid rgba(245, 158, 11, 0.18)',
marginBottom: 24,
position: 'relative',
overflow: 'hidden',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 12 }}>
<div
style={{
width: 52,
height: 52,
borderRadius: 14,
background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 6px 20px rgba(245, 158, 11, 0.3)',
}}
>
<BarChartOutlined style={{ fontSize: 24, color: '#fff' }} />
</div>
<div>
<Typography.Title level={2} style={{ margin: 0, color: '#1e293b', fontWeight: 700 }}>
<span style={{ color: '#6366f1', fontSize: 14, fontWeight: 400, marginLeft: 8 }}>
<div className="shiny-text-container" style={{ marginBottom: 4 }}>
<span
className="shiny-text"
style={{
fontSize: 26,
fontWeight: 700,
backgroundImage:
'linear-gradient(90deg, #f59e0b 0%, #d97706 30%, #ea580c 50%, #d97706 70%, #f59e0b 100%)',
}}
>
</span>
</Typography.Title>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, color: '#b45309' }}>
<RiseOutlined style={{ fontSize: 12 }} />
<span></span>
</div>
</div>
</div>
<Typography.Text style={{ color: '#64748b' }}>
<Typography.Text style={{ color: '#78716c', fontSize: 14, lineHeight: 1.7 }}>
,,
</Typography.Text>
</div>
<Card
bordered={false}
{/* 4 个 KPI 统计卡片 */}
{/* <div
className="animate-fadeInUp stagger-children"
style={{
borderRadius: 20,
background: 'linear-gradient(135deg, rgba(245,158,11,0.04) 0%, rgba(217,119,6,0.04) 100%)',
border: '1px solid rgba(245,158,11,0.1)',
padding: 10,
marginBottom: 32,
display: 'grid',
gridTemplateColumns: 'repeat(4, 1fr)',
gap: 16,
marginBottom: 24,
}}
>
<Typography.Title level={4} style={{ marginBottom: 20, color: '#1e293b' }}>
<FireOutlined style={{ marginRight: 8, color: '#f59e0b' }} />
</Typography.Title>
<Card
bordered={false}
style={{
borderRadius: 16,
background: '#fafafa',
padding: 40,
textAlign: 'center',
}}
>
<div style={{ color: '#94a3b8', fontSize: 14 }}>
{stats.map((item, index) => (
<div
key={index}
className="project-card"
style={{
padding: 20,
borderRadius: 16,
background: '#fff',
border: '1px solid #e2e8f0',
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 14,
}}
>
<div
style={{
width: 44,
height: 44,
borderRadius: 12,
background: `linear-gradient(135deg, ${item.color}15, ${item.color}08)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{item.icon}
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 2,
fontSize: 12,
fontWeight: 600,
color: '#10b981',
background: 'rgba(16, 185, 129, 0.08)',
padding: '2px 8px',
borderRadius: 6,
}}
>
<RiseOutlined style={{ fontSize: 10 }} />
{item.trend}
</div>
</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 4, marginBottom: 4 }}>
<span style={{ fontSize: 24, fontWeight: 700, color: '#1e293b' }}>{item.value}</span>
<span style={{ fontSize: 13, color: '#94a3b8' }}>{item.unit}</span>
</div>
<div style={{ fontSize: 13, color: '#64748b' }}>{item.label}</div>
</div>
</Card>
</Card>
))}
</div> */}
{/* 行业热度排行 */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
<Typography.Title level={4} style={{ margin: 0, color: '#1e293b' }}>
<TrophyOutlined style={{ marginRight: 8, color: '#6366f1' }} />
</Typography.Title>
<Button
type="primary"
size="middle"
onClick={handleUnlock}
<div style={{ position: 'relative' }}>
{/* <div
className="animate-fadeInUp"
style={{
borderRadius: 10,
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
border: 'none',
padding: '24px 28px',
borderRadius: 16,
background: '#fff',
border: '1px solid #e2e8f0',
marginBottom: 24,
}}
>
<LockOutlined style={{ marginRight: 6 }} />
</Button>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 20,
}}
>
<div style={{ display: 'flex', alignItems: 'center' }}>
<div
style={{
width: 4,
height: 18,
background: 'linear-gradient(180deg, #f59e0b, #d97706)',
borderRadius: 2,
marginRight: 10,
}}
/>
<FireOutlined style={{ marginRight: 8, color: '#f59e0b', fontSize: 16 }} />
<span style={{ fontSize: 17, fontWeight: 600, color: '#1f2937' }}>行业热度排行</span>
<span
style={{
marginLeft: 10,
fontSize: 12,
fontWeight: 400,
color: '#94a3b8',
}}
>
基于近 7 天数据 · 实时更新
</span>
</div>
<span style={{ fontSize: 12, color: '#94a3b8' }}>更新时间: 10 分钟前</span>
</div>
<div style={{position: 'relative', height: 300}}>
<Typography.Text style={{ width: '100%', textAlign: 'center', color: '#64748b', display: 'block', height: 100, background: '#f3f4f6', marginTop: 20, marginBottom: 20, padding: 20, }}>
实时追踪各行业爆款素材,把握创作风向,打造热门内容
</Typography.Text>
<div
style={{
position: 'absolute',
inset: 0,
// background:
// ' linear-gradient(180deg, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.7) 100%)',
background: 'rgb(255,255,255)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
gap: 2,
color: '#fff',
borderRadius: 6,
// boxShadow: '0 0 10px rgba(0,0,0,0.5)',
}}
>
<LockOutlined style={{ fontSize: 18, color: '#94a3b8', cursor: 'pointer' }}
onClick={handleUnlock}
/>
<span style={{ fontSize: 16, color: '#1f2937', opacity: 0.9 }}>立即解锁全部</span>
</div>
</div>
<div className="stagger-children">
{industryRank.map((item) => {
const rankColors: Record<number, string> = {
1: 'linear-gradient(135deg, #f59e0b, #d97706)',
2: 'linear-gradient(135deg, #94a3b8, #64748b)',
3: 'linear-gradient(135deg, #ea580c, #c2410c)',
};
const rankColor = rankColors[item.rank] || '#cbd5e1';
const trendIcon =
item.change === 'up' ? (
<RiseOutlined style={{ fontSize: 11, color: '#10b981' }} />
) : item.change === 'down' ? (
<RiseOutlined
style={{ fontSize: 11, color: '#ef4444', transform: 'rotate(180deg)' }}
/>
) : (
<span style={{ fontSize: 11, color: '#94a3b8' }}>—</span>
);
return (
<div
key={item.rank}
className="project-card animate-fadeInUp"
style={{
display: 'flex',
alignItems: 'center',
padding: '12px 16px',
borderRadius: 12,
marginBottom: 8,
background: '#f8fafc',
border: '1px solid transparent',
cursor: 'pointer',
}}
>
<div
style={{
width: 32,
height: 32,
borderRadius: 8,
background: rankColor,
color: '#fff',
fontSize: 14,
fontWeight: 700,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginRight: 14,
boxShadow: item.rank <= 3 ? '0 2px 8px rgba(0,0,0,0.15)' : 'none',
}}
>
{item.rank}
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div
style={{
fontSize: 14,
fontWeight: 600,
color: '#1e293b',
marginBottom: 6,
}}
>
{item.name}
</div>
<div
style={{
height: 6,
background: '#e2e8f0',
borderRadius: 3,
overflow: 'hidden',
}}
>
<div
style={{
width: `${item.hot}%`,
height: '100%',
background:
item.rank === 1
? 'linear-gradient(90deg, #f59e0b, #d97706)'
: item.rank === 2
? 'linear-gradient(90deg, #94a3b8, #64748b)'
: item.rank === 3
? 'linear-gradient(90deg, #ea580c, #c2410c)'
: 'linear-gradient(90deg, #6366f1, #8b5cf6)',
borderRadius: 3,
}}
/>
</div>
</div>
<div
style={{
width: 80,
textAlign: 'right',
fontSize: 14,
fontWeight: 600,
color: '#1e293b',
marginLeft: 16,
}}
>
{item.hot}
</div>
<div
style={{
width: 80,
textAlign: 'right',
fontSize: 12,
color: '#64748b',
marginLeft: 12,
}}
>
{item.value.toLocaleString()} 素材
</div>
<div style={{ width: 24, marginLeft: 12, textAlign: 'center' }}>{trendIcon}</div>
</div>
);
})}
</div>
</div> */}
{/* 爆款素材榜单 */}
<div
className="animate-fadeInUp"
style={{
padding: '24px 28px',
borderRadius: 16,
background: '#fff',
// border: '1px solid #e2e8f0',
}}
>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 20,
}}
>
<div style={{ display: 'flex', alignItems: 'center' }}>
<div
style={{
width: 4,
height: 18,
background: 'linear-gradient(180deg, #6366f1, #8b5cf6)',
borderRadius: 2,
marginRight: 10,
}}
/>
<TrophyOutlined style={{ marginRight: 8, color: '#6366f1', fontSize: 16 }} />
<span style={{ fontSize: 17, fontWeight: 600, color: '#1f2937' }}></span>
<span
style={{
marginLeft: 10,
fontSize: 12,
fontWeight: 400,
color: '#94a3b8',
}}
>
{/* TOP 100 */}
</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>
<div
className="stagger-children"
style={{
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: 16,
position: 'relative',
}}
>
<div
style={{
position: 'absolute',
zIndex: 1,
inset: 0,
// background:
// ' linear-gradient(180deg, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.7) 100%)',
background: 'rgb(255,255,255)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
gap: 2,
color: '#fff',
borderRadius: 6,
// boxShadow: '0 0 10px rgba(0,0,0,0.5)',
}}
>
<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: 18, color: '#94a3b8', cursor: 'pointer' }}
onClick={handleUnlock}
/>
<span style={{ fontSize: 16, color: '#1f2937', opacity: 0.9 }}>立即解锁全部</span> */}
</div>
{popularList.map((item) => (
<div
key={item.rank}
className="project-card animate-fadeInUp"
style={{
display: 'flex',
gap: 14,
padding: 14,
borderRadius: 14,
background: '#fff',
border: '1px solid #e2e8f0',
cursor: 'pointer',
}}
>
<div
style={{
position: 'relative',
width: 140,
height: 80,
flexShrink: 0,
borderRadius: 10,
overflow: 'hidden',
background: 'linear-gradient(135deg, #1e1b4b 0%, #312e81 50%, #4c1d95 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<PlayCircleOutlined style={{ fontSize: 28, color: 'rgba(255,255,255,0.6)' }} />
{/* 排名徽章 */}
<div
style={{
position: 'absolute',
top: 6,
left: 6,
padding: '2px 8px',
borderRadius: 6,
background: item.tagColor,
color: '#fff',
fontSize: 11,
fontWeight: 600,
}}
>
{item.tag}
</div>
</div>
<div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column' }}>
<div
style={{
fontSize: 14,
fontWeight: 600,
color: '#1e293b',
marginBottom: 4,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{item.title}
</div>
<div style={{ fontSize: 12, color: '#94a3b8', marginBottom: 8 }}>
{item.author}
</div>
<div style={{ display: 'flex', gap: 12, fontSize: 12, color: '#64748b' }}>
<span>
<EyeOutlined style={{ marginRight: 3 }} />
{item.views}
</span>
<span>
<LikeOutlined style={{ marginRight: 3 }} />
{item.likes}
</span>
<span>
<CommentOutlined style={{ marginRight: 3 }} />
{item.comments}
</span>
</div>
</div>
{/* 锁定遮罩 */}
{/* <div
style={{
position: 'absolute',
inset: 0,
background:
' linear-gradient(to right, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.7) 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
gap: 2,
color: '#fff',
borderRadius: 6,
}}
>
<LockOutlined style={{ fontSize: 18 }} />
<span style={{ fontSize: 10, opacity: 0.9 }}>会员可看</span>
</div> */}
</div>
))}
</div>
</div>
</div>
<Card
bordered={false}
style={{
borderRadius: 16,
background: '#fafafa',
padding: 40,
textAlign: 'center',
}}
>
<div style={{ color: '#94a3b8', fontSize: 14 }}>
</div>
</Card>
</div>
);
};
export default PopularPage;
export default PopularPage;
+8 -2
View File
@@ -31,6 +31,8 @@ function InitialInfo() {
// 引擎和视频参数相关状态
const [enginesele, setEnginesele] = useState<any>({});
const [countType, setCountType] = useState('');
// 图片引擎 id:取自 getEngine().engine.image[0].id
const [imageEngineId, setImageEngineId] = useState<string>('');
const [showEngineModal, setShowEngineModal] = useState(false);
const [showVideoSettingsModal, setShowVideoSettingsModal] = useState(false);
const [videoDuration, setVideoDuration] = useState(5);
@@ -155,6 +157,10 @@ function InitialInfo() {
durations: firstEngine.supportedDurations || [5, 8, 10, 12, 15],
});
}
// 默认选中第一个图片引擎
if (data.engine?.image && data.engine.image.length > 0) {
setImageEngineId(data.engine.image[0].id);
}
})
.catch((error: any) => {
});
@@ -341,7 +347,7 @@ function InitialInfo() {
const createimage = (stepId: number) => {
let params = {
engine_id: "0019e3dac0b795b925b",
engine_id: imageEngineId,
image_proportion: "1:1",
image_px: "2048x2048",
image_size: "2K"
@@ -355,7 +361,7 @@ function InitialInfo() {
const newcreateimage = () => {
// console.log('下一步:', stepId);
let params = {
engine_id: "0019e3dac0b795b925b",
engine_id: imageEngineId,
image_proportion: "1:1",
image_px: "2048x2048",
image_size: "2K"
+17
View File
@@ -1,10 +1,27 @@
export interface ResourceCapacity {
enabled: boolean;
source?: string;
hasUserConfig?: boolean;
usedBytes: number;
availableBytes: number;
totalBytes: number;
usagePercent: number;
exceeded: boolean;
limitValue?: string;
limitUnit: string; // 'KB' | 'MB' | 'GB' 等
}
export interface User {
id: string;
username: string;
email: string;
avatar?: string;
credits: number;
isAdmin?: boolean;
phone?: string;
allowedMenus?: string[] | null;
mustSetPassword?: boolean;
resourceCapacity?: ResourceCapacity;
}
export interface CreditRecord {