From 46054c8b2fe36590540827be4968d31483c4dd5e Mon Sep 17 00:00:00 2001 From: logan Date: Wed, 27 May 2026 03:25:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=99=BB=E5=BD=95=E9=A1=B55=E7=A7=8D?= =?UTF-8?q?=E4=B8=BB=E6=B5=81=E9=AB=98=E7=BA=A7=E9=A3=8E=E6=A0=BC=E9=87=8D?= =?UTF-8?q?=E5=86=99=20+=20=E4=B8=BB=E9=A2=98/=E7=99=BB=E5=BD=95=E9=A1=B5?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E7=8B=AC=E7=AB=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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等) - 移除登录页主题切换按钮,风格与主题彻底解耦 --- .../src/components/login/CenteredCard.tsx | 170 +++++++++-- frontend/src/components/login/FullImage.tsx | 200 ++++++++++--- frontend/src/components/login/MinimalDark.tsx | 135 ++++++--- frontend/src/components/login/SidePanel.tsx | 206 ++++++++++--- frontend/src/components/login/SplitView.tsx | 280 ++++++++++++++---- frontend/src/pages/AppearanceSettings.tsx | 202 +++++++------ 6 files changed, 901 insertions(+), 292 deletions(-) diff --git a/frontend/src/components/login/CenteredCard.tsx b/frontend/src/components/login/CenteredCard.tsx index b2b33b4..60a8827 100644 --- a/frontend/src/components/login/CenteredCard.tsx +++ b/frontend/src/components/login/CenteredCard.tsx @@ -1,57 +1,175 @@ import { useLogin } from '@/hooks/useLogin'; -import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme'; -import { Sun, Moon } from 'lucide-react'; export default function CenteredCard() { 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 ( -
- {/* Gradient background */} -
+
+ {/* Full-screen dark gradient background with noise texture */} +
+ {/* Subtle noise / dot pattern overlay */} +
+ {/* Extra floating glow orbs */} +
+
- {/* Theme toggle */} - - - {/* Card */} -
-
-

- SQL审核平台 -

-

登录您的账号

+ {/* Logo icon */} +
+ + + + +
+

+ SQL审核平台 +

+

+ 登录您的工作区 +

+
- {error &&
{error}
} + {error && ( +
+ {error} +
+ )} 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)'; }} /> 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)'; }} /> diff --git a/frontend/src/components/login/FullImage.tsx b/frontend/src/components/login/FullImage.tsx index d67a04a..abf0df2 100644 --- a/frontend/src/components/login/FullImage.tsx +++ b/frontend/src/components/login/FullImage.tsx @@ -1,74 +1,204 @@ import { useLogin } from '@/hooks/useLogin'; -import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme'; -import { Sun, Moon } from 'lucide-react'; export default function FullImage() { 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 ( -
- {/* Circuit board pattern background */} +
+ {/* Deep dark background #0d1117 */}
- {/* Theme toggle */} - - - {/* Glass card */} + {/* Particle grid — dots and lines */}
+ + {/* Connecting grid lines */} +
+ + {/* Large glow orb — top right */} +
+ + {/* Secondary glow — bottom left */} +
+ + {/* Center card */} +
+ {/* Small brand mark at top */}
-

- SQL审核平台 +
+ + + + + +
+

+ SQL审核平台

-

登录您的工作区

+

+ 登录您的工作区 +

- - {error &&
{error}
} + + {error && ( +
+ {error} +
+ )} + {/* Dark inset inputs */} 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)'; }} /> 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)'; }} /> diff --git a/frontend/src/components/login/MinimalDark.tsx b/frontend/src/components/login/MinimalDark.tsx index 1c12d75..2ba7cf1 100644 --- a/frontend/src/components/login/MinimalDark.tsx +++ b/frontend/src/components/login/MinimalDark.tsx @@ -1,51 +1,118 @@ import { useLogin } from '@/hooks/useLogin'; -import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme'; -import { Sun, Moon, Database } from 'lucide-react'; export default function MinimalDark() { 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 ( -
- - +
- -

- SQL审核平台 + {/* Tiny gradient dot logo */} +
+ + {/* Brand name with wide tracking */} +

+ SQL审核平台

-

安全的数据库审核与合规管理

