diff --git a/frontend/src/components/login/GradientMeshView.tsx b/frontend/src/components/login/GradientMeshView.tsx index 63a29f3..087da06 100644 --- a/frontend/src/components/login/GradientMeshView.tsx +++ b/frontend/src/components/login/GradientMeshView.tsx @@ -1,307 +1,214 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { useLogin } from '@/hooks/useLogin'; -/* ------------------------------------------------------------------ */ -/* KEYFRAMES */ -/* ------------------------------------------------------------------ */ const KEYFRAMES = ` -@keyframes mesh-drift-1 { - 0%, 100% { transform: translate(0, 0); } - 50% { transform: translate(30px, 20px); } +@keyframes orb-1 { + 0%, 100% { transform: translate(0, 0) scale(1); } + 33% { transform: translate(30px, -40px) scale(1.05); } + 66% { transform: translate(-20px, 20px) scale(0.95); } } -@keyframes mesh-drift-2 { - 0%, 100% { transform: translate(0, 0); } - 50% { transform: translate(-25px, -30px); } +@keyframes orb-2 { + 0%, 100% { transform: translate(0, 0) scale(1); } + 33% { transform: translate(-35px, 25px) scale(0.97); } + 66% { transform: translate(25px, -30px) scale(1.03); } } -@keyframes mesh-drift-3 { - 0%, 100% { transform: translate(0, 0); } - 50% { transform: translate(20px, -25px); } +@keyframes orb-3 { + 0%, 100% { transform: translate(0, 0) scale(1); } + 50% { transform: translate(20px, 35px) scale(1.04); } } -@keyframes mesh-drift-4 { - 0%, 100% { transform: translate(0, 0); } - 50% { transform: translate(-30px, 20px); } +@keyframes spin-slow { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } } -@keyframes mesh-drift-5 { - 0%, 100% { transform: translate(0, 0); } - 50% { transform: translate(25px, 30px); } +@keyframes pulse-ring { + 0% { transform: scale(1); opacity: 0.3; } + 50% { transform: scale(1.08); opacity: 0.15; } + 100% { transform: scale(1); opacity: 0.3; } } -@keyframes mesh-drift-6 { - 0%, 100% { transform: translate(0, 0); } - 50% { transform: translate(-15px, 25px); } -} -@keyframes mesh-drift-7 { - 0%, 100% { transform: translate(0, 0); } - 50% { transform: translate(20px, -15px); } +@keyframes float-gentle { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-8px); } } `; -/* ------------------------------------------------------------------ */ -/* COMPONENT */ -/* ------------------------------------------------------------------ */ export default function GradientMeshView(): React.JSX.Element { const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin(); - const [focusedField, setFocusedField] = useState(null); - const [hoverBtn, setHoverBtn] = useState(false); - + const [focus, setFocus] = useState(null); + const [time, setTime] = useState(0); const isReady = username.trim().length > 0 && password.trim().length > 0; - /* ---- marble / terrazzo blobs (light tones) ---- */ - const blobs: React.CSSProperties[] = [ - { - position: 'fixed', top: '-120px', left: '-80px', - width: '650px', height: '650px', - background: 'radial-gradient(circle, rgba(99,102,241,0.06), transparent 70%)', - filter: 'blur(90px)', pointerEvents: 'none', - animation: 'mesh-drift-1 14s ease-in-out infinite', - }, - { - position: 'fixed', bottom: '-100px', right: '-60px', - width: '550px', height: '550px', - background: 'radial-gradient(circle, rgba(244,63,94,0.04), transparent 70%)', - filter: 'blur(100px)', pointerEvents: 'none', - animation: 'mesh-drift-2 12s ease-in-out infinite', - }, - { - position: 'fixed', top: '35%', right: '8%', - width: '450px', height: '450px', - background: 'radial-gradient(circle, rgba(16,185,129,0.05), transparent 70%)', - filter: 'blur(110px)', pointerEvents: 'none', - animation: 'mesh-drift-3 16s ease-in-out infinite', - }, - { - position: 'fixed', bottom: '10%', left: '8%', - width: '500px', height: '500px', - background: 'radial-gradient(circle, rgba(139,92,246,0.04), transparent 70%)', - filter: 'blur(95px)', pointerEvents: 'none', - animation: 'mesh-drift-4 13s ease-in-out infinite', - }, - { - position: 'fixed', top: '-60px', right: '25%', - width: '580px', height: '580px', - background: 'radial-gradient(circle, rgba(99,102,241,0.04), transparent 70%)', - filter: 'blur(100px)', pointerEvents: 'none', - animation: 'mesh-drift-5 15s ease-in-out infinite', - }, - { - position: 'fixed', top: '60%', left: '30%', - width: '400px', height: '400px', - background: 'radial-gradient(circle, rgba(244,63,94,0.03), transparent 70%)', - filter: 'blur(105px)', pointerEvents: 'none', - animation: 'mesh-drift-6 11s ease-in-out infinite', - }, - { - position: 'fixed', top: '15%', left: '50%', - width: '480px', height: '480px', - background: 'radial-gradient(circle, rgba(16,185,129,0.04), transparent 70%)', - filter: 'blur(95px)', pointerEvents: 'none', - animation: 'mesh-drift-7 17s ease-in-out infinite', - }, - ]; + useEffect(() => { + const id = setInterval(() => setTime(t => t + 1), 50); + return () => clearInterval(id); + }, []); - /* ---- input base style ---- */ - const inputBase: React.CSSProperties = { - width: '100%', - background: '#fff', - border: '1px solid #e5e7eb', - borderRadius: 12, - padding: '14px 18px', - color: '#111827', - fontSize: 15, - outline: 'none', - transition: 'all 0.25s ease', - boxSizing: 'border-box', - }; - - const getInputStyle = (field: string): React.CSSProperties => ({ - ...inputBase, - borderColor: focusedField === field ? '#8b5cf6' : '#e5e7eb', - boxShadow: focusedField === field - ? '0 0 0 3px rgba(99,102,241,0.1)' - : 'none', - }); + // Animated hue shift for orbs + const hue1 = 220 + Math.sin(time * 0.02) * 15; + const hue2 = 260 + Math.cos(time * 0.015) * 20; + const hue3 = 190 + Math.sin(time * 0.025) * 10; return ( <> +
- {/* ===================== ROOT CONTAINER ===================== */} -
+ {/* Orb 1 - top left */} +
+ {/* Orb 2 - bottom right */} +
+ {/* Orb 3 - center right */} +
- {/* ---- marble / terrazzo texture blobs ---- */} - {blobs.map((b, i) => ( -
- ))} + {/* Geometric decoration - rotating ring */} +
+ + + + +
- {/* ---- subtle dot-grid texture (light version) ---- */} -
+ {/* Subtle grid dots */} +
- {/* ===================== LOGIN CARD ===================== */} -
+ {/* Decorative line accents */} +
+
- {/* ---- brand ---- */} -
- {/* Logo */} -
- + {/* Login card */} +
+ + + {/* Pulse ring behind logo */} +
+ + {/* Brand */} +
+
+
- -

+

OpsxSQL

-

+

企业级数据库审核与合规管理

- {/* ---- form ---- */} -
- {/* username */} - setUsername(e.target.value)} - onFocus={() => setFocusedField('username')} - onBlur={() => setFocusedField(null)} - style={getInputStyle('username')} - autoComplete="username" - /> - - {/* password */} - setPassword(e.target.value)} - onFocus={() => setFocusedField('password')} - onBlur={() => setFocusedField(null)} - style={getInputStyle('password')} - autoComplete="current-password" - /> - - {/* error */} - {error && ( -
+ {/* Username */} +
+
+ +
+ setUsername(e.target.value)} + onFocus={() => setFocus('username')} onBlur={() => setFocus(null)} + autoComplete="username" style={{ - background: 'rgba(239,68,68,0.06)', - color: '#ef4444', - borderRadius: 10, - padding: '10px 14px', - fontSize: 13, + width: '100%', height: 50, padding: '0 16px 0 44px', fontSize: 14, color: '#111827', + background: focus === 'username' ? '#fff' : '#f4f4f5', + border: `1.5px solid ${focus === 'username' ? '#818cf8' : 'transparent'}`, + borderRadius: 14, outline: 'none', boxSizing: 'border-box', transition: 'all 0.2s ease', + boxShadow: focus === 'username' ? '0 0 0 4px rgba(99,102,241,0.06)' : 'none', }} - > + /> +
+ + {/* Password */} +
+
+ +
+ setPassword(e.target.value)} + onFocus={() => setFocus('password')} onBlur={() => setFocus(null)} + autoComplete="current-password" + style={{ + width: '100%', height: 50, padding: '0 16px 0 44px', fontSize: 14, color: '#111827', + background: focus === 'password' ? '#fff' : '#f4f4f5', + border: `1.5px solid ${focus === 'password' ? '#818cf8' : 'transparent'}`, + borderRadius: 14, outline: 'none', boxSizing: 'border-box', transition: 'all 0.2s ease', + boxShadow: focus === 'password' ? '0 0 0 4px rgba(99,102,241,0.06)' : 'none', + }} + /> +
+ + {/* Error */} + {error && ( +
+ {error}
)} - {/* submit */} + {/* Submit */} - {/* ---- copyright ---- */} -

