From 66c3328e239ab312f92454215418277cc7eb6a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E4=BD=B3=E8=89=BA?= Date: Tue, 30 Jun 2026 09:10:27 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B5=84=E6=BA=90=E5=AD=98=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- video-gen-app/src/api/mock.ts | 12 + .../src/components/Layout/AppLayout.tsx | 181 ++++- video-gen-app/src/index.css | 722 ++++++++++++++---- video-gen-app/src/pages/CreativePlazaPage.tsx | 343 +++++++-- video-gen-app/src/pages/HomePage.tsx | 56 +- video-gen-app/src/pages/InitialInfo.tsx | 10 +- .../src/pages/InitialReplication.tsx | 3 + video-gen-app/src/pages/PopularPage.tsx | 702 +++++++++++++++-- video-gen-app/src/pages/RemoveRw.tsx | 10 +- video-gen-app/src/types/index.ts | 17 + 10 files changed, 1723 insertions(+), 333 deletions(-) diff --git a/video-gen-app/src/api/mock.ts b/video-gen-app/src/api/mock.ts index bea7a751..5e55cefb 100644 --- a/video-gen-app/src/api/mock.ts +++ b/video-gen-app/src/api/mock.ts @@ -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[] = [ diff --git a/video-gen-app/src/components/Layout/AppLayout.tsx b/video-gen-app/src/components/Layout/AppLayout.tsx index bc11a447..04eb97c1 100644 --- a/video-gen-app/src/components/Layout/AppLayout.tsx +++ b/video-gen-app/src/components/Layout/AppLayout.tsx @@ -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; + + // 单位:后端返回的 limitUnit(KB / 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 ( +
+
+ + + 资源存储 + + {data.enabled ? ( + + {used.toFixed(2)} / {total.toFixed(2)} {unit} + + ) : ( + + 当前使用 {usedAuto.val} {usedAuto.unit} + + )} +
+ {data.enabled && ( + <> +
+
+
+
+ + {isOver + ? `存储超额 · 超用 ${Math.abs(available).toFixed(2)} ${unit}` + : `剩余 ${available.toFixed(2)} ${unit}`} + + {rawPercent.toFixed(1)}% +
+ + )} +
+ ); +}; + 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 = () => {
+ + {/* 资源存储容量展示(来自 getUser.resourceCapacity) */} + @@ -714,6 +890,9 @@ const AppLayout: React.FC = () => { + {/* 资源存储容量展示(与桌面端共用 StorageCard) */} + + {topLevelItems.map(item => { const menuType = item.menu_type ?? item.menuType; const hasChildren = childMap[item.id] && childMap[item.id].length > 0; diff --git a/video-gen-app/src/index.css b/video-gen-app/src/index.css index 2c679d73..24078d5d 100644 --- a/video-gen-app/src/index.css +++ b/video-gen-app/src/index.css @@ -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); -} +} \ No newline at end of file diff --git a/video-gen-app/src/pages/CreativePlazaPage.tsx b/video-gen-app/src/pages/CreativePlazaPage.tsx index 4d58436c..0f34b088 100644 --- a/video-gen-app/src/pages/CreativePlazaPage.tsx +++ b/video-gen-app/src/pages/CreativePlazaPage.tsx @@ -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: , - title: 'AI智能生成', - description: '先进的AI技术,一键生成高质量视频内容', + icon: , + title: 'AI 智能生成', + description: '先进 AI 技术一键产出高质量视频内容', }, { - icon: , + icon: , title: '多格式输出', - description: '支持多种视频格式,满足不同场景需求', + description: '支持多种视频格式,满足不同场景需求', }, { - icon: , + icon: , title: '品质保证', - description: '专业级画质,细节清晰,色彩鲜艳', + description: '专业级画质,细节清晰,色彩鲜艳', }, { - icon: , + icon: , 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 ( -
-
-
-
- +
+ {/* 顶部 Hero 区域 */} +
+
+
+
- - 创意素材案例 - - 探索AI创作无限可能 +
+ + 创意素材案例 - +
+
+ + 探索 AI 创作无限可能 +
- - 精选AI生成的优秀素材案例,展示AI创作的无限潜力与创意灵感 + + 精选 AI 生成的优秀素材案例,展示 AI 创作的无限潜力与创意灵感
-
+ {/* 特性卡片 */} + {/*
{features.map((feature, index) => ( -
-
{feature.icon}
- +
+
+ {feature.icon} +
+
{feature.title} - - +
+
{feature.description} - +
))} -
+
*/} -
- - - 精选案例展示 - - -
- - -
- 敬请期待更多创意案例 +
+
+
+ + 精选案例展示 + +
+ {/* */}
- + + {/* 案例网格 */} +
+
+ + {/* + 会员可查看 */} +
+ + {caseList.map((item, index) => ( +
+
+ {/* 模糊视频缩略图占位 */} + + {/* 标签 */} +
+ {item.tag} +
+ +
+
+ + {item.title} + + +
+ +
+ ))} +
+ {/* 锁定遮罩 */} + + +
); }; -export default CreativePlazaPage; \ No newline at end of file +export default CreativePlazaPage; diff --git a/video-gen-app/src/pages/HomePage.tsx b/video-gen-app/src/pages/HomePage.tsx index 7e5ced8e..a4fc513f 100644 --- a/video-gen-app/src/pages/HomePage.tsx +++ b/video-gen-app/src/pages/HomePage.tsx @@ -72,7 +72,7 @@ const HomePage: React.FC = () => { setActiveTab(key); }; - + const aiEntries = [ { @@ -120,21 +120,43 @@ const HomePage: React.FC = () => { ]; return ( -
+
+
+

首页工作台

+

如需进行账户素材推送: + { + navigate('/authorization') + }}> + 一键推送 + + + + +

+
{/* ========== 顶部工作台引导区域(三步流程) ========== */}
@@ -151,7 +173,7 @@ const HomePage: React.FC = () => {
-
+ {/*
{ onClick={() => { navigate('/authorization') }} - >一键授权 + >如需进行账户素材推送 一键推送 -
+
*/}
@@ -553,7 +575,7 @@ const HomePage: React.FC = () => { 近期作品
- +
{/* Tab切换 */}
@@ -739,7 +761,7 @@ const HomePage: React.FC = () => { {/* ========== 素材案例区域 ========== */} -
{ alt={`素材案例 ${index + 1}`} style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> - {/* 案例悬停遮罩(由父级 hover 触发) */} + 案例悬停遮罩(由父级 hover 触发)
{
))}
-
+
*/}
); }; diff --git a/video-gen-app/src/pages/InitialInfo.tsx b/video-gen-app/src/pages/InitialInfo.tsx index 715f4440..f0532b95 100644 --- a/video-gen-app/src/pages/InitialInfo.tsx +++ b/video-gen-app/src/pages/InitialInfo.tsx @@ -31,6 +31,8 @@ function InitialInfo() { // 引擎和视频参数相关状态 const [enginesele, setEnginesele] = useState({}); const [countType, setCountType] = useState(''); + // 图片引擎 id:取自 getEngine().engine.image[0].id + const [imageEngineId, setImageEngineId] = useState(''); 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" diff --git a/video-gen-app/src/pages/InitialReplication.tsx b/video-gen-app/src/pages/InitialReplication.tsx index dafea04e..832afc4c 100644 --- a/video-gen-app/src/pages/InitialReplication.tsx +++ b/video-gen-app/src/pages/InitialReplication.tsx @@ -850,6 +850,7 @@ const GenerateConver: React.FC = () => { ) : ( { ) : ( { const handleUnlock = () => { - message.info('请联系客服开通'); + message.info('请联系客服开通会员'); }; + // 顶部统计 KPI + const stats = [ + { + icon: , + label: '今日爆款', + value: '1,286', + unit: '条', + trend: '+12.5%', + color: '#f59e0b', + }, + { + icon: , + label: '总浏览量', + value: '428', + unit: '万', + trend: '+8.2%', + color: '#6366f1', + }, + { + icon: , + label: '总互动数', + value: '76.4', + unit: '万', + trend: '+15.7%', + color: '#ec4899', + }, + { + icon: , + 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 ( -
-
-
-
- +
+ {/* Hero 区域 - 橙金主题(区别创意素材案例的紫粉) */} +
+
+
+
- - 行业爆款大盘 - - 发现热门素材趋势 +
+ + 行业爆款大盘 - +
+
+ + 实时追踪全网热门素材趋势 +
- - 实时追踪各行业爆款素材,把握创作风向,打造热门内容 + + 实时追踪各行业爆款素材,把握创作风向,打造热门内容
- - - - 行业热度排行 - - -
- 敬请期待行业热度排行 + {stats.map((item, index) => ( +
+
+
+ {item.icon} +
+
+ + {item.trend} +
+
+
+ {item.value} + {item.unit} +
+
{item.label}
- - + ))} +
*/} + {/* 行业热度排行 */} -
- - - 爆款素材榜单 - - +
+
+
+ + 行业热度排行 + + 基于近 7 天数据 · 实时更新 + +
+ 更新时间: 10 分钟前 +
+
+ + 实时追踪各行业爆款素材,把握创作风向,打造热门内容 + +
+ + 立即解锁全部 +
+
+ + + + +
+ {industryRank.map((item) => { + const rankColors: Record = { + 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' ? ( + + ) : item.change === 'down' ? ( + + ) : ( + + ); + + return ( +
+
+ {item.rank} +
+
+
+ {item.name} +
+
+
+
+
+
+ {item.hot} +
+
+ {item.value.toLocaleString()} 素材 +
+
{trendIcon}
+
+ ); + })} +
+
*/} + + {/* 爆款素材榜单 */} +
+
+
+
+ + 爆款素材榜单 + + {/* TOP 100 */} + +
+ {/* */} +
+ +
+
+ + {/* + 立即解锁全部 */} +
+ + {popularList.map((item) => ( +
+
+ + {/* 排名徽章 */} +
+ {item.tag} +
+ +
+
+
+ {item.title} +
+
+ {item.author} +
+
+ + + {item.views} + + + + {item.likes} + + + + {item.comments} + +
+
+ {/* 锁定遮罩 */} + {/*
+ + 会员可看 +
*/} +
+ ))} +
+ + +
+ + +
- -
- 敬请期待更多爆款素材 -
-
); }; -export default PopularPage; \ No newline at end of file +export default PopularPage; diff --git a/video-gen-app/src/pages/RemoveRw.tsx b/video-gen-app/src/pages/RemoveRw.tsx index 3478c8b9..d4e76c10 100644 --- a/video-gen-app/src/pages/RemoveRw.tsx +++ b/video-gen-app/src/pages/RemoveRw.tsx @@ -31,6 +31,8 @@ function InitialInfo() { // 引擎和视频参数相关状态 const [enginesele, setEnginesele] = useState({}); const [countType, setCountType] = useState(''); + // 图片引擎 id:取自 getEngine().engine.image[0].id + const [imageEngineId, setImageEngineId] = useState(''); 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" diff --git a/video-gen-app/src/types/index.ts b/video-gen-app/src/types/index.ts index 84615a10..2d88c4e9 100644 --- a/video-gen-app/src/types/index.ts +++ b/video-gen-app/src/types/index.ts @@ -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 {