Merge branch 'main' of https://gitee.com/wg123/video-gen
This commit is contained in:
@@ -7,7 +7,6 @@ from app.enums.user import *
|
|||||||
from app.enums.credit_record import *
|
from app.enums.credit_record import *
|
||||||
from app.enums.token_usage import *
|
from app.enums.token_usage import *
|
||||||
from app.enums.generation_task import *
|
from app.enums.generation_task import *
|
||||||
from app.enums.recent_generation import *
|
|
||||||
from app.enums.generation_status import *
|
from app.enums.generation_status import *
|
||||||
from app.enums.sms import *
|
from app.enums.sms import *
|
||||||
from app.enums.notification import *
|
from app.enums.notification import *
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="/assets/index-4U-_hqyL.js"></script>
|
<script type="module" crossorigin src="/assets/index-CyfX-OBY.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-eLRg4pQk.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-eLRg4pQk.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -183,69 +183,3 @@
|
|||||||
border-right-color: var(--border);
|
border-right-color: var(--border);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Login Page Responsive Styles */
|
|
||||||
.login-container {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 900px) {
|
|
||||||
.login-container {
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-left-section {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 32px 16px;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 900px) {
|
|
||||||
.login-left-section {
|
|
||||||
padding: 0 80px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-features {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 900px) {
|
|
||||||
.login-features {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-right-section {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 1;
|
|
||||||
padding: 16px 16px 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 900px) {
|
|
||||||
.login-right-section {
|
|
||||||
padding: 40px 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.login-left-section {
|
|
||||||
padding: 24px 12px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-right-section {
|
|
||||||
padding: 12px 12px 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -460,7 +460,13 @@ const AppLayout: React.FC = () => {
|
|||||||
|
|
||||||
{/* Menu */}
|
{/* Menu */}
|
||||||
<div style={{ flex: 1, padding: '8px 8px', overflow: 'auto' }}>
|
<div style={{ flex: 1, padding: '8px 8px', overflow: 'auto' }}>
|
||||||
|
{/* {!collapsed && (
|
||||||
|
<div style={{ color: 'rgba(0,0,0,0.3)', fontSize: 11, fontWeight: 600, padding: '8px 12px 6px', letterSpacing: 1 }}>
|
||||||
|
导航
|
||||||
|
</div>
|
||||||
|
)} */}
|
||||||
{(() => {
|
{(() => {
|
||||||
|
// Build child map for all menu items
|
||||||
const childMap: Record<string, MenuConfig[]> = {};
|
const childMap: Record<string, MenuConfig[]> = {};
|
||||||
menuItems.forEach(m => {
|
menuItems.forEach(m => {
|
||||||
const pid = m.parent_id ?? m.parentId;
|
const pid = m.parent_id ?? m.parentId;
|
||||||
@@ -470,8 +476,10 @@ const AppLayout: React.FC = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get top-level items (parent_id is null/undefined)
|
||||||
const topLevelItems = menuItems.filter(m => !(m.parent_id ?? m.parentId));
|
const topLevelItems = menuItems.filter(m => !(m.parent_id ?? m.parentId));
|
||||||
|
|
||||||
|
// Sort top-level items by sort_order
|
||||||
topLevelItems.sort((a, b) => {
|
topLevelItems.sort((a, b) => {
|
||||||
const orderA = typeof a.sortOrder === 'number' ? a.sortOrder : Infinity;
|
const orderA = typeof a.sortOrder === 'number' ? a.sortOrder : Infinity;
|
||||||
const orderB = typeof b.sortOrder === 'number' ? b.sortOrder : Infinity;
|
const orderB = typeof b.sortOrder === 'number' ? b.sortOrder : Infinity;
|
||||||
@@ -483,123 +491,52 @@ const AppLayout: React.FC = () => {
|
|||||||
const renderMenuItem = (item: MenuConfig, depth: number = 0) => {
|
const renderMenuItem = (item: MenuConfig, depth: number = 0) => {
|
||||||
const isActive = item.path === selectedKey;
|
const isActive = item.path === selectedKey;
|
||||||
const menuIcon = iconMap[item.icon] || <HomeOutlined />;
|
const menuIcon = iconMap[item.icon] || <HomeOutlined />;
|
||||||
const hasChildren = childMap[item.id] && childMap[item.id].length > 0;
|
|
||||||
const isExpanded = collapsedGroups[item.id] !== true;
|
|
||||||
|
|
||||||
const handleClick = () => {
|
|
||||||
if (hasChildren) {
|
|
||||||
setCollapsedGroups(prev => ({
|
|
||||||
...prev,
|
|
||||||
[item.id]: !prev[item.id]
|
|
||||||
}));
|
|
||||||
} else if (item.path) {
|
|
||||||
navigate(item.path);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={item.id}>
|
<div key={item.id} onClick={() => item.path && navigate(item.path)} style={{
|
||||||
<div
|
display: 'flex', alignItems: 'center',
|
||||||
onClick={handleClick}
|
justifyContent: 'flex-start',
|
||||||
style={{
|
gap: 10,
|
||||||
display: 'flex', alignItems: 'center',
|
padding: depth > 0 ? '6px 12px 6px 32px' : '6px 14px',
|
||||||
justifyContent: 'flex-start',
|
borderRadius: 12, margin: '1px 4px', cursor: 'pointer',
|
||||||
gap: 10,
|
fontSize: depth > 0 ? 13 : 14, fontWeight: isActive ? 600 : 400,
|
||||||
padding: depth > 0 ? '8px 12px 8px 32px' : '8px 14px',
|
color: isActive ? '#4f46e5' : '#475569',
|
||||||
borderRadius: 12, margin: '2px 4px', cursor: 'pointer',
|
background: isActive ? 'linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(139, 92, 246, 0.08) 100%)' : 'transparent',
|
||||||
fontSize: depth > 0 ? 13 : 14, fontWeight: isActive ? 600 : 400,
|
transition: 'all 0.2s ease',
|
||||||
color: isActive ? '#4f46e5' : '#475569',
|
}}>
|
||||||
background: isActive ? 'linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(139, 92, 246, 0.08) 100%)' : 'transparent',
|
<span style={{
|
||||||
transition: 'all 0.2s ease',
|
fontSize: depth > 0 ? 14 : 16,
|
||||||
}}
|
flexShrink: 0,
|
||||||
onMouseEnter={(e) => {
|
color: isActive ? '#6366f1' : '#64748b',
|
||||||
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.05)';
|
}}>{menuIcon}</span>
|
||||||
}}
|
<span style={{ whiteSpace: 'nowrap' }}>{item.label}</span>
|
||||||
onMouseLeave={(e) => {
|
|
||||||
if (!isActive) {
|
|
||||||
e.currentTarget.style.background = 'transparent';
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span style={{
|
|
||||||
fontSize: depth > 0 ? 14 : 16,
|
|
||||||
flexShrink: 0,
|
|
||||||
color: isActive ? '#6366f1' : '#64748b',
|
|
||||||
}}>{menuIcon}</span>
|
|
||||||
<span style={{ whiteSpace: 'nowrap', flex: 1, textAlign: 'left' }}>{item.label}</span>
|
|
||||||
{hasChildren && (
|
|
||||||
<span style={{
|
|
||||||
fontSize: 14,
|
|
||||||
color: '#94a3b8',
|
|
||||||
transition: 'transform 0.2s ease',
|
|
||||||
transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)',
|
|
||||||
}}>
|
|
||||||
<RightOutlined />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{hasChildren && isExpanded && (
|
|
||||||
<div style={{ overflow: 'hidden' }}>
|
|
||||||
{(childMap[item.id] || []).sort((a, b) => {
|
|
||||||
const orderA = typeof a.sortOrder === 'number' ? a.sortOrder : Infinity;
|
|
||||||
const orderB = typeof b.sortOrder === 'number' ? b.sortOrder : Infinity;
|
|
||||||
return orderA - orderB;
|
|
||||||
}).map(c => renderMenuItem(c, depth + 1))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Render top-level items and their children in order
|
||||||
topLevelItems.forEach(item => {
|
topLevelItems.forEach(item => {
|
||||||
const menuType = item.menu_type ?? item.menuType;
|
const menuType = item.menu_type ?? item.menuType;
|
||||||
const hasChildren = childMap[item.id] && childMap[item.id].length > 0;
|
|
||||||
const isExpanded = collapsedGroups[item.id] !== true;
|
|
||||||
|
|
||||||
if (menuType === 'group') {
|
if (menuType === 'group') {
|
||||||
|
// Render group with its children
|
||||||
|
const children = (childMap[item.id] || []).sort((a, b) => {
|
||||||
|
const orderA = typeof a.sortOrder === 'number' ? a.sortOrder : Infinity;
|
||||||
|
const orderB = typeof b.sortOrder === 'number' ? b.sortOrder : Infinity;
|
||||||
|
return orderA - orderB;
|
||||||
|
});
|
||||||
items.push(
|
items.push(
|
||||||
<div key={item.id}>
|
<div key={item.id}>
|
||||||
<div
|
<div style={{
|
||||||
onClick={() => {
|
color: '#94a3b8', fontSize: 12, fontWeight: 600,
|
||||||
setCollapsedGroups(prev => ({
|
padding: '10px 14px 4px', letterSpacing: 0.5, textTransform: 'uppercase',
|
||||||
...prev,
|
}}>
|
||||||
[item.id]: !prev[item.id]
|
{item.label}
|
||||||
}));
|
</div>
|
||||||
}}
|
{children.map(c => renderMenuItem(c, 1))}
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
color: '#94a3b8', fontSize: 12, fontWeight: 600,
|
|
||||||
padding: '10px 14px 4px', letterSpacing: 0.5, textTransform: 'uppercase',
|
|
||||||
cursor: 'pointer',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>{item.label}</span>
|
|
||||||
<span style={{
|
|
||||||
fontSize: 12,
|
|
||||||
color: '#cbd5e1',
|
|
||||||
transition: 'transform 0.2s ease',
|
|
||||||
transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)',
|
|
||||||
}}>
|
|
||||||
<RightOutlined />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{isExpanded && (
|
|
||||||
<div style={{ overflow: 'hidden' }}>
|
|
||||||
{(childMap[item.id] || []).sort((a, b) => {
|
|
||||||
const orderA = typeof a.sortOrder === 'number' ? a.sortOrder : Infinity;
|
|
||||||
const orderB = typeof b.sortOrder === 'number' ? b.sortOrder : Infinity;
|
|
||||||
return orderA - orderB;
|
|
||||||
}).map(c => renderMenuItem(c, 1))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (hasChildren) {
|
|
||||||
items.push(renderMenuItem(item));
|
|
||||||
} else {
|
} else {
|
||||||
|
// Render standalone page
|
||||||
items.push(renderMenuItem(item));
|
items.push(renderMenuItem(item));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -280,7 +280,9 @@ const LoginPage: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="login-container" style={{
|
<div style={{
|
||||||
|
minHeight: '100vh',
|
||||||
|
display: 'flex',
|
||||||
backgroundImage: `url(/backimage.png)`,
|
backgroundImage: `url(/backimage.png)`,
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
@@ -308,34 +310,37 @@ const LoginPage: React.FC = () => {
|
|||||||
}} />
|
}} />
|
||||||
|
|
||||||
{/* Left side - features */}
|
{/* Left side - features */}
|
||||||
<div className="login-left-section">
|
<div style={{
|
||||||
<Space direction="vertical" size={24}>
|
flex: 1, display: 'flex', flexDirection: 'column',
|
||||||
|
justifyContent: 'center', padding: '0 80px', zIndex: 1,
|
||||||
|
}}>
|
||||||
|
<Space direction="vertical" size={36}>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 16 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 20 }}>
|
||||||
{siteLogo ? (
|
{siteLogo ? (
|
||||||
<img src={siteLogo} alt="logo" style={{ width: 48, height: 48, borderRadius: 12, objectFit: 'contain' }} />
|
<img src={siteLogo} alt="logo" style={{ width: 52, height: 52, borderRadius: 14, objectFit: 'contain' }} />
|
||||||
) : (
|
) : (
|
||||||
<div style={{
|
<div style={{
|
||||||
width: 48, height: 48, borderRadius: 12,
|
width: 52, height: 52, borderRadius: 14,
|
||||||
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
boxShadow: '0 8px 24px rgba(99,102,241,0.25)',
|
boxShadow: '0 8px 24px rgba(99,102,241,0.25)',
|
||||||
}}>
|
}}>
|
||||||
<ThunderboltOutlined style={{ fontSize: 24, color: '#fff' }} />
|
<ThunderboltOutlined style={{ fontSize: 26, color: '#fff' }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<span style={{ color: '#1e293b', fontSize: 24, fontWeight: 800, letterSpacing: -0.5 }}>
|
<span style={{ color: '#1e293b', fontSize: 28, fontWeight: 800, letterSpacing: -0.5 }}>
|
||||||
{siteName}
|
{siteName}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="shiny-text-container" style={{ fontSize: 20 }}>
|
<div className="shiny-text-container">
|
||||||
<span className="shiny-text">AI赋能创意,素材触手可及</span>
|
<span className="shiny-text">AI赋能创意,素材触手可及</span>
|
||||||
</div>
|
</div>
|
||||||
<Typography.Paragraph style={{ color: '#64748b', fontSize: 15, maxWidth: 480, lineHeight: 1.8 }}>
|
<Typography.Paragraph style={{ color: '#64748b', fontSize: 17, maxWidth: 480, lineHeight: 1.8 }}>
|
||||||
专业的 AI 素材生成平台,通过智能提示词优化,让您的创意快速转化为精美视频、图片
|
专业的 AI 素材生成平台,通过智能提示词优化,<br />让您的创意快速转化为精美视频、图片
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
</div>
|
</div>
|
||||||
<div className="login-features">
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
|
||||||
{features.map((f, i) => (
|
{features.map((f, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
@@ -370,12 +375,15 @@ const LoginPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right side - login/register form */}
|
{/* Right side - login/register form */}
|
||||||
<div className="login-right-section">
|
<div style={{
|
||||||
|
flex: 1, display: 'flex', alignItems: 'center',
|
||||||
|
justifyContent: 'center', zIndex: 1, padding: '40px 24px',
|
||||||
|
}}>
|
||||||
<Card style={{
|
<Card style={{
|
||||||
width: '100%', maxWidth: 400, borderRadius: 16,
|
width: 440, maxWidth: '100%', borderRadius: 20,
|
||||||
boxShadow: '0 12px 40px rgba(0,0,0,0.08)',
|
boxShadow: '0 20px 60px rgba(0,0,0,0.08)',
|
||||||
border: '1px solid #e2e8f0', background: '#fff',
|
border: '1px solid #e2e8f0', background: '#fff',
|
||||||
}} styles={{ body: { padding: '24px 20px' } }}>
|
}} styles={{ body: { padding: '36px 28px' } }}>
|
||||||
<Typography.Title level={3} style={{ textAlign: 'center', marginBottom: 6, color: '#1e293b', fontWeight: 700 }}>
|
<Typography.Title level={3} style={{ textAlign: 'center', marginBottom: 6, color: '#1e293b', fontWeight: 700 }}>
|
||||||
{mode === 'register' ? '创建账号' : '欢迎回来'}
|
{mode === 'register' ? '创建账号' : '欢迎回来'}
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
|
|||||||
Reference in New Issue
Block a user