- © 2025 OpsxSQL -

+ {/* Footer */} +
+
+ OpsxSQL · 安全合规的数据库管理 +
diff --git a/frontend/src/components/login/NeonCityView.tsx b/frontend/src/components/login/NeonCityView.tsx index e7decaf..5d80884 100644 --- a/frontend/src/components/login/NeonCityView.tsx +++ b/frontend/src/components/login/NeonCityView.tsx @@ -1,301 +1,153 @@ -import React from 'react'; +import React, { useState } from 'react'; import { useLogin } from '@/hooks/useLogin'; const KEYFRAMES = ` -@keyframes float-slow { - 0%, 100% { transform: translate(0, 0) scale(1); } - 25% { transform: translate(20px, -30px) scale(1.03); } - 50% { transform: translate(-10px, -50px) scale(1.01); } - 75% { transform: translate(15px, -20px) scale(1.04); } +@keyframes morph-1 { + 0%, 100% { border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; transform: translate(0, 0) rotate(0deg); } + 25% { border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%; transform: translate(15px, -25px) rotate(5deg); } + 50% { border-radius: 50% 60% 30% 60% / 40% 50% 60% 50%; transform: translate(-10px, 15px) rotate(-3deg); } + 75% { border-radius: 40% 30% 60% 50% / 60% 40% 50% 70%; transform: translate(20px, -10px) rotate(7deg); } } - -@keyframes float-medium { - 0%, 100% { transform: translate(0, 0) scale(1); } - 25% { transform: translate(-25px, -40px) scale(1.02); } - 50% { transform: translate(20px, -60px) scale(0.98); } - 75% { transform: translate(-15px, -25px) scale(1.03); } +@keyframes morph-2 { + 0%, 100% { border-radius: 40% 60% 70% 30% / 50% 40% 60% 50%; transform: translate(0, 0) rotate(0deg); } + 33% { border-radius: 70% 30% 50% 60% / 30% 60% 40% 70%; transform: translate(-20px, 20px) rotate(-8deg); } + 66% { border-radius: 50% 40% 60% 50% / 60% 70% 30% 40%; transform: translate(25px, -15px) rotate(4deg); } } - -@keyframes float-fast { - 0%, 100% { transform: translate(0, 0) scale(1); } - 25% { transform: translate(15px, -20px) scale(1.01); } - 50% { transform: translate(-20px, -35px) scale(1.02); } - 75% { transform: translate(10px, -15px) scale(0.99); } +@keyframes morph-3 { + 0%, 100% { border-radius: 50% 50% 40% 60% / 40% 60% 50% 50%; transform: translate(0, 0) rotate(0deg); } + 50% { border-radius: 30% 70% 50% 50% / 70% 30% 60% 40%; transform: translate(-15px, -20px) rotate(6deg); } +} +@keyframes line-flow { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(200%); } +} +@keyframes float-card { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-6px); } +} +@keyframes shimmer { + 0% { background-position: -200% center; } + 100% { background-position: 200% center; } } `; -// Floating shapes: [width, height, top, left, color, opacity, animation, duration] -const FLOATING_SHAPES: [number, number, string, string, string, number, string, string][] = [ - [340, 280, '8%', '5%', 'linear-gradient(135deg, #e0f2fe 0%, #f3e8ff 100%)', 0.5, 'float-slow', '20s'], - [240, 200, '55%', '2%', 'linear-gradient(135deg, #f3e8ff 0%, #ede9fe 100%)', 0.4, 'float-medium', '25s'], - [180, 160, '70%', '15%', 'linear-gradient(135deg, #dbeafe 0%, #e0f2fe 100%)', 0.35, 'float-fast', '18s'], - [280, 240, '20%', '18%', 'linear-gradient(135deg, #ede9fe 0%, #e0f2fe 100%)', 0.3, 'float-slow', '22s'], -]; - export default function NeonCityView(): JSX.Element { const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin(); - + const [focus, setFocus] = useState(null); const isDisabled = !username || !password || loading; return ( <> -
- {/* Floating soft geometric shapes */} - {FLOATING_SHAPES.map(([w, h, top, left, bg, opacity, anim, dur], i) => ( -
+
+ + {/* Morphing blobs */} +
+
+
+ + {/* Fine grid */} +
+ + {/* Decorative lines */} + {[15, 35, 58, 78].map((t, i) => ( +
+
+
))} {/* Login card */} -
- {/* Brand area */} -
- {/* Logo */} -
- -
+
- {/* Title */} -

+ {/* Brand */} +
+
+ +
+

OpsxSQL

- - {/* Subtitle */} -

+

企业级数据库审核与合规管理

- {/* Login form */}
- {/* Username input */} -
- - setUsername(e.target.value)} - placeholder="请输入用户名" - autoComplete="username" - style={{ - width: '100%', - height: 46, - padding: '0 16px', - fontSize: 14, - color: '#1a1a2e', - background: '#f5f5f5', - border: '1.5px solid transparent', - borderRadius: 10, - outline: 'none', - boxSizing: 'border-box', - transition: 'border-color 0.25s ease, background 0.25s ease', - }} - onFocus={(e) => { - e.currentTarget.style.borderColor = '#93c5fd'; - e.currentTarget.style.background = '#fff'; - }} - onBlur={(e) => { - e.currentTarget.style.borderColor = 'transparent'; - e.currentTarget.style.background = '#f5f5f5'; - }} - /> + {/* Username */} +
+
+
+ +
+ setUsername(e.target.value)} placeholder="请输入用户名" autoComplete="username" + onFocus={() => setFocus('username')} onBlur={() => setFocus(null)} + style={{ width: '100%', height: 50, padding: '0 16px 0 46px', fontSize: 14, color: '#0f172a', background: focus === 'username' ? '#fff' : '#f1f5f9', border: `1.5px solid ${focus === 'username' ? '#818cf8' : 'transparent'}`, borderRadius: 14, outline: 'none', boxSizing: 'border-box', transition: 'all 0.2s ease', boxShadow: focus === 'username' ? '0 0 0 4px rgba(99,102,241,0.08)' : 'none' }} + /> +
- {/* Password input */} + {/* Password */}
- - setPassword(e.target.value)} - placeholder="请输入密码" - autoComplete="current-password" - style={{ - width: '100%', - height: 46, - padding: '0 16px', - fontSize: 14, - color: '#1a1a2e', - background: '#f5f5f5', - border: '1.5px solid transparent', - borderRadius: 10, - outline: 'none', - boxSizing: 'border-box', - transition: 'border-color 0.25s ease, background 0.25s ease', - }} - onFocus={(e) => { - e.currentTarget.style.borderColor = '#93c5fd'; - e.currentTarget.style.background = '#fff'; - }} - onBlur={(e) => { - e.currentTarget.style.borderColor = 'transparent'; - e.currentTarget.style.background = '#f5f5f5'; - }} - /> +
+
+ +
+ setPassword(e.target.value)} placeholder="请输入密码" autoComplete="current-password" + onFocus={() => setFocus('password')} onBlur={() => setFocus(null)} + style={{ width: '100%', height: 50, padding: '0 16px 0 46px', fontSize: 14, color: '#0f172a', background: focus === 'password' ? '#fff' : '#f1f5f9', border: `1.5px solid ${focus === 'password' ? '#818cf8' : 'transparent'}`, borderRadius: 14, outline: 'none', boxSizing: 'border-box', transition: 'all 0.2s ease', boxShadow: focus === 'password' ? '0 0 0 4px rgba(99,102,241,0.08)' : 'none' }} + /> +
- {/* Error message */} + {/* Error */} {error && ( -
+
+ {error}
)} - {/* Login button */} + {/* Submit */} - {/* Copyright */} -

