1
This commit is contained in:
@@ -4,7 +4,10 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>VideoGen.AI 管理后台</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<title>后台管理</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--font-sans: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--nav-bg: #08080c;
|
||||
--nav-surface: rgba(255,255,255,0.04);
|
||||
--nav-border: rgba(255,255,255,0.06);
|
||||
--nav-text: #e8e8ec;
|
||||
--nav-text-muted: #8b8fa3;
|
||||
--nav-hover: rgba(255,255,255,0.08);
|
||||
--nav-active: rgba(255,255,255,0.12);
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after { box-sizing: border-box; }
|
||||
|
||||
html, body {
|
||||
margin: 0; padding: 0;
|
||||
font-family: var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
background: #f8f9fc;
|
||||
color: #1a1a2e;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
#root { min-height: 100vh; }
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(<App />)
|
||||
|
||||
@@ -181,26 +181,28 @@ const AdminLayout: React.FC = () => {
|
||||
collapsed={collapsed}
|
||||
onCollapse={setCollapsed}
|
||||
width={220}
|
||||
theme="dark"
|
||||
theme="light"
|
||||
style={{
|
||||
background: 'linear-gradient(180deg, #0f0f23 0%, #1a1a35 100%)',
|
||||
background: '#ffffff',
|
||||
borderRight: '1px solid #e5e7eb',
|
||||
}}
|
||||
>
|
||||
{/* Logo */}
|
||||
<div style={{
|
||||
height: 64, display: 'flex', alignItems: 'center',
|
||||
justifyContent: 'center', gap: 10,
|
||||
borderBottom: '1px solid rgba(255,255,255,0.06)',
|
||||
borderBottom: '1px solid #e5e7eb',
|
||||
}}>
|
||||
<div style={{
|
||||
width: 32, height: 32, borderRadius: 8,
|
||||
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(99,102,241,0.3)',
|
||||
}}>
|
||||
<ThunderboltOutlined style={{ fontSize: 16, color: '#fff' }} />
|
||||
</div>
|
||||
{!collapsed && (
|
||||
<span style={{ color: '#f1f5f9', fontSize: 15, fontWeight: 700 }}>
|
||||
<span style={{ color: '#1f2937', fontSize: 15, fontWeight: 600, letterSpacing: -0.02 }}>
|
||||
管理后台
|
||||
</span>
|
||||
)}
|
||||
@@ -212,12 +214,17 @@ const AdminLayout: React.FC = () => {
|
||||
selectedKeys={[activeKey]}
|
||||
defaultOpenKeys={openKeys}
|
||||
items={antMenuItems}
|
||||
style={{
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
paddingTop: 8,
|
||||
}}
|
||||
onClick={({ key }) => {
|
||||
if (key.startsWith('group-')) return;
|
||||
navigate(key);
|
||||
}}
|
||||
style={{ background: 'transparent', borderRight: 0, marginTop: 8 }}
|
||||
theme="dark"
|
||||
theme="light"
|
||||
/>
|
||||
|
||||
</Sider>
|
||||
@@ -228,6 +235,7 @@ const AdminLayout: React.FC = () => {
|
||||
height: 56, background: '#fff', borderBottom: '1px solid #f0f0f5',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||
padding: '0 24px',
|
||||
fontFamily: 'var(--font-sans)',
|
||||
}}>
|
||||
<Typography.Text strong style={{ fontSize: 16 }}>
|
||||
{antMenuItems.find(m => m.key === activeKey)?.label
|
||||
|
||||
@@ -80,7 +80,7 @@ router = APIRouter(prefix="/admin", tags=["admin"])
|
||||
@router.get("/users")
|
||||
async def list_users(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
page_size: int = Query(20, ge=1, le=500),
|
||||
search: str = Query(""),
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@@ -248,7 +248,7 @@ async def admin_change_password(
|
||||
@router.get("/credit-records")
|
||||
async def list_credit_records(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
page_size: int = Query(20, ge=1, le=500),
|
||||
user_id: str | None = Query(None),
|
||||
user_name: str | None = Query(None),
|
||||
type: str | None = Query(None),
|
||||
@@ -315,7 +315,7 @@ async def list_credit_records(
|
||||
@router.get("/notifications")
|
||||
async def list_admin_notifications(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
page_size: int = Query(20, ge=1, le=500),
|
||||
user_id: str | None = Query(None),
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@@ -573,7 +573,7 @@ async def get_payment_stats(
|
||||
@router.get("/payment-orders")
|
||||
async def list_payment_orders(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
page_size: int = Query(20, ge=1, le=500),
|
||||
payment_method: str | None = Query(None),
|
||||
status: str | None = Query(None),
|
||||
start_date: str | None = Query(None),
|
||||
@@ -1086,7 +1086,7 @@ async def update_system_config(
|
||||
@router.get("/operation-logs")
|
||||
async def list_operation_logs(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
page_size: int = Query(20, ge=1, le=500),
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
@@ -1279,7 +1279,7 @@ async def get_stats(
|
||||
@router.get("/token-usage")
|
||||
async def list_token_usage(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
page_size: int = Query(20, ge=1, le=500),
|
||||
user_id: str | None = Query(None),
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@@ -1321,7 +1321,7 @@ async def admin_list_generation_records(
|
||||
user_id: str | None = Query(None),
|
||||
status: str | None = Query(None),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
page_size: int = Query(20, ge=1, le=500),
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>video-gen-app</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<title>民众智创</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -119,10 +119,10 @@ const EXPANDED_W = 240;
|
||||
const COLLAPSED_W = 68;
|
||||
|
||||
const GRADIENTS = [
|
||||
{ gradient: 'linear-gradient(135deg, #f59e0b, #f97316)', shadow: 'rgba(245,158,11,0.3)', icon: <StarFilled /> },
|
||||
{ gradient: 'linear-gradient(135deg, #6366f1, #8b5cf6)', shadow: 'rgba(99,102,241,0.3)', icon: <FireFilled /> },
|
||||
{ gradient: 'linear-gradient(135deg, #06b6d4, #0ea5e9)', shadow: 'rgba(6,182,212,0.3)', icon: <CrownFilled /> },
|
||||
{ gradient: 'linear-gradient(135deg, #10b981, #059669)', shadow: 'rgba(16,185,129,0.3)', icon: <BankFilled /> },
|
||||
{ gradient: 'linear-gradient(135deg, #c9a96e, #a67c52)', shadow: 'rgba(201,169,110,0.25)', icon: <StarFilled /> },
|
||||
{ gradient: 'linear-gradient(135deg, #4a5568, #2d3748)', shadow: 'rgba(74,85,104,0.25)', icon: <FireFilled /> },
|
||||
{ gradient: 'linear-gradient(135deg, #718096, #4a5568)', shadow: 'rgba(113,128,150,0.25)', icon: <CrownFilled /> },
|
||||
{ gradient: 'linear-gradient(135deg, #5a67d8, #434190)', shadow: 'rgba(90,103,216,0.25)', icon: <BankFilled /> },
|
||||
];
|
||||
|
||||
const AppLayout: React.FC = () => {
|
||||
@@ -264,7 +264,7 @@ const AppLayout: React.FC = () => {
|
||||
|
||||
const userMenuItems = [
|
||||
{ key: 'profile', icon: <UserOutlined />, label: `账号: ${user?.username}`, disabled: true },
|
||||
{ key: 'credits', icon: <WalletOutlined />, label: `可用积分: ${user?.credits ?? 0}`, disabled: true },
|
||||
{ key: 'credits', icon: <WalletOutlined style={{ color: '#c9a96e' }} />, label: `积分: ${user?.credits ?? 0}`, disabled: true },
|
||||
{ type: 'divider' as const },
|
||||
{ key: 'myCredits', icon: <WalletOutlined />, label: '积分明细' },
|
||||
{ key: 'orderRecords', icon: <FileTextOutlined />, label: '订单记录' },
|
||||
@@ -369,8 +369,8 @@ const AppLayout: React.FC = () => {
|
||||
{/* Desktop Sidebar */}
|
||||
<div className="desktop-sidebar" style={{
|
||||
width: sidebarW, position: 'fixed', left: 0, top: 0, bottom: 0, zIndex: 100,
|
||||
background: 'linear-gradient(180deg, #0f0f23 0%, #1a1a35 100%)',
|
||||
borderRight: '1px solid rgba(255,255,255,0.05)',
|
||||
background: 'var(--nav-bg)',
|
||||
borderRight: '1px solid var(--nav-border)',
|
||||
display: 'flex', flexDirection: 'column',
|
||||
transition: 'width 0.25s ease',
|
||||
overflow: 'hidden',
|
||||
@@ -379,26 +379,26 @@ const AppLayout: React.FC = () => {
|
||||
<div style={{
|
||||
height: 72, display: 'flex', alignItems: 'center',
|
||||
justifyContent: collapsed ? 'center' : 'space-between',
|
||||
borderBottom: '1px solid rgba(255,255,255,0.06)',
|
||||
borderBottom: '1px solid #e5e7eb',
|
||||
padding: collapsed ? '0' : '0 16px 0 20px',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, overflow: 'hidden' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, overflow: 'hidden' }}>
|
||||
<div style={{
|
||||
width: 36, height: 36, borderRadius: 10, flexShrink: 0,
|
||||
width: 38, height: 38, borderRadius: 12, flexShrink: 0,
|
||||
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
boxShadow: '0 4px 12px rgba(99,102,241,0.3)', overflow: 'hidden',
|
||||
boxShadow: '0 2px 12px rgba(99,102,241,0.3)', overflow: 'hidden',
|
||||
}}>
|
||||
{siteLogo ? (
|
||||
<img src={siteLogo} alt="logo" style={{ width: 28, height: 28, objectFit: 'contain' }} />
|
||||
) : (
|
||||
<ThunderboltOutlined style={{ fontSize: 18, color: '#fff' }} />
|
||||
<ThunderboltOutlined style={{ fontSize: 18, color: '#1f2937' }} />
|
||||
)}
|
||||
</div>
|
||||
{!collapsed && (
|
||||
<span style={{
|
||||
color: '#f1f5f9', fontSize: 17, fontWeight: 800, letterSpacing: -0.5,
|
||||
color: 'var(--nav-text)', fontSize: 16, fontWeight: 600, letterSpacing: -0.02,
|
||||
whiteSpace: 'nowrap', opacity: collapsed ? 0 : 1,
|
||||
transition: 'opacity 0.2s ease',
|
||||
}}>
|
||||
@@ -411,7 +411,7 @@ const AppLayout: React.FC = () => {
|
||||
{/* Menu */}
|
||||
<div style={{ flex: 1, padding: '8px 8px', overflow: 'auto' }}>
|
||||
{/* {!collapsed && (
|
||||
<div style={{ color: 'rgba(148,163,184,0.4)', fontSize: 11, fontWeight: 600, padding: '8px 12px 6px', letterSpacing: 1 }}>
|
||||
<div style={{ color: 'rgba(0,0,0,0.3)', fontSize: 11, fontWeight: 600, padding: '8px 12px 6px', letterSpacing: 1 }}>
|
||||
导航
|
||||
</div>
|
||||
)} */}
|
||||
@@ -434,13 +434,12 @@ const AppLayout: React.FC = () => {
|
||||
<div key={item.id} onClick={() => item.path && navigate(item.path)} style={{
|
||||
display: 'flex', alignItems: 'center',
|
||||
justifyContent: collapsed ? 'center' : 'flex-start',
|
||||
gap: collapsed ? 0 : 10,
|
||||
padding: collapsed ? '10px 0' : depth > 0 ? '8px 14px 8px 32px' : '10px 14px',
|
||||
borderRadius: 10, margin: '2px 0', cursor: 'pointer',
|
||||
fontSize: depth > 0 ? 12 : 13, fontWeight: isActive ? 600 : 400,
|
||||
color: isActive ? '#fff' : 'rgba(148,163,184,0.75)',
|
||||
background: isActive ? 'linear-gradient(135deg, rgba(99,102,241,0.2), rgba(139,92,246,0.15))' : 'transparent',
|
||||
border: isActive ? '1px solid rgba(99,102,241,0.2)' : '1px solid transparent',
|
||||
gap: collapsed ? 0 : 12,
|
||||
padding: collapsed ? '10px 0' : depth > 0 ? '7px 14px 7px 32px' : '9px 16px',
|
||||
borderRadius: 8, margin: '1px 0', cursor: 'pointer',
|
||||
fontSize: depth > 0 ? 12 : 13, fontWeight: isActive ? 500 : 400,
|
||||
color: isActive ? 'var(--nav-text)' : 'var(--nav-text-muted)',
|
||||
background: isActive ? 'var(--nav-active)' : 'transparent',
|
||||
transition: 'all 0.2s ease',
|
||||
}}>
|
||||
<span style={{ fontSize: depth > 0 ? 14 : 16, flexShrink: 0 }}>{menuIcon}</span>
|
||||
@@ -457,8 +456,8 @@ const AppLayout: React.FC = () => {
|
||||
items.push(
|
||||
<div key={g.id}>
|
||||
<div style={{
|
||||
color: 'rgba(148,163,184,0.4)', fontSize: 11, fontWeight: 600,
|
||||
padding: '8px 12px 6px', letterSpacing: 1,
|
||||
color: 'var(--nav-text-muted)', fontSize: 10, fontWeight: 500,
|
||||
padding: '8px 16px 6px', letterSpacing: 0.05, textTransform: 'uppercase',
|
||||
}}>
|
||||
{g.label}
|
||||
</div>
|
||||
@@ -483,23 +482,23 @@ const AppLayout: React.FC = () => {
|
||||
<div onClick={() => setRechargeModalOpen(true)} style={{
|
||||
margin: collapsed ? '4px auto' : '4px 14px',
|
||||
padding: collapsed ? '10px' : '10px 14px',
|
||||
borderRadius: 12, cursor: 'pointer',
|
||||
borderRadius: 10, cursor: 'pointer',
|
||||
display: 'flex', alignItems: 'center', justifyContent: collapsed ? 'center' : 'flex-start',
|
||||
gap: collapsed ? 0 : 10,
|
||||
color: '#818cf8', fontSize: 13, fontWeight: 600,
|
||||
background: 'linear-gradient(135deg, rgba(99,102,241,0.2), rgba(139,92,246,0.2))',
|
||||
border: '1px solid rgba(99,102,241,0.25)',
|
||||
color: '#c9a96e', fontSize: 13, fontWeight: 500,
|
||||
background: 'rgba(201,169,110,0.08)',
|
||||
border: '1px solid rgba(201,169,110,0.15)',
|
||||
transition: 'all 0.2s',
|
||||
}}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.background = 'linear-gradient(135deg, rgba(99,102,241,0.3), rgba(139,92,246,0.3))'; }}
|
||||
onMouseLeave={(e) => { e.currentTarget.style.background = 'linear-gradient(135deg, rgba(99,102,241,0.2), rgba(139,92,246,0.2))'; }}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.background = 'rgba(201,169,110,0.15)'; }}
|
||||
onMouseLeave={(e) => { e.currentTarget.style.background = 'rgba(201,169,110,0.08)'; }}
|
||||
>
|
||||
<PlusOutlined style={{ fontSize: 14 }} />
|
||||
{!collapsed && <span>充值积分</span>}
|
||||
</div>
|
||||
|
||||
{/* User block at bottom-left */}
|
||||
<div style={{ padding: collapsed ? '12px 8px' : '14px 14px', borderTop: '1px solid rgba(255,255,255,0.06)', flexShrink: 0 }}>
|
||||
<div style={{ padding: collapsed ? '12px 8px' : '14px 14px', borderTop: '1px solid #e5e7eb', flexShrink: 0 }}>
|
||||
<Dropdown menu={{ items: userMenuItems, onClick: handleUserMenuClick }} placement="topRight" arrow>
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center',
|
||||
@@ -507,19 +506,19 @@ const AppLayout: React.FC = () => {
|
||||
gap: collapsed ? 0 : 10,
|
||||
padding: collapsed ? '8px' : '8px 10px',
|
||||
borderRadius: 12, cursor: 'pointer', transition: 'background 0.2s',
|
||||
background: 'rgba(255,255,255,0.03)',
|
||||
background: 'rgba(0,0,0,0.02)',
|
||||
}}
|
||||
onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(255,255,255,0.06)'}
|
||||
onMouseLeave={(e) => e.currentTarget.style.background = 'rgba(255,255,255,0.03)'}
|
||||
onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(0,0,0,0.04)'}
|
||||
onMouseLeave={(e) => e.currentTarget.style.background = 'rgba(0,0,0,0.02)'}
|
||||
>
|
||||
<Avatar size={32} icon={<UserOutlined />}
|
||||
style={{ background: 'linear-gradient(135deg, #6366f1, #8b5cf6)', flexShrink: 0 }} />
|
||||
style={{ background: 'linear-gradient(135deg, #4a5568, #2d3748)', flexShrink: 0 }} />
|
||||
{!collapsed && (
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ color: '#e2e8f0', fontSize: 13, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
<div style={{ color: 'var(--nav-text)', fontSize: 13, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', letterSpacing: -0.01 }}>
|
||||
{user?.username}
|
||||
</div>
|
||||
<div style={{ color: 'rgba(148,163,184,0.5)', fontSize: 11 }}>可用积分:{user?.credits || 0}</div>
|
||||
<div style={{ color: 'var(--nav-text-muted)', fontSize: 11, letterSpacing: 0 }}>积分: {user?.credits || 0}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -543,7 +542,7 @@ const AppLayout: React.FC = () => {
|
||||
position: 'absolute', left: '50%', top: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 24, height: 56, borderRadius: '0 8px 8px 0',
|
||||
background: '#1a1a35', border: '1px solid rgba(255,255,255,0.1)', borderLeft: 'none',
|
||||
background: '#f8f9fb', border: '1px solid #e5e7eb', borderLeft: 'none',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
|
||||
boxShadow: '2px 0 12px rgba(0,0,0,0.2)',
|
||||
animation: 'fadeInRight 0.2s ease both',
|
||||
@@ -557,7 +556,7 @@ const AppLayout: React.FC = () => {
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="desktop-content" style={{
|
||||
marginLeft: sidebarW, flex: 1, minHeight: '100vh', background: '#f5f6fa',
|
||||
marginLeft: sidebarW, flex: 1, minHeight: '100vh', background: '#f8f9fc',
|
||||
padding: '24px 32px 32px', transition: 'margin-left 0.25s ease',
|
||||
}}>
|
||||
<Outlet />
|
||||
@@ -618,8 +617,8 @@ const AppLayout: React.FC = () => {
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Space style={{ marginBottom: 16 }}>
|
||||
<WalletOutlined style={{ color: '#6366f1' }} />
|
||||
<Typography.Text style={{ color: '#64748b' }}>当前积分余额:</Typography.Text>
|
||||
<Typography.Text strong style={{ color: '#6366f1', fontSize: 18 }}>{user?.credits ?? 0}</Typography.Text>
|
||||
<Typography.Text style={{ color: '#64748b', letterSpacing: 0 }}>当前积分余额</Typography.Text>
|
||||
<Typography.Text strong style={{ color: '#6366f1', fontSize: 20, fontWeight: 600 }}>{user?.credits ?? 0}</Typography.Text>
|
||||
</Space>
|
||||
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
|
||||
{rechargeOptions.map((opt, idx) => {
|
||||
|
||||
+26
-13
@@ -1,4 +1,15 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--font-sans: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--nav-bg: #f8f9fb;
|
||||
--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);
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
@@ -6,11 +17,12 @@
|
||||
|
||||
html, body {
|
||||
margin: 0; padding: 0;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
font-family: var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
background: #f5f6fa;
|
||||
background: #f8f9fc;
|
||||
color: #1a1a2e;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
#root { min-height: 100vh; }
|
||||
|
||||
@@ -120,26 +132,27 @@ html, body {
|
||||
.mobile-bottom-nav {
|
||||
display: none;
|
||||
position: fixed; bottom: 0; left: 0; right: 0; z-index: 200;
|
||||
height: 64px;
|
||||
background: rgba(255,255,255,0.92);
|
||||
backdrop-filter: blur(20px);
|
||||
border-top: 1px solid rgba(0,0,0,0.06);
|
||||
box-shadow: 0 -2px 12px rgba(0,0,0,0.06);
|
||||
height: 60px;
|
||||
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);
|
||||
}
|
||||
.mobile-bottom-nav .nav-item {
|
||||
flex: 1; display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center; gap: 3px;
|
||||
align-items: center; justify-content: center; gap: 2px;
|
||||
cursor: pointer; transition: all 0.2s;
|
||||
color: #94a3b8; font-size: 10px; font-weight: 500;
|
||||
color: #8b8fa3; font-size: 9px; font-weight: 400;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
.mobile-bottom-nav .nav-item.active {
|
||||
color: #6366f1;
|
||||
color: #08080c;
|
||||
}
|
||||
.mobile-bottom-nav .nav-item .nav-icon {
|
||||
font-size: 20px; transition: transform 0.2s;
|
||||
font-size: 18px; transition: transform 0.2s;
|
||||
}
|
||||
.mobile-bottom-nav .nav-item.active .nav-icon {
|
||||
transform: scale(1.1);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* ── Responsive Breakpoints ────────────── */
|
||||
|
||||
Reference in New Issue
Block a user