- - {error &&
{error}
} + {/* Thin divider */} +
- 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' }} - /> - 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' }} - /> + + {error && ( +
+ {error} +
+ )} + {/* Borderless inputs — bottom line only */} +
+ setUsername(e.target.value)} + 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)'; }} + /> +
+ +
+ setPassword(e.target.value)} + 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)'; }} + /> +
+ + {/* Full-width slim button */} diff --git a/frontend/src/components/login/SidePanel.tsx b/frontend/src/components/login/SidePanel.tsx index acfdc44..69fca83 100644 --- a/frontend/src/components/login/SidePanel.tsx +++ b/frontend/src/components/login/SidePanel.tsx @@ -1,70 +1,180 @@ 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() { 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 ( -
- - - {/* Left Panel — 40% */} +
+ {/* Left Panel — 38% Brand Area */}
- {/* Decorative circles */} -
-
-
-
-
+ {/* Subtle decorative gradient orb */} +
-
- -

SQL审核平台

-

- 为团队提供智能 SQL 审核、实时合规监控与数据库安全保障。 + {/* Top brand section */} +

+ {/* Logo mark */} +
+ + + + + +
+ + {/* Stacked large text — left aligned */} +
+
+ SQL审核 +
+
+ 平台 +
+
+

+ 企业级数据库审核与合规管理解决方案,为团队提供安全可靠的数据管理体验。

+ + {/* Bottom feature pills */} +
+ {features.map((tag) => ( + + {tag} + + ))} +
- {/* Right Panel — Login Form */} -
- -

欢迎回来

-

登录以访问控制台

+ {/* Right Panel — 62% Login Form */} +
+ +

+ 欢迎回来 +

+

+ 登录以继续 +

- {error &&
{error}
} + {error && ( +
+ {error} +
+ )} - 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' }} - /> - 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' }} - /> +
+ + setUsername(e.target.value)} + 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)'; }} + /> +
+ +
+ + setPassword(e.target.value)} + 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)'; }} + /> +
diff --git a/frontend/src/components/login/SplitView.tsx b/frontend/src/components/login/SplitView.tsx index fcc0668..c476a8d 100644 --- a/frontend/src/components/login/SplitView.tsx +++ b/frontend/src/components/login/SplitView.tsx @@ -1,82 +1,246 @@ 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() { 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 ( -
- + {/* Animated geometric decorations */} + {/* Large circle — top right */} +
+ {/* Medium circle — bottom left */} +
+ {/* Small circle — center upper */} +
+ {/* Tiny dot */} +
+ {/* Decorative lines */} +
+
+ {/* Another small ring */} +
- {/* Left Panel */} -
-
-

- SQL审核平台 -

-

- 企业级 SQL 审核与合规管理平台。 -

-
- {features.map((f) => ( -
- -
{f.label}
-
{f.desc}
-
- ))} + {/* Brand content */} +
+ {/* Logo mark */} +
+ + + + +
+ +

+ SQL审核平台 +

+

+ 安全 · 高效 · 可信赖的企业级数据库管理 +

+ + {/* Keyframe animations */} +
- {/* Right Panel — Login Form */} -
+ {/* Right Panel — 45% Login Form */} +
-

登录

-

请输入您的账号信息

+

+ 登录 +

+

+ 请输入您的账号信息以继续 +

- {error &&
{error}
} + {error && ( +
+ {error} +
+ )} - 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' }} - /> - 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' }} - /> +
+ + setUsername(e.target.value)} + 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)'; }} + /> +
+ +
+ + setPassword(e.target.value)} + 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)'; }} + /> +
diff --git a/frontend/src/pages/AppearanceSettings.tsx b/frontend/src/pages/AppearanceSettings.tsx index 38d75da..37596ae 100644 --- a/frontend/src/pages/AppearanceSettings.tsx +++ b/frontend/src/pages/AppearanceSettings.tsx @@ -5,6 +5,15 @@ import { Check, Save, Loader2 } from 'lucide-react'; import api from '@/api/request'; import { toast } from '@/hooks/useToast'; +// Login style preview color themes (independent from app theme) +const LOGIN_PREVIEW_COLORS: Record = { + 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() { const { theme, setTheme } = useTheme(); const [loginStyle, setLoginStyle] = useState('split'); @@ -12,12 +21,10 @@ export default function AppearanceSettings() { const [loading, setLoading] = useState(true); const [dirty, setDirty] = useState(false); - // Load preferences from backend on mount useEffect(() => { api.get<{ theme?: string; login_style?: string }>('/user/preferences') .then(prefs => { if (prefs?.theme) { - // Backward compat: map old values const t = prefs.theme; if (t === 'dark' || t === 'light') { 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); }) .catch(() => { - // Fallback to localStorage const stored = localStorage.getItem('login-style') as LoginStyle | null; if (stored) setLoginStyle(stored); }) @@ -71,10 +77,10 @@ export default function AppearanceSettings() { } return ( -
- {/* Theme Mode */} +
+ {/* ===== Theme Mode Section ===== */}
-

主题模式

+

界面配色

选择界面配色方案,即时预览效果

{THEME_OPTIONS.map(opt => ( @@ -87,7 +93,7 @@ export default function AppearanceSettings() { > {/* Color preview swatch */}
- {/* Login Page Style */} + {/* ===== Login Page Style Section ===== */}

登录页风格

-

选择登录页面的视觉风格,保存后退出登录即可看到效果

+

选择登录页面的视觉风格,保存后退出登录即可预览

- {LOGIN_STYLE_OPTIONS.map(opt => ( - - ))} + {/* Active check mark */} + {loginStyle === opt.id && ( +
+ +
+ )} + +

{opt.name}

+

{opt.desc}

+

{preview.label}

+ + ); + })}