- © OpsxSQL · 安全合规的数据库管理 -

+ {/* Footer */} +
+
+ OpsxSQL · 安全合规的数据库管理 +
diff --git a/frontend/src/pages/AnnouncementPage.tsx b/frontend/src/pages/AnnouncementPage.tsx index 14c8b30..182eeee 100644 --- a/frontend/src/pages/AnnouncementPage.tsx +++ b/frontend/src/pages/AnnouncementPage.tsx @@ -346,7 +346,7 @@ export default function AnnouncementPage() { title={deleteConfirm?.msg || '确认操作'} variant="danger" confirmText="确定" - onConfirm={() => { deleteConfirm?.onConfirm(); }} + onConfirm={() => { deleteConfirm?.onConfirm(); setDeleteConfirm(null); }} onCancel={() => setDeleteConfirm(null)} />
diff --git a/frontend/src/pages/InstanceDetailPage.tsx b/frontend/src/pages/InstanceDetailPage.tsx index cf47092..ea0ae9c 100644 --- a/frontend/src/pages/InstanceDetailPage.tsx +++ b/frontend/src/pages/InstanceDetailPage.tsx @@ -268,7 +268,7 @@ export default function InstanceDetailPage() { title={deleteConfirm?.msg || '确认操作'} variant="danger" confirmText="确定" - onConfirm={() => { deleteConfirm?.onConfirm(); }} + onConfirm={() => { deleteConfirm?.onConfirm(); setDeleteConfirm(null); }} onCancel={() => setDeleteConfirm(null)} />

diff --git a/frontend/src/pages/InstanceGroupPage.tsx b/frontend/src/pages/InstanceGroupPage.tsx index 373155d..994d6a8 100644 --- a/frontend/src/pages/InstanceGroupPage.tsx +++ b/frontend/src/pages/InstanceGroupPage.tsx @@ -441,7 +441,7 @@ export default function InstanceGroupPage() { title={deleteConfirm?.msg || '确认操作'} variant="danger" confirmText="确定" - onConfirm={() => { deleteConfirm?.onConfirm(); }} + onConfirm={() => { deleteConfirm?.onConfirm(); setDeleteConfirm(null); }} onCancel={() => setDeleteConfirm(null)} /> diff --git a/frontend/src/pages/InstancePage.tsx b/frontend/src/pages/InstancePage.tsx index ee92abf..1f0fed1 100644 --- a/frontend/src/pages/InstancePage.tsx +++ b/frontend/src/pages/InstancePage.tsx @@ -1453,7 +1453,7 @@ function CreateInstanceForm({ defaultType, onCancel, onCreated }: { title={deleteConfirm?.msg || '确认操作'} variant="danger" confirmText="确定" - onConfirm={() => { deleteConfirm?.onConfirm(); }} + onConfirm={() => { deleteConfirm?.onConfirm(); setDeleteConfirm(null); }} onCancel={() => setDeleteConfirm(null)} />
diff --git a/frontend/src/pages/NotificationPage.tsx b/frontend/src/pages/NotificationPage.tsx index a14b606..a2988a7 100644 --- a/frontend/src/pages/NotificationPage.tsx +++ b/frontend/src/pages/NotificationPage.tsx @@ -453,7 +453,7 @@ export default function NotificationPage() { title={deleteConfirm?.msg || '确认操作'} variant="danger" confirmText="确定" - onConfirm={() => { deleteConfirm?.onConfirm(); }} + onConfirm={() => { deleteConfirm?.onConfirm(); setDeleteConfirm(null); }} onCancel={() => setDeleteConfirm(null)} />
diff --git a/frontend/src/pages/NotificationsPage.tsx b/frontend/src/pages/NotificationsPage.tsx index 4e787e8..7b95ec8 100644 --- a/frontend/src/pages/NotificationsPage.tsx +++ b/frontend/src/pages/NotificationsPage.tsx @@ -179,7 +179,7 @@ export default function NotificationsPage() { title={deleteConfirm?.msg || '确认操作'} variant="danger" confirmText="确定" - onConfirm={() => { deleteConfirm?.onConfirm(); }} + onConfirm={() => { deleteConfirm?.onConfirm(); setDeleteConfirm(null); }} onCancel={() => setDeleteConfirm(null)} />
diff --git a/frontend/src/pages/RedisDetailPage.tsx b/frontend/src/pages/RedisDetailPage.tsx index e500da0..b505391 100644 --- a/frontend/src/pages/RedisDetailPage.tsx +++ b/frontend/src/pages/RedisDetailPage.tsx @@ -313,7 +313,7 @@ export default function RedisDetailPage() { title={deleteConfirm?.msg || '确认操作'} variant="danger" confirmText="确定" - onConfirm={() => { deleteConfirm?.onConfirm(); }} + onConfirm={() => { deleteConfirm?.onConfirm(); setDeleteConfirm(null); }} onCancel={() => setDeleteConfirm(null)} />
diff --git a/frontend/src/pages/UsersPage.tsx b/frontend/src/pages/UsersPage.tsx index c3ef98f..4d1b068 100644 --- a/frontend/src/pages/UsersPage.tsx +++ b/frontend/src/pages/UsersPage.tsx @@ -530,7 +530,7 @@ export default function UsersPage() { title={deleteConfirm?.msg || '确认操作'} variant="danger" confirmText="确定" - onConfirm={() => { deleteConfirm?.onConfirm(); }} + onConfirm={() => { deleteConfirm?.onConfirm(); setDeleteConfirm(null); }} onCancel={() => setDeleteConfirm(null)} />
diff --git a/frontend/src/pages/WorkflowPage.tsx b/frontend/src/pages/WorkflowPage.tsx index 23ba242..586703c 100644 --- a/frontend/src/pages/WorkflowPage.tsx +++ b/frontend/src/pages/WorkflowPage.tsx @@ -816,7 +816,7 @@ export default function WorkflowPage() { title={deleteConfirm?.msg || '确认操作'} variant="danger" confirmText="确定" - onConfirm={() => { deleteConfirm?.onConfirm(); }} + onConfirm={() => { deleteConfirm?.onConfirm(); setDeleteConfirm(null); }} onCancel={() => setDeleteConfirm(null)} />