feat: 登录页5种主流高级风格重写 + 主题/登录页设置独立
5个登录组件全面重新设计: - SplitView: 分屏聚焦风格(Linear/Notion) — 左55%深色渐变+几何动画装饰,右45%纯净表单+底线输入框 - CenteredCard: 沉浸渐变风格(Stripe/Vercel) — 紫蓝辐射背景+毛玻璃卡片+发光效果 - SidePanel: 品牌侧栏风格(Slack/Figma) — 左38%大号标语+feature pills,右62%宽留白表单 - FullImage: 粒子网格风格(GitHub/AWS) — CSS粒子光点+网格线+发光球体+深色内嵌卡片 - MinimalDark: 极简线条风格(Apple/Linear) — 纯黑背景+16px渐变圆点logo+宽字距+底线输入框 AppearanceSettings 主题与登录页完全独立: - 界面配色区: 5种主题配色即时预览 - 登录页风格区: 5种风格缩略图预览+参考品牌标签(Linear/Notion, Stripe/Vercel等) - 移除登录页主题切换按钮,风格与主题彻底解耦
This commit is contained in:
1 parent
ed582e30e6
commit
46054c8b2f
6 files changed
+836
-227
No files matched your search
@@ -1,57 +1,175 @@
|
|||||||
import { useLogin } from '@/hooks/useLogin';
|
import { useLogin } from '@/hooks/useLogin';
|
||||||
import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme';
|
|
||||||
import { Sun, Moon } from 'lucide-react';
|
|
||||||
|
|
||||||
export default function CenteredCard() {
|
export default function CenteredCard() {
|
||||||
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
|
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
|
||||||
const { theme, toggleTheme } = useTheme();
|
|
||||||
const isDark = THEME_OPTIONS.find(o => o.id === theme)?.isDark ?? true;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24, position: 'relative' }}>
|
<div
|
||||||
{/* Gradient background */}
|
className="flex items-center justify-center min-h-screen relative"
|
||||||
<div className="dg-gradient-br" style={{ position: 'fixed', inset: 0, zIndex: 0 }} />
|
style={{ padding: 24 }}
|
||||||
|
|
||||||
{/* Theme toggle */}
|
|
||||||
<button
|
|
||||||
onClick={toggleTheme}
|
|
||||||
style={{ position: 'fixed', top: 20, right: 20, zIndex: 50, background: 'rgba(255,255,255,0.1)', border: 'none', borderRadius: 8, padding: 8, cursor: 'pointer', color: '#fff', backdropFilter: 'blur(4px)' }}
|
|
||||||
>
|
>
|
||||||
{isDark ? <Sun size={18} /> : <Moon size={18} />}
|
{/* Full-screen dark gradient background with noise texture */}
|
||||||
</button>
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
inset: 0,
|
||||||
|
zIndex: 0,
|
||||||
|
background: `
|
||||||
|
radial-gradient(ellipse at 50% 40%, #0a0a1a 0%, #1a0a2e 70%, #0f0520 100%)
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Subtle noise / dot pattern overlay */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
inset: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
background: `
|
||||||
|
radial-gradient(circle at 20% 80%, rgba(99, 102, 241, 0.04) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 80% 20%, rgba(139, 92, 246, 0.05) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 50% 50%, rgba(99,102,241,0.02) 0%, transparent 80%)
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Extra floating glow orbs */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: '10%',
|
||||||
|
left: '60%',
|
||||||
|
width: 400,
|
||||||
|
height: 400,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'radial-gradient(circle, rgba(99,102,241,0.06), transparent 70%)',
|
||||||
|
zIndex: 1,
|
||||||
|
filter: 'blur(40px)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: '5%',
|
||||||
|
right: '60%',
|
||||||
|
width: 300,
|
||||||
|
height: 300,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'radial-gradient(circle, rgba(139,92,246,0.05), transparent 70%)',
|
||||||
|
zIndex: 1,
|
||||||
|
filter: 'blur(40px)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Card */}
|
{/* Center glassmorphism card */}
|
||||||
<div style={{ position: 'relative', zIndex: 10, width: '100%', maxWidth: 400, background: 'var(--dg-bg-primary)', borderRadius: 14, padding: '40px 32px', boxShadow: '0 8px 32px rgba(0,0,0,0.3)' }}>
|
<div
|
||||||
<div style={{ textAlign: 'center', marginBottom: 32 }}>
|
style={{
|
||||||
<h1 style={{ fontSize: 24, fontWeight: 700, marginBottom: 6 }}>
|
position: 'relative',
|
||||||
<span className="dg-gradient-text">SQL审核平台</span>
|
zIndex: 10,
|
||||||
</h1>
|
width: '100%',
|
||||||
<p style={{ color: 'var(--dg-text-muted)', fontSize: 14 }}>登录您的账号</p>
|
maxWidth: 420,
|
||||||
|
background: 'rgba(255,255,255,0.03)',
|
||||||
|
backdropFilter: 'blur(24px)',
|
||||||
|
WebkitBackdropFilter: 'blur(24px)',
|
||||||
|
borderRadius: 20,
|
||||||
|
padding: '48px 36px',
|
||||||
|
boxShadow: `
|
||||||
|
0 0 0 1px rgba(255,255,255,0.06),
|
||||||
|
0 8px 40px rgba(0,0,0,0.4),
|
||||||
|
0 0 80px rgba(99,102,241,0.08)
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Logo icon */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 44,
|
||||||
|
height: 44,
|
||||||
|
borderRadius: 12,
|
||||||
|
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
margin: '0 auto 28px',
|
||||||
|
boxShadow: '0 4px 20px rgba(99,102,241,0.35)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<ellipse cx="12" cy="5" rx="9" ry="3" />
|
||||||
|
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
|
||||||
|
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h1 style={{ textAlign: 'center', fontSize: 22, fontWeight: 600, color: '#fff', marginBottom: 6 }}>
|
||||||
|
SQL审核平台
|
||||||
|
</h1>
|
||||||
|
<p style={{ textAlign: 'center', color: 'rgba(255,255,255,0.45)', fontSize: 14, marginBottom: 36 }}>
|
||||||
|
登录您的工作区
|
||||||
|
</p>
|
||||||
|
|
||||||
<form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
<form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||||
{error && <div style={{ color: '#f87171', fontSize: 13, textAlign: 'center' }}>{error}</div>}
|
{error && (
|
||||||
|
<div style={{ color: '#f87171', fontSize: 13, textAlign: 'center', padding: '10px 14px', background: 'rgba(248,113,113,0.08)', borderRadius: 8 }}>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="用户名"
|
placeholder="用户名"
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
style={{ padding: '11px 14px', borderRadius: 8, background: 'var(--dg-bg-secondary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
padding: '13px 16px',
|
||||||
|
borderRadius: 10,
|
||||||
|
background: 'rgba(255,255,255,0.06)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.08)',
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 14,
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'border-color 0.2s, background 0.2s',
|
||||||
|
}}
|
||||||
|
onFocus={(e) => { e.target.style.borderColor = 'rgba(99,102,241,0.4)'; e.target.style.background = 'rgba(255,255,255,0.08)'; }}
|
||||||
|
onBlur={(e) => { e.target.style.borderColor = 'rgba(255,255,255,0.08)'; e.target.style.background = 'rgba(255,255,255,0.06)'; }}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="密码"
|
placeholder="密码"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
style={{ padding: '11px 14px', borderRadius: 8, background: 'var(--dg-bg-secondary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
padding: '13px 16px',
|
||||||
|
borderRadius: 10,
|
||||||
|
background: 'rgba(255,255,255,0.06)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.08)',
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 14,
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'border-color 0.2s, background 0.2s',
|
||||||
|
}}
|
||||||
|
onFocus={(e) => { e.target.style.borderColor = 'rgba(99,102,241,0.4)'; e.target.style.background = 'rgba(255,255,255,0.08)'; }}
|
||||||
|
onBlur={(e) => { e.target.style.borderColor = 'rgba(255,255,255,0.08)'; e.target.style.background = 'rgba(255,255,255,0.06)'; }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading || !username || !password}
|
disabled={loading || !username || !password}
|
||||||
className="dg-gradient-bg"
|
className="dg-gradient-bg w-full"
|
||||||
style={{ padding: '11px 14px', borderRadius: 8, border: 'none', color: '#fff', fontWeight: 600, fontSize: 14, cursor: loading || !username || !password ? 'not-allowed' : 'pointer', opacity: loading || !username || !password ? 0.6 : 1, marginTop: 4 }}
|
style={{
|
||||||
|
padding: '13px 20px',
|
||||||
|
borderRadius: 10,
|
||||||
|
border: 'none',
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 15,
|
||||||
|
cursor: loading || !username || !password ? 'not-allowed' : 'pointer',
|
||||||
|
opacity: loading || !username || !password ? 0.5 : 1,
|
||||||
|
marginTop: 8,
|
||||||
|
transition: 'opacity 0.2s, transform 0.1s',
|
||||||
|
boxShadow: '0 4px 20px rgba(99,102,241,0.25)',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{loading ? '登录中…' : '登录'}
|
{loading ? '登录中…' : '登录'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,74 +1,204 @@
|
|||||||
import { useLogin } from '@/hooks/useLogin';
|
import { useLogin } from '@/hooks/useLogin';
|
||||||
import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme';
|
|
||||||
import { Sun, Moon } from 'lucide-react';
|
|
||||||
|
|
||||||
export default function FullImage() {
|
export default function FullImage() {
|
||||||
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
|
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
|
||||||
const { theme, toggleTheme } = useTheme();
|
|
||||||
const isDark = THEME_OPTIONS.find(o => o.id === theme)?.isDark ?? true;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24, position: 'relative', overflow: 'hidden' }}>
|
<div
|
||||||
{/* Circuit board pattern background */}
|
className="flex items-center justify-center min-h-screen relative"
|
||||||
|
style={{ padding: 24, overflow: 'hidden' }}
|
||||||
|
>
|
||||||
|
{/* Deep dark background #0d1117 */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed', inset: 0, zIndex: 0,
|
position: 'fixed',
|
||||||
background: `
|
inset: 0,
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 48px, var(--dg-border) 48px, var(--dg-border) 49px),
|
zIndex: 0,
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 48px, var(--dg-border) 48px, var(--dg-border) 49px),
|
background: '#0d1117',
|
||||||
var(--dg-bg-primary)
|
|
||||||
`,
|
|
||||||
opacity: 0.5,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Theme toggle */}
|
{/* Particle grid — dots and lines */}
|
||||||
<button
|
|
||||||
onClick={toggleTheme}
|
|
||||||
style={{ position: 'fixed', top: 20, right: 20, zIndex: 50, background: 'var(--dg-bg-tertiary)', border: 'none', borderRadius: 8, padding: 8, cursor: 'pointer', color: 'var(--dg-text-secondary)', backdropFilter: 'blur(8px)' }}
|
|
||||||
>
|
|
||||||
{isDark ? <Sun size={18} /> : <Moon size={18} />}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Glass card */}
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'relative', zIndex: 10, width: '100%', maxWidth: 400,
|
position: 'fixed',
|
||||||
background: 'rgba(0,0,0,0.45)', backdropFilter: 'blur(16px)',
|
inset: 0,
|
||||||
borderRadius: 16, padding: '40px 32px',
|
zIndex: 1,
|
||||||
color: '#fff',
|
backgroundImage: `
|
||||||
|
radial-gradient(circle at 15% 25%, rgba(99,102,241,0.15) 1px, transparent 1px),
|
||||||
|
radial-gradient(circle at 35% 60%, rgba(99,102,241,0.12) 1px, transparent 1px),
|
||||||
|
radial-gradient(circle at 55% 15%, rgba(139,92,246,0.12) 1px, transparent 1px),
|
||||||
|
radial-gradient(circle at 75% 45%, rgba(99,102,241,0.10) 1px, transparent 1px),
|
||||||
|
radial-gradient(circle at 85% 70%, rgba(139,92,246,0.10) 1px, transparent 1px),
|
||||||
|
radial-gradient(circle at 25% 80%, rgba(99,102,241,0.08) 1px, transparent 1px),
|
||||||
|
radial-gradient(circle at 65% 85%, rgba(139,92,246,0.08) 1px, transparent 1px),
|
||||||
|
radial-gradient(circle at 45% 40%, rgba(99,102,241,0.14) 1px, transparent 1px),
|
||||||
|
radial-gradient(circle at 10% 55%, rgba(139,92,246,0.06) 1px, transparent 1px),
|
||||||
|
radial-gradient(circle at 90% 20%, rgba(99,102,241,0.08) 1px, transparent 1px),
|
||||||
|
radial-gradient(circle at 5% 90%, rgba(139,92,246,0.07) 1px, transparent 1px),
|
||||||
|
radial-gradient(circle at 50% 70%, rgba(99,102,241,0.06) 1px, transparent 1px)
|
||||||
|
`,
|
||||||
|
backgroundSize: '200px 200px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Connecting grid lines */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
inset: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
backgroundImage: `
|
||||||
|
linear-gradient(rgba(99,102,241,0.03) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(99,102,241,0.03) 1px, transparent 1px)
|
||||||
|
`,
|
||||||
|
backgroundSize: '80px 80px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Large glow orb — top right */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: '-15%',
|
||||||
|
right: '-10%',
|
||||||
|
width: 600,
|
||||||
|
height: 600,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'radial-gradient(circle, rgba(99,102,241,0.12) 0%, rgba(139,92,246,0.06) 40%, transparent 70%)',
|
||||||
|
zIndex: 1,
|
||||||
|
filter: 'blur(60px)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Secondary glow — bottom left */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: '-20%',
|
||||||
|
left: '-10%',
|
||||||
|
width: 400,
|
||||||
|
height: 400,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'radial-gradient(circle, rgba(139,92,246,0.06), transparent 70%)',
|
||||||
|
zIndex: 1,
|
||||||
|
filter: 'blur(40px)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Center card */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 10,
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: 400,
|
||||||
|
background: 'rgba(13, 17, 23, 0.8)',
|
||||||
|
backdropFilter: 'blur(20px)',
|
||||||
|
WebkitBackdropFilter: 'blur(20px)',
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: '40px 32px',
|
||||||
|
boxShadow: `
|
||||||
|
0 0 0 1px rgba(255,255,255,0.08),
|
||||||
|
0 8px 40px rgba(0,0,0,0.5),
|
||||||
|
0 0 100px rgba(99,102,241,0.04)
|
||||||
|
`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* Small brand mark at top */}
|
||||||
<div style={{ textAlign: 'center', marginBottom: 32 }}>
|
<div style={{ textAlign: 'center', marginBottom: 32 }}>
|
||||||
<h1 style={{ fontSize: 24, fontWeight: 700, marginBottom: 6 }}>
|
<div
|
||||||
<span className="dg-gradient-text">SQL审核平台</span>
|
style={{
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
borderRadius: 10,
|
||||||
|
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginBottom: 20,
|
||||||
|
boxShadow: '0 2px 12px rgba(99,102,241,0.3)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<ellipse cx="12" cy="5" rx="9" ry="3" />
|
||||||
|
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
|
||||||
|
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1 style={{ fontSize: 20, fontWeight: 600, color: '#fff', marginBottom: 4 }}>
|
||||||
|
SQL审核平台
|
||||||
</h1>
|
</h1>
|
||||||
<p style={{ color: 'rgba(255,255,255,0.6)', fontSize: 14 }}>登录您的工作区</p>
|
<p style={{ color: 'rgba(255,255,255,0.4)', fontSize: 13 }}>
|
||||||
|
登录您的工作区
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
<form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||||
{error && <div style={{ color: '#f87171', fontSize: 13, textAlign: 'center' }}>{error}</div>}
|
{error && (
|
||||||
|
<div style={{ color: '#f87171', fontSize: 13, textAlign: 'center', padding: '10px 14px', background: 'rgba(248,113,113,0.06)', borderRadius: 8 }}>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Dark inset inputs */}
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="用户名"
|
placeholder="用户名"
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
style={{ padding: '11px 14px', borderRadius: 8, background: 'rgba(255,255,255,0.08)', border: '1px solid rgba(255,255,255,0.12)', color: '#fff', fontSize: 14, outline: 'none' }}
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderRadius: 8,
|
||||||
|
background: 'rgba(255,255,255,0.04)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.06)',
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 14,
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'border-color 0.2s, background 0.2s',
|
||||||
|
boxShadow: 'inset 0 1px 3px rgba(0,0,0,0.2)',
|
||||||
|
}}
|
||||||
|
onFocus={(e) => { e.target.style.borderColor = 'rgba(99,102,241,0.3)'; e.target.style.background = 'rgba(255,255,255,0.06)'; }}
|
||||||
|
onBlur={(e) => { e.target.style.borderColor = 'rgba(255,255,255,0.06)'; e.target.style.background = 'rgba(255,255,255,0.04)'; }}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="密码"
|
placeholder="密码"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
style={{ padding: '11px 14px', borderRadius: 8, background: 'rgba(255,255,255,0.08)', border: '1px solid rgba(255,255,255,0.12)', color: '#fff', fontSize: 14, outline: 'none' }}
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderRadius: 8,
|
||||||
|
background: 'rgba(255,255,255,0.04)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.06)',
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 14,
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'border-color 0.2s, background 0.2s',
|
||||||
|
boxShadow: 'inset 0 1px 3px rgba(0,0,0,0.2)',
|
||||||
|
}}
|
||||||
|
onFocus={(e) => { e.target.style.borderColor = 'rgba(99,102,241,0.3)'; e.target.style.background = 'rgba(255,255,255,0.06)'; }}
|
||||||
|
onBlur={(e) => { e.target.style.borderColor = 'rgba(255,255,255,0.06)'; e.target.style.background = 'rgba(255,255,255,0.04)'; }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading || !username || !password}
|
disabled={loading || !username || !password}
|
||||||
className="dg-gradient-bg"
|
className="dg-gradient-bg w-full"
|
||||||
style={{ padding: '11px 14px', borderRadius: 8, border: 'none', color: '#fff', fontWeight: 600, fontSize: 14, cursor: loading || !username || !password ? 'not-allowed' : 'pointer', opacity: loading || !username || !password ? 0.6 : 1, marginTop: 4 }}
|
style={{
|
||||||
|
padding: '12px 20px',
|
||||||
|
borderRadius: 8,
|
||||||
|
border: 'none',
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 14,
|
||||||
|
cursor: loading || !username || !password ? 'not-allowed' : 'pointer',
|
||||||
|
opacity: loading || !username || !password ? 0.5 : 1,
|
||||||
|
marginTop: 6,
|
||||||
|
transition: 'opacity 0.2s, transform 0.1s',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{loading ? '登录中…' : '登录'}
|
{loading ? '登录中…' : '登录'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,51 +1,118 @@
|
|||||||
import { useLogin } from '@/hooks/useLogin';
|
import { useLogin } from '@/hooks/useLogin';
|
||||||
import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme';
|
|
||||||
import { Sun, Moon, Database } from 'lucide-react';
|
|
||||||
|
|
||||||
export default function MinimalDark() {
|
export default function MinimalDark() {
|
||||||
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
|
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
|
||||||
const { theme, toggleTheme } = useTheme();
|
|
||||||
const isDark = THEME_OPTIONS.find(o => o.id === theme)?.isDark ?? true;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 24, background: 'var(--dg-bg-primary)', color: 'var(--dg-text-primary)' }}>
|
<div
|
||||||
<button
|
className="flex flex-col items-center justify-center min-h-screen"
|
||||||
onClick={toggleTheme}
|
style={{ background: '#0a0a0a', padding: 24 }}
|
||||||
style={{ position: 'fixed', top: 20, right: 20, zIndex: 50, background: 'var(--dg-bg-tertiary)', border: 'none', borderRadius: 8, padding: 8, cursor: 'pointer', color: 'var(--dg-text-secondary)' }}
|
|
||||||
>
|
>
|
||||||
{isDark ? <Sun size={18} /> : <Moon size={18} />}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div style={{ width: '100%', maxWidth: 320, textAlign: 'center' }}>
|
<div style={{ width: '100%', maxWidth: 320, textAlign: 'center' }}>
|
||||||
<Database size={36} className="dg-gradient-text" style={{ marginBottom: 16, display: 'inline-block' }} />
|
{/* Tiny gradient dot logo */}
|
||||||
<h1 style={{ fontSize: 22, fontWeight: 700, marginBottom: 4 }}>
|
<div
|
||||||
<span className="dg-gradient-text">SQL审核平台</span>
|
style={{
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
||||||
|
margin: '0 auto 24px',
|
||||||
|
boxShadow: '0 0 12px rgba(99,102,241,0.3)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Brand name with wide tracking */}
|
||||||
|
<h1
|
||||||
|
className="tracking-wider"
|
||||||
|
style={{ fontSize: 15, fontWeight: 500, color: 'rgba(255,255,255,0.8)', letterSpacing: '3px', marginBottom: 0 }}
|
||||||
|
>
|
||||||
|
SQL审核平台
|
||||||
</h1>
|
</h1>
|
||||||
<p style={{ color: 'var(--dg-text-muted)', fontSize: 13, marginBottom: 36 }}>安全的数据库审核与合规管理</p>
|
|
||||||
|
|
||||||
<form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
{/* Thin divider */}
|
||||||
{error && <div style={{ color: '#f87171', fontSize: 13 }}>{error}</div>}
|
<div
|
||||||
|
style={{
|
||||||
|
width: 40,
|
||||||
|
height: 1,
|
||||||
|
background: 'rgba(255,255,255,0.1)',
|
||||||
|
margin: '32px auto',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<form
|
||||||
|
onSubmit={handleLogin}
|
||||||
|
style={{ display: 'flex', flexDirection: 'column' }}
|
||||||
|
>
|
||||||
|
{error && (
|
||||||
|
<div style={{ color: '#f87171', fontSize: 13, marginBottom: 20, textAlign: 'left' }}>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Borderless inputs — bottom line only */}
|
||||||
|
<div style={{ marginBottom: 24 }}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="用户名"
|
placeholder="用户名"
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
style={{ padding: '10px 14px', borderRadius: 8, background: 'var(--dg-bg-secondary)', border: 'none', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
padding: '12px 0',
|
||||||
|
background: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
borderBottom: '1px solid rgba(255,255,255,0.1)',
|
||||||
|
borderRadius: 0,
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 14,
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'border-color 0.25s',
|
||||||
|
}}
|
||||||
|
onFocus={(e) => { e.target.style.borderBottomColor = 'rgba(99,102,241,0.5)'; }}
|
||||||
|
onBlur={(e) => { e.target.style.borderBottomColor = 'rgba(255,255,255,0.1)'; }}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 40 }}>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="密码"
|
placeholder="密码"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
style={{ padding: '10px 14px', borderRadius: 8, background: 'var(--dg-bg-secondary)', border: 'none', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
padding: '12px 0',
|
||||||
|
background: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
borderBottom: '1px solid rgba(255,255,255,0.1)',
|
||||||
|
borderRadius: 0,
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 14,
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'border-color 0.25s',
|
||||||
|
}}
|
||||||
|
onFocus={(e) => { e.target.style.borderBottomColor = 'rgba(99,102,241,0.5)'; }}
|
||||||
|
onBlur={(e) => { e.target.style.borderBottomColor = 'rgba(255,255,255,0.1)'; }}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Full-width slim button */}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading || !username || !password}
|
disabled={loading || !username || !password}
|
||||||
className="dg-gradient-bg"
|
className="dg-gradient-bg w-full"
|
||||||
style={{ padding: '10px 14px', borderRadius: 8, border: 'none', color: '#fff', fontWeight: 600, fontSize: 14, cursor: loading || !username || !password ? 'not-allowed' : 'pointer', opacity: loading || !username || !password ? 0.6 : 1, marginTop: 8 }}
|
style={{
|
||||||
|
padding: '12px 20px',
|
||||||
|
borderRadius: 6,
|
||||||
|
border: 'none',
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: 14,
|
||||||
|
cursor: loading || !username || !password ? 'not-allowed' : 'pointer',
|
||||||
|
opacity: loading || !username || !password ? 0.4 : 1,
|
||||||
|
transition: 'opacity 0.2s, transform 0.1s',
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{loading ? '登录中…' : '登录'}
|
{loading ? '登录中…' : '登录'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,70 +1,180 @@
|
|||||||
import { useLogin } from '@/hooks/useLogin';
|
import { useLogin } from '@/hooks/useLogin';
|
||||||
import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme';
|
|
||||||
import { Sun, Moon, Database } from 'lucide-react';
|
const features = ['SQL审核', '变更审批', '实时监控', '权限管理'];
|
||||||
|
|
||||||
export default function SidePanel() {
|
export default function SidePanel() {
|
||||||
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
|
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
|
||||||
const { theme, toggleTheme } = useTheme();
|
|
||||||
const isDark = THEME_OPTIONS.find(o => o.id === theme)?.isDark ?? true;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', minHeight: '100vh', background: 'var(--dg-bg-primary)', color: 'var(--dg-text-primary)' }}>
|
<div className="flex min-h-screen" style={{ background: 'var(--dg-bg-primary)', color: 'var(--dg-text-primary)' }}>
|
||||||
<button
|
{/* Left Panel — 38% Brand Area */}
|
||||||
onClick={toggleTheme}
|
|
||||||
style={{ position: 'fixed', top: 20, right: 20, zIndex: 50, background: 'var(--dg-bg-tertiary)', border: 'none', borderRadius: 8, padding: 8, cursor: 'pointer', color: 'var(--dg-text-secondary)' }}
|
|
||||||
>
|
|
||||||
{isDark ? <Sun size={18} /> : <Moon size={18} />}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Left Panel — 40% */}
|
|
||||||
<div
|
<div
|
||||||
className="dg-gradient-br"
|
className="relative flex flex-col justify-between"
|
||||||
style={{ width: '40%', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', padding: 48, position: 'relative', overflow: 'hidden' }}
|
style={{
|
||||||
|
width: '38%',
|
||||||
|
background: 'linear-gradient(180deg, #111827 0%, #1f2937 100%)',
|
||||||
|
padding: '56px 44px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{/* Decorative circles */}
|
{/* Subtle decorative gradient orb */}
|
||||||
<div style={{ position: 'absolute', top: '15%', left: '10%', width: 80, height: 80, borderRadius: '50%', background: 'rgba(255,255,255,0.06)' }} />
|
<div
|
||||||
<div style={{ position: 'absolute', bottom: '20%', right: '15%', width: 120, height: 120, borderRadius: '50%', background: 'rgba(255,255,255,0.04)' }} />
|
style={{
|
||||||
<div style={{ position: 'absolute', top: '55%', left: '60%', width: 40, height: 40, borderRadius: '50%', background: 'rgba(255,255,255,0.07)' }} />
|
position: 'absolute',
|
||||||
<div style={{ position: 'absolute', top: '30%', right: '30%', width: 12, height: 12, borderRadius: '50%', background: 'rgba(255,255,255,0.12)' }} />
|
top: '-80px',
|
||||||
<div style={{ position: 'absolute', bottom: '40%', left: '25%', width: 8, height: 8, borderRadius: '50%', background: 'rgba(255,255,255,0.10)' }} />
|
right: '-80px',
|
||||||
|
width: 280,
|
||||||
|
height: 280,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'radial-gradient(circle, rgba(99,102,241,0.08), transparent 70%)',
|
||||||
|
filter: 'blur(30px)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<div style={{ position: 'relative', zIndex: 2, textAlign: 'center', maxWidth: 320 }}>
|
{/* Top brand section */}
|
||||||
<Database size={40} style={{ color: '#fff', marginBottom: 20 }} />
|
<div style={{ position: 'relative', zIndex: 2 }}>
|
||||||
<h1 style={{ fontSize: 26, fontWeight: 700, color: '#fff', marginBottom: 12 }}>SQL审核平台</h1>
|
{/* Logo mark */}
|
||||||
<p style={{ color: 'rgba(255,255,255,0.7)', fontSize: 14, lineHeight: 1.6 }}>
|
<div
|
||||||
为团队提供智能 SQL 审核、实时合规监控与数据库安全保障。
|
style={{
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 10,
|
||||||
|
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginBottom: 56,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<ellipse cx="12" cy="5" rx="9" ry="3" />
|
||||||
|
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
|
||||||
|
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stacked large text — left aligned */}
|
||||||
|
<div style={{ marginBottom: 24 }}>
|
||||||
|
<div style={{ fontSize: 52, fontWeight: 700, color: '#fff', lineHeight: 1.1, letterSpacing: '-1px' }}>
|
||||||
|
SQL审核
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 52, fontWeight: 700, color: '#fff', lineHeight: 1.1, letterSpacing: '-1px', marginTop: 4 }}>
|
||||||
|
平台
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p style={{ color: 'rgba(255,255,255,0.4)', fontSize: 15, lineHeight: 1.6, maxWidth: 280 }}>
|
||||||
|
企业级数据库审核与合规管理解决方案,为团队提供安全可靠的数据管理体验。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom feature pills */}
|
||||||
|
<div style={{ position: 'relative', zIndex: 2, display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
||||||
|
{features.map((tag) => (
|
||||||
|
<span
|
||||||
|
key={tag}
|
||||||
|
style={{
|
||||||
|
padding: '6px 14px',
|
||||||
|
borderRadius: 20,
|
||||||
|
background: 'rgba(255,255,255,0.06)',
|
||||||
|
color: 'rgba(255,255,255,0.6)',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
letterSpacing: '0.3px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Panel — Login Form */}
|
{/* Right Panel — 62% Login Form */}
|
||||||
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 48 }}>
|
<div
|
||||||
<form onSubmit={handleLogin} style={{ width: '100%', maxWidth: 360, display: 'flex', flexDirection: 'column', gap: 20 }}>
|
className="flex items-center justify-center"
|
||||||
<h2 style={{ fontSize: 22, fontWeight: 600, marginBottom: 4 }}>欢迎回来</h2>
|
style={{ flex: 1, padding: 64 }}
|
||||||
<p style={{ color: 'var(--dg-text-muted)', fontSize: 14, marginBottom: 8 }}>登录以访问控制台</p>
|
>
|
||||||
|
<form
|
||||||
|
onSubmit={handleLogin}
|
||||||
|
style={{ width: '100%', maxWidth: 380, display: 'flex', flexDirection: 'column' }}
|
||||||
|
>
|
||||||
|
<h2 style={{ fontSize: 30, fontWeight: 600, marginBottom: 8, color: 'var(--dg-text-primary)' }}>
|
||||||
|
欢迎回来
|
||||||
|
</h2>
|
||||||
|
<p style={{ color: 'var(--dg-text-muted)', fontSize: 15, marginBottom: 44 }}>
|
||||||
|
登录以继续
|
||||||
|
</p>
|
||||||
|
|
||||||
{error && <div style={{ color: '#f87171', fontSize: 13 }}>{error}</div>}
|
{error && (
|
||||||
|
<div style={{ color: '#f87171', fontSize: 13, marginBottom: 16, padding: '10px 14px', background: 'rgba(248,113,113,0.08)', borderRadius: 8 }}>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 24 }}>
|
||||||
|
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, color: 'var(--dg-text-secondary)', marginBottom: 8 }}>
|
||||||
|
用户名
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="用户名"
|
placeholder="请输入用户名"
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
style={{ padding: '10px 14px', borderRadius: 8, background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
padding: '13px 16px',
|
||||||
|
borderRadius: 10,
|
||||||
|
background: 'var(--dg-bg-secondary)',
|
||||||
|
border: 'none',
|
||||||
|
color: 'var(--dg-text-primary)',
|
||||||
|
fontSize: 14,
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'background 0.2s',
|
||||||
|
}}
|
||||||
|
onFocus={(e) => { e.target.style.background = 'var(--dg-bg-tertiary)'; }}
|
||||||
|
onBlur={(e) => { e.target.style.background = 'var(--dg-bg-secondary)'; }}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 36 }}>
|
||||||
|
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, color: 'var(--dg-text-secondary)', marginBottom: 8 }}>
|
||||||
|
密码
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="密码"
|
placeholder="请输入密码"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
style={{ padding: '10px 14px', borderRadius: 8, background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
padding: '13px 16px',
|
||||||
|
borderRadius: 10,
|
||||||
|
background: 'var(--dg-bg-secondary)',
|
||||||
|
border: 'none',
|
||||||
|
color: 'var(--dg-text-primary)',
|
||||||
|
fontSize: 14,
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'background 0.2s',
|
||||||
|
}}
|
||||||
|
onFocus={(e) => { e.target.style.background = 'var(--dg-bg-tertiary)'; }}
|
||||||
|
onBlur={(e) => { e.target.style.background = 'var(--dg-bg-secondary)'; }}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading || !username || !password}
|
disabled={loading || !username || !password}
|
||||||
className="dg-gradient-bg"
|
className="dg-gradient-bg w-full"
|
||||||
style={{ padding: '10px 14px', borderRadius: 8, border: 'none', color: '#fff', fontWeight: 600, fontSize: 14, cursor: loading || !username || !password ? 'not-allowed' : 'pointer', opacity: loading || !username || !password ? 0.6 : 1 }}
|
style={{
|
||||||
|
padding: '13px 20px',
|
||||||
|
borderRadius: 10,
|
||||||
|
border: 'none',
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 15,
|
||||||
|
cursor: loading || !username || !password ? 'not-allowed' : 'pointer',
|
||||||
|
opacity: loading || !username || !password ? 0.5 : 1,
|
||||||
|
transition: 'opacity 0.2s, transform 0.1s',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{loading ? '登录中…' : '登录'}
|
{loading ? '登录中…' : '登录'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,82 +1,246 @@
|
|||||||
import { useLogin } from '@/hooks/useLogin';
|
import { useLogin } from '@/hooks/useLogin';
|
||||||
import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme';
|
|
||||||
import { Sun, Moon, Database, Code, GitBranch, Bell, History, UserCog } from 'lucide-react';
|
|
||||||
|
|
||||||
const features = [
|
|
||||||
{ icon: Database, label: 'SQL审核', desc: '实时查询分析与合规检测' },
|
|
||||||
{ icon: Code, label: '智能规则', desc: '自定义审核规则引擎' },
|
|
||||||
{ icon: GitBranch, label: '变更管理', desc: 'DDL/DML 变更审批流' },
|
|
||||||
{ icon: Bell, label: '即时通知', desc: '多渠道消息推送' },
|
|
||||||
{ icon: History, label: '审计追踪', desc: '完整操作历史记录' },
|
|
||||||
{ icon: UserCog, label: '权限管理', desc: '基于角色的访问控制' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function SplitView() {
|
export default function SplitView() {
|
||||||
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
|
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
|
||||||
const { theme, toggleTheme } = useTheme();
|
|
||||||
const isDark = THEME_OPTIONS.find(o => o.id === theme)?.isDark ?? true;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', minHeight: '100vh', background: 'var(--dg-bg-primary)', color: 'var(--dg-text-primary)' }}>
|
<div className="flex min-h-screen" style={{ background: 'var(--dg-bg-primary)', color: 'var(--dg-text-primary)' }}>
|
||||||
<button
|
{/* Left Panel — 55% Brand Area */}
|
||||||
onClick={toggleTheme}
|
<div
|
||||||
style={{ position: 'fixed', top: 20, right: 20, zIndex: 50, background: 'var(--dg-bg-tertiary)', border: 'none', borderRadius: 8, padding: 8, cursor: 'pointer', color: 'var(--dg-text-secondary)' }}
|
className="relative overflow-hidden flex items-center justify-center"
|
||||||
|
style={{
|
||||||
|
width: '55%',
|
||||||
|
background: 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)',
|
||||||
|
padding: '48px 56px',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{isDark ? <Sun size={18} /> : <Moon size={18} />}
|
{/* Animated geometric decorations */}
|
||||||
</button>
|
{/* Large circle — top right */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '-60px',
|
||||||
|
right: '-40px',
|
||||||
|
width: 260,
|
||||||
|
height: 260,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'rgba(99, 102, 241, 0.08)',
|
||||||
|
animation: 'splitFloat1 8s ease-in-out infinite',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Medium circle — bottom left */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '40px',
|
||||||
|
left: '30px',
|
||||||
|
width: 160,
|
||||||
|
height: 160,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'rgba(139, 92, 246, 0.06)',
|
||||||
|
animation: 'splitFloat2 10s ease-in-out infinite',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Small circle — center upper */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '25%',
|
||||||
|
right: '18%',
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'rgba(99, 102, 241, 0.15)',
|
||||||
|
animation: 'splitFloat3 6s ease-in-out infinite',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Tiny dot */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '65%',
|
||||||
|
left: '20%',
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'rgba(255,255,255,0.12)',
|
||||||
|
animation: 'splitFloat1 7s ease-in-out infinite',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Decorative lines */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '15%',
|
||||||
|
left: '10%',
|
||||||
|
width: 120,
|
||||||
|
height: 1,
|
||||||
|
background: 'linear-gradient(90deg, rgba(99,102,241,0.3), transparent)',
|
||||||
|
transform: 'rotate(-25deg)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '25%',
|
||||||
|
right: '10%',
|
||||||
|
width: 180,
|
||||||
|
height: 1,
|
||||||
|
background: 'linear-gradient(90deg, rgba(139,92,246,0.2), transparent)',
|
||||||
|
transform: 'rotate(15deg)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Another small ring */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '15%',
|
||||||
|
right: '25%',
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: '50%',
|
||||||
|
border: '1px solid rgba(99,102,241,0.2)',
|
||||||
|
animation: 'splitFloat2 9s ease-in-out infinite',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Left Panel */}
|
{/* Brand content */}
|
||||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '48px 40px', background: 'var(--dg-bg-secondary)' }}>
|
<div style={{ position: 'relative', zIndex: 2, maxWidth: 420 }}>
|
||||||
<div style={{ maxWidth: 440 }}>
|
{/* Logo mark */}
|
||||||
<h1 style={{ fontSize: 28, fontWeight: 700, marginBottom: 8 }}>
|
<div
|
||||||
<span className="dg-gradient-text">SQL审核平台</span>
|
style={{
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 12,
|
||||||
|
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginBottom: 40,
|
||||||
|
boxShadow: '0 4px 24px rgba(99,102,241,0.3)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<ellipse cx="12" cy="5" rx="9" ry="3" />
|
||||||
|
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
|
||||||
|
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 style={{ fontSize: 40, fontWeight: 700, color: '#fff', marginBottom: 16, letterSpacing: '-0.5px', lineHeight: 1.2 }}>
|
||||||
|
SQL审核平台
|
||||||
</h1>
|
</h1>
|
||||||
<p style={{ color: 'var(--dg-text-muted)', fontSize: 15, marginBottom: 36, lineHeight: 1.5 }}>
|
<p style={{ color: 'rgba(255,255,255,0.5)', fontSize: 16, lineHeight: 1.7, fontWeight: 400 }}>
|
||||||
企业级 SQL 审核与合规管理平台。
|
安全 · 高效 · 可信赖的企业级数据库管理
|
||||||
</p>
|
</p>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
|
|
||||||
{features.map((f) => (
|
|
||||||
<div key={f.label} style={{ padding: 16, borderRadius: 10, background: 'var(--dg-bg-tertiary)' }}>
|
|
||||||
<f.icon size={20} className="dg-gradient-text" />
|
|
||||||
<div style={{ fontWeight: 600, fontSize: 13, marginTop: 8 }}>{f.label}</div>
|
|
||||||
<div style={{ fontSize: 12, color: 'var(--dg-text-muted)', marginTop: 2 }}>{f.desc}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Panel — Login Form */}
|
{/* Keyframe animations */}
|
||||||
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 48 }}>
|
<style>{`
|
||||||
|
@keyframes splitFloat1 {
|
||||||
|
0%, 100% { transform: translateY(0); }
|
||||||
|
50% { transform: translateY(-20px); }
|
||||||
|
}
|
||||||
|
@keyframes splitFloat2 {
|
||||||
|
0%, 100% { transform: translateY(0); }
|
||||||
|
50% { transform: translateY(15px); }
|
||||||
|
}
|
||||||
|
@keyframes splitFloat3 {
|
||||||
|
0%, 100% { transform: translate(0, 0); }
|
||||||
|
50% { transform: translate(8px, -12px); }
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Panel — 45% Login Form */}
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-center"
|
||||||
|
style={{ width: '45%', padding: 48 }}
|
||||||
|
>
|
||||||
<form
|
<form
|
||||||
onSubmit={handleLogin}
|
onSubmit={handleLogin}
|
||||||
style={{ width: '100%', maxWidth: 360, display: 'flex', flexDirection: 'column', gap: 20 }}
|
style={{ width: '100%', maxWidth: 340, display: 'flex', flexDirection: 'column' }}
|
||||||
>
|
>
|
||||||
<h2 style={{ fontSize: 22, fontWeight: 600, marginBottom: 4 }}>登录</h2>
|
<h2 style={{ fontSize: 28, fontWeight: 600, marginBottom: 8, color: 'var(--dg-text-primary)' }}>
|
||||||
<p style={{ color: 'var(--dg-text-muted)', fontSize: 14, marginBottom: 8 }}>请输入您的账号信息</p>
|
登录
|
||||||
|
</h2>
|
||||||
|
<p style={{ color: 'var(--dg-text-muted)', fontSize: 14, marginBottom: 40 }}>
|
||||||
|
请输入您的账号信息以继续
|
||||||
|
</p>
|
||||||
|
|
||||||
{error && <div style={{ color: '#f87171', fontSize: 13 }}>{error}</div>}
|
{error && (
|
||||||
|
<div style={{ color: '#f87171', fontSize: 13, marginBottom: 16, padding: '10px 14px', background: 'rgba(248,113,113,0.08)', borderRadius: 8 }}>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 28 }}>
|
||||||
|
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, color: 'var(--dg-text-secondary)', marginBottom: 8 }}>
|
||||||
|
用户名
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="用户名"
|
placeholder="请输入用户名"
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
style={{ padding: '10px 14px', borderRadius: 8, background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
padding: '12px 0',
|
||||||
|
background: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
borderBottom: '1px solid var(--dg-border)',
|
||||||
|
borderRadius: 0,
|
||||||
|
color: 'var(--dg-text-primary)',
|
||||||
|
fontSize: 15,
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'border-color 0.2s',
|
||||||
|
}}
|
||||||
|
onFocus={(e) => { e.target.style.borderBottomColor = '#6366f1'; }}
|
||||||
|
onBlur={(e) => { e.target.style.borderBottomColor = 'var(--dg-border)'; }}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 36 }}>
|
||||||
|
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, color: 'var(--dg-text-secondary)', marginBottom: 8 }}>
|
||||||
|
密码
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="密码"
|
placeholder="请输入密码"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
style={{ padding: '10px 14px', borderRadius: 8, background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
padding: '12px 0',
|
||||||
|
background: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
borderBottom: '1px solid var(--dg-border)',
|
||||||
|
borderRadius: 0,
|
||||||
|
color: 'var(--dg-text-primary)',
|
||||||
|
fontSize: 15,
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'border-color 0.2s',
|
||||||
|
}}
|
||||||
|
onFocus={(e) => { e.target.style.borderBottomColor = '#6366f1'; }}
|
||||||
|
onBlur={(e) => { e.target.style.borderBottomColor = 'var(--dg-border)'; }}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading || !username || !password}
|
disabled={loading || !username || !password}
|
||||||
className="dg-gradient-bg"
|
className="dg-gradient-bg w-full"
|
||||||
style={{ padding: '10px 14px', borderRadius: 8, border: 'none', color: '#fff', fontWeight: 600, fontSize: 14, cursor: loading || !username || !password ? 'not-allowed' : 'pointer', opacity: loading || !username || !password ? 0.6 : 1 }}
|
style={{
|
||||||
|
padding: '13px 20px',
|
||||||
|
borderRadius: 10,
|
||||||
|
border: 'none',
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 15,
|
||||||
|
cursor: loading || !username || !password ? 'not-allowed' : 'pointer',
|
||||||
|
opacity: loading || !username || !password ? 0.5 : 1,
|
||||||
|
transition: 'opacity 0.2s, transform 0.1s',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{loading ? '登录中…' : '登录'}
|
{loading ? '登录中…' : '登录'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -5,6 +5,15 @@ import { Check, Save, Loader2 } from 'lucide-react';
|
|||||||
import api from '@/api/request';
|
import api from '@/api/request';
|
||||||
import { toast } from '@/hooks/useToast';
|
import { toast } from '@/hooks/useToast';
|
||||||
|
|
||||||
|
// Login style preview color themes (independent from app theme)
|
||||||
|
const LOGIN_PREVIEW_COLORS: Record<LoginStyle, { bg: string; accent: string; label: string }> = {
|
||||||
|
split: { bg: 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)', accent: '#6366f1', label: 'Linear/Notion' },
|
||||||
|
centered: { bg: 'radial-gradient(ellipse at 50% 40%, #0a0a1a 0%, #1a0a2e 100%)', accent: '#8b5cf6', label: 'Stripe/Vercel' },
|
||||||
|
sidepanel: { bg: 'linear-gradient(180deg, #111827 0%, #1f2937 100%)', accent: '#6366f1', label: 'Slack/Figma' },
|
||||||
|
fullimage: { bg: '#0d1117', accent: '#6366f1', label: 'GitHub/AWS' },
|
||||||
|
minimal: { bg: '#0a0a0a', accent: '#8b5cf6', label: 'Apple/Linear' },
|
||||||
|
};
|
||||||
|
|
||||||
export default function AppearanceSettings() {
|
export default function AppearanceSettings() {
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
const [loginStyle, setLoginStyle] = useState<LoginStyle>('split');
|
const [loginStyle, setLoginStyle] = useState<LoginStyle>('split');
|
||||||
@@ -12,12 +21,10 @@ export default function AppearanceSettings() {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [dirty, setDirty] = useState(false);
|
const [dirty, setDirty] = useState(false);
|
||||||
|
|
||||||
// Load preferences from backend on mount
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.get<{ theme?: string; login_style?: string }>('/user/preferences')
|
api.get<{ theme?: string; login_style?: string }>('/user/preferences')
|
||||||
.then(prefs => {
|
.then(prefs => {
|
||||||
if (prefs?.theme) {
|
if (prefs?.theme) {
|
||||||
// Backward compat: map old values
|
|
||||||
const t = prefs.theme;
|
const t = prefs.theme;
|
||||||
if (t === 'dark' || t === 'light') {
|
if (t === 'dark' || t === 'light') {
|
||||||
setTheme(t === 'dark' ? 'ocean-deep' : 'light-professional');
|
setTheme(t === 'dark' ? 'ocean-deep' : 'light-professional');
|
||||||
@@ -28,7 +35,6 @@ export default function AppearanceSettings() {
|
|||||||
if (prefs?.login_style) setLoginStyle(prefs.login_style as LoginStyle);
|
if (prefs?.login_style) setLoginStyle(prefs.login_style as LoginStyle);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
// Fallback to localStorage
|
|
||||||
const stored = localStorage.getItem('login-style') as LoginStyle | null;
|
const stored = localStorage.getItem('login-style') as LoginStyle | null;
|
||||||
if (stored) setLoginStyle(stored);
|
if (stored) setLoginStyle(stored);
|
||||||
})
|
})
|
||||||
@@ -71,10 +77,10 @@ export default function AppearanceSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-10">
|
||||||
{/* Theme Mode */}
|
{/* ===== Theme Mode Section ===== */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-semibold mb-1">主题模式</h3>
|
<h3 className="text-sm font-semibold mb-1">界面配色</h3>
|
||||||
<p className="text-xs text-[var(--dg-text-muted)] mb-4">选择界面配色方案,即时预览效果</p>
|
<p className="text-xs text-[var(--dg-text-muted)] mb-4">选择界面配色方案,即时预览效果</p>
|
||||||
<div className="grid grid-cols-5 gap-3">
|
<div className="grid grid-cols-5 gap-3">
|
||||||
{THEME_OPTIONS.map(opt => (
|
{THEME_OPTIONS.map(opt => (
|
||||||
@@ -87,7 +93,7 @@ export default function AppearanceSettings() {
|
|||||||
>
|
>
|
||||||
{/* Color preview swatch */}
|
{/* Color preview swatch */}
|
||||||
<div
|
<div
|
||||||
className="w-full h-6 rounded-md mb-3"
|
className="w-full h-8 rounded-lg mb-3"
|
||||||
style={{
|
style={{
|
||||||
background: opt.isDark
|
background: opt.isDark
|
||||||
? `linear-gradient(135deg, ${opt.primaryColor} 0%, color-mix(in srgb, ${opt.primaryColor} 40%, #1a1a2e) 50%, #0d1117 100%)`
|
? `linear-gradient(135deg, ${opt.primaryColor} 0%, color-mix(in srgb, ${opt.primaryColor} 40%, #1a1a2e) 50%, #0d1117 100%)`
|
||||||
@@ -109,100 +115,114 @@ export default function AppearanceSettings() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Login Page Style */}
|
{/* ===== Login Page Style Section ===== */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-semibold mb-1">登录页风格</h3>
|
<h3 className="text-sm font-semibold mb-1">登录页风格</h3>
|
||||||
<p className="text-xs text-[var(--dg-text-muted)] mb-4">选择登录页面的视觉风格,保存后退出登录即可看到效果</p>
|
<p className="text-xs text-[var(--dg-text-muted)] mb-4">选择登录页面的视觉风格,保存后退出登录即可预览</p>
|
||||||
<div className="grid grid-cols-5 gap-3">
|
<div className="grid grid-cols-5 gap-3">
|
||||||
{LOGIN_STYLE_OPTIONS.map(opt => (
|
{LOGIN_STYLE_OPTIONS.map(opt => {
|
||||||
|
const preview = LOGIN_PREVIEW_COLORS[opt.id];
|
||||||
|
return (
|
||||||
<button
|
<button
|
||||||
key={opt.id}
|
key={opt.id}
|
||||||
onClick={() => handleLoginStyleChange(opt.id)}
|
onClick={() => handleLoginStyleChange(opt.id)}
|
||||||
className={`group relative rounded-xl p-4 text-left transition-all ${
|
className={`group relative rounded-xl p-4 text-left transition-all ${
|
||||||
loginStyle === opt.id
|
loginStyle === opt.id
|
||||||
? 'bg-blue-500/10 ring-1 ring-blue-500/30'
|
? 'ring-2 ring-indigo-500/50 bg-[var(--dg-bg-tertiary)]'
|
||||||
: 'bg-[var(--dg-bg-tertiary)] hover:bg-[var(--dg-bg-hover)]'
|
: 'bg-[var(--dg-bg-tertiary)] hover:bg-[var(--dg-bg-hover)]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{/* Thumbnail preview */}
|
{/* Login style miniature preview */}
|
||||||
<div className={`w-full aspect-[4/3] rounded-lg mb-3 overflow-hidden ${
|
<div
|
||||||
opt.id === 'split' ? 'flex' :
|
className="w-full aspect-[4/3] rounded-lg mb-3 overflow-hidden relative"
|
||||||
opt.id === 'centered' ? 'flex items-center justify-center dg-gradient-br' :
|
style={{ background: preview.bg }}
|
||||||
opt.id === 'sidepanel' ? 'flex' :
|
>
|
||||||
opt.id === 'fullimage' ? 'flex items-center justify-center' :
|
{/* Abstract mini layout */}
|
||||||
'flex items-center justify-center bg-[#0a0a0a]'
|
|
||||||
}`}>
|
|
||||||
{opt.id === 'split' && (
|
{opt.id === 'split' && (
|
||||||
<>
|
<>
|
||||||
<div className="flex-1 bg-[var(--dg-bg-primary)] p-1.5">
|
<div className="absolute inset-0 flex">
|
||||||
<div className="w-3 h-1 rounded-sm bg-blue-500/30 mb-1" />
|
<div className="flex-1 p-2 flex flex-col justify-center gap-1">
|
||||||
<div className="grid grid-cols-2 gap-0.5">
|
<div className="w-6 h-1.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.3)' }} />
|
||||||
{[0,1,2,3].map(i => <div key={i} className="h-1.5 rounded-sm bg-[var(--dg-bg-tertiary)]" />)}
|
<div className="w-8 h-1 rounded-sm" style={{ background: 'rgba(255,255,255,0.15)' }} />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="w-2/5 bg-[var(--dg-bg-secondary)] p-2 flex flex-col justify-center gap-1">
|
||||||
|
<div className="h-0.5 rounded-sm bg-[var(--dg-bg-hover)]" />
|
||||||
|
<div className="h-0.5 rounded-sm bg-[var(--dg-bg-hover)]" />
|
||||||
|
<div className="h-1 rounded-sm mt-0.5" style={{ background: preview.accent, opacity: 0.6 }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="w-2/5 bg-[var(--dg-bg-secondary)] p-1.5 flex flex-col gap-0.5 justify-center">
|
|
||||||
<div className="h-1 rounded-sm bg-[var(--dg-bg-tertiary)]" />
|
|
||||||
<div className="h-1 rounded-sm bg-[var(--dg-bg-tertiary)]" />
|
|
||||||
<div className="h-1.5 rounded-sm dg-gradient-bg opacity-60" />
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{opt.id === 'centered' && (
|
{opt.id === 'centered' && (
|
||||||
<div className="w-3/4 bg-white/10 backdrop-blur rounded-md p-1.5 flex flex-col gap-0.5 items-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
<div className="w-4 h-1 rounded-sm bg-white/40" />
|
<div className="w-3/4 rounded-lg p-2 flex flex-col items-center gap-1" style={{ background: 'rgba(255,255,255,0.04)', backdropFilter: 'blur(4px)', border: '1px solid rgba(255,255,255,0.06)' }}>
|
||||||
<div className="w-6 h-1 rounded-sm bg-white/20" />
|
<div className="w-4 h-1 rounded-sm" style={{ background: 'rgba(255,255,255,0.4)' }} />
|
||||||
<div className="w-full h-1 rounded-sm bg-white/15" />
|
<div className="w-6 h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.15)' }} />
|
||||||
<div className="w-full h-1 rounded-sm bg-white/15" />
|
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.08)' }} />
|
||||||
<div className="w-full h-1.5 rounded-sm bg-white/30" />
|
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.08)' }} />
|
||||||
|
<div className="w-full h-1 rounded-sm mt-0.5" style={{ background: preview.accent, opacity: 0.5 }} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{opt.id === 'sidepanel' && (
|
{opt.id === 'sidepanel' && (
|
||||||
<>
|
<div className="absolute inset-0 flex">
|
||||||
<div className="w-2/5 dg-gradient-br p-1.5 flex flex-col justify-center items-center gap-0.5">
|
<div className="w-[38%] p-2 flex flex-col justify-end gap-1" style={{ background: preview.bg }}>
|
||||||
<div className="w-3 h-3 rounded-full bg-white/20" />
|
<div className="flex flex-wrap gap-0.5">
|
||||||
<div className="w-4 h-0.5 rounded-sm bg-white/15" />
|
{['SQL', 'DDL', 'ACL'].map(t => (
|
||||||
|
<span key={t} className="px-1 rounded text-[5px]" style={{ background: 'rgba(255,255,255,0.08)', color: 'rgba(255,255,255,0.5)' }}>{t}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 bg-[var(--dg-bg-primary)] p-2 flex flex-col justify-center gap-1">
|
||||||
|
<div className="h-0.5 rounded-sm bg-[var(--dg-bg-hover)]" />
|
||||||
|
<div className="h-0.5 rounded-sm bg-[var(--dg-bg-hover)]" />
|
||||||
|
<div className="h-1 rounded-sm mt-0.5" style={{ background: preview.accent, opacity: 0.5 }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 bg-[var(--dg-bg-primary)] p-1.5 flex flex-col gap-0.5 justify-center">
|
|
||||||
<div className="h-1 rounded-sm bg-[var(--dg-bg-tertiary)]" />
|
|
||||||
<div className="h-1 rounded-sm bg-[var(--dg-bg-tertiary)]" />
|
|
||||||
<div className="h-1.5 rounded-sm dg-gradient-bg opacity-60" />
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
{opt.id === 'fullimage' && (
|
{opt.id === 'fullimage' && (
|
||||||
<div className="w-full h-full" style={{
|
<div className="absolute inset-0">
|
||||||
background: 'repeating-linear-gradient(0deg, transparent, transparent 8px, rgba(59,130,246,0.15) 8px, rgba(59,130,246,0.15) 9px), repeating-linear-gradient(90deg, transparent, transparent 8px, rgba(59,130,246,0.15) 8px, rgba(59,130,246,0.15) 9px)',
|
{/* Grid dots */}
|
||||||
}}>
|
<div className="absolute inset-0" style={{
|
||||||
<div className="w-3/4 mx-auto mt-3 bg-black/40 backdrop-blur rounded-md p-1.5 flex flex-col gap-0.5 items-center">
|
backgroundImage: 'radial-gradient(circle, rgba(99,102,241,0.15) 0.5px, transparent 0.5px)',
|
||||||
<div className="w-3 h-0.5 rounded-sm bg-white/30" />
|
backgroundSize: '12px 12px',
|
||||||
<div className="w-full h-0.5 rounded-sm bg-white/10" />
|
}} />
|
||||||
<div className="w-full h-0.5 rounded-sm bg-white/10" />
|
{/* Center card */}
|
||||||
<div className="w-full h-1 rounded-sm bg-blue-500/40" />
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="w-3/4 rounded p-1.5 flex flex-col items-center gap-0.5" style={{ background: 'rgba(0,0,0,0.4)', border: '1px solid rgba(255,255,255,0.06)' }}>
|
||||||
|
<div className="w-3 h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.25)' }} />
|
||||||
|
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.06)' }} />
|
||||||
|
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.06)' }} />
|
||||||
|
<div className="w-full h-0.5 rounded-sm mt-0.5" style={{ background: preview.accent, opacity: 0.4 }} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{opt.id === 'minimal' && (
|
{opt.id === 'minimal' && (
|
||||||
<div className="w-full h-full flex flex-col items-center justify-center gap-1 p-2">
|
<div className="absolute inset-0 flex flex-col items-center justify-center gap-1.5 p-2">
|
||||||
<div className="w-4 h-1 rounded-sm bg-white/20" />
|
<div className="w-2 h-2 rounded-full" style={{ background: `linear-gradient(135deg, ${preview.accent}, #a855f7)` }} />
|
||||||
<div className="w-6 h-0.5 rounded-sm bg-white/10" />
|
<div className="w-8 h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.15)' }} />
|
||||||
<div className="w-full h-1 rounded-sm bg-white/8" />
|
<div className="w-6 h-px my-1" style={{ background: 'rgba(255,255,255,0.1)' }} />
|
||||||
<div className="w-full h-1 rounded-sm bg-white/8" />
|
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.06)' }} />
|
||||||
<div className="w-full h-1.5 rounded-sm bg-white/15" />
|
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.06)' }} />
|
||||||
|
<div className="w-full h-0.5 rounded-sm mt-0.5" style={{ background: preview.accent, opacity: 0.4 }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Active check mark */}
|
{/* Active check mark */}
|
||||||
{loginStyle === opt.id && (
|
{loginStyle === opt.id && (
|
||||||
<div className="absolute top-2 right-2 w-5 h-5 rounded-full bg-blue-500 flex items-center justify-center">
|
<div className="absolute top-2 right-2 w-5 h-5 rounded-full bg-indigo-500 flex items-center justify-center">
|
||||||
<Check className="w-3 h-3 text-white" />
|
<Check className="w-3 h-3 text-white" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<h4 className="text-sm font-medium mb-0.5">{opt.name}</h4>
|
<h4 className="text-sm font-medium mb-0.5">{opt.name}</h4>
|
||||||
<p className="text-xs text-[var(--dg-text-muted)]">{opt.desc}</p>
|
<p className="text-xs text-[var(--dg-text-muted)] leading-snug">{opt.desc}</p>
|
||||||
|
<p className="text-[10px] text-[var(--dg-text-muted)] mt-1 opacity-60">{preview.label}</p>
|
||||||
</button>
|
</button>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user