feat: 登录页扩展至10种风格 — 新增极光之境/星际漫游/水墨山水/霓虹都市/渐变网格
- 新增 AuroraView.tsx: 4条极光光带+15星光+毛玻璃卡片 - 新增 CosmosView.tsx: 3星云+45颗星星+2流星+行星装饰 - 新增 InkWashView.tsx: 三层远山+水墨晕染+金色点缀+亮色表单 - 新增 NeonCityView.tsx: 城市剪影+霓虹灯管+雨滴+赛博朋克风格 - 新增 GradientMeshView.tsx: 5渐变光晕+点阵纹理+呼吸动画 - LoginPage.tsx: LoginStyle类型扩展10种+映射+选项 - AppearanceSettings.tsx: 预览颜色+缩略图10种
This commit is contained in:
1 parent
872c204e80
commit
bf5c077b5b
7 files changed
+2433
-2
No files matched your search
@@ -0,0 +1,505 @@
|
||||
import React from 'react';
|
||||
import { useLogin } from '@/hooks/useLogin';
|
||||
|
||||
const KEYFRAMES = `
|
||||
@keyframes neon-pulse {
|
||||
0%, 100% { opacity: 0.6; box-shadow: 0 0 10px currentColor, 0 0 20px currentColor, 0 0 40px currentColor; }
|
||||
50% { opacity: 1; box-shadow: 0 0 20px currentColor, 0 0 40px currentColor, 0 0 80px currentColor; }
|
||||
}
|
||||
|
||||
@keyframes neon-flicker {
|
||||
0%, 100% { opacity: 1; }
|
||||
3% { opacity: 0.4; }
|
||||
6% { opacity: 1; }
|
||||
7% { opacity: 0.2; }
|
||||
8% { opacity: 1; }
|
||||
92% { opacity: 1; }
|
||||
93% { opacity: 0.3; }
|
||||
94% { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes rain-fall {
|
||||
0% { transform: translateY(-100vh) translateX(0); opacity: 0; }
|
||||
10% { opacity: 0.6; }
|
||||
90% { opacity: 0.6; }
|
||||
100% { transform: translateY(100vh) translateX(20px); opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes grid-move {
|
||||
0% { transform: perspective(500px) rotateX(60deg) translateY(0); }
|
||||
100% { transform: perspective(500px) rotateX(60deg) translateY(40px); }
|
||||
}
|
||||
|
||||
@keyframes city-breathe {
|
||||
0%, 100% { transform: scaleY(1); }
|
||||
50% { transform: scaleY(1.02); }
|
||||
}
|
||||
|
||||
@keyframes neon-glow-text {
|
||||
0%, 100% { text-shadow: 0 0 10px rgba(236, 72, 153, 0.5), 0 0 20px rgba(236, 72, 153, 0.3); }
|
||||
50% { text-shadow: 0 0 20px rgba(236, 72, 153, 0.8), 0 0 40px rgba(236, 72, 153, 0.5); }
|
||||
}
|
||||
`;
|
||||
|
||||
// Building data: [width, heightPercent, leftPercent, glowColor]
|
||||
const BUILDINGS: [number, number, number, string][] = [
|
||||
[40, 18, 0, 'rgba(236, 72, 153, 0.4)'],
|
||||
[55, 28, 4, 'rgba(168, 85, 247, 0.35)'],
|
||||
[35, 15, 9, 'rgba(6, 182, 212, 0.3)'],
|
||||
[70, 35, 12, 'rgba(236, 72, 153, 0.45)'],
|
||||
[45, 22, 19, 'rgba(168, 85, 247, 0.4)'],
|
||||
[50, 30, 23, 'rgba(6, 182, 212, 0.35)'],
|
||||
[30, 16, 28, 'rgba(236, 72, 153, 0.3)'],
|
||||
[80, 38, 31, 'rgba(168, 85, 247, 0.5)'],
|
||||
[42, 20, 39, 'rgba(6, 182, 212, 0.4)'],
|
||||
[60, 32, 43, 'rgba(236, 72, 153, 0.45)'],
|
||||
[38, 17, 49, 'rgba(168, 85, 247, 0.3)'],
|
||||
[75, 36, 52, 'rgba(6, 182, 212, 0.5)'],
|
||||
[48, 25, 60, 'rgba(236, 72, 153, 0.4)'],
|
||||
[55, 30, 65, 'rgba(168, 85, 247, 0.45)'],
|
||||
[33, 14, 70, 'rgba(6, 182, 212, 0.3)'],
|
||||
[85, 40, 73, 'rgba(236, 72, 153, 0.5)'],
|
||||
[44, 21, 82, 'rgba(168, 85, 247, 0.4)'],
|
||||
[58, 33, 86, 'rgba(6, 182, 212, 0.45)'],
|
||||
[36, 16, 92, 'rgba(236, 72, 153, 0.35)'],
|
||||
[50, 27, 95, 'rgba(168, 85, 247, 0.4)'],
|
||||
];
|
||||
|
||||
// Neon tube data: [topPercent, color, animationDelay, animationDuration]
|
||||
const NEON_TUBES: [number, string, string, string][] = [
|
||||
[22, '#ec4899', '0s', '3s'],
|
||||
[38, '#06b6d4', '1s', '4s'],
|
||||
[52, '#a855f7', '2s', '3.5s'],
|
||||
];
|
||||
|
||||
// Rain drop data: [leftPercent, delay, duration]
|
||||
const RAIN_DROPS: [number, string, string][] = [
|
||||
[5, '0s', '1.5s'],
|
||||
[15, '0.3s', '1.8s'],
|
||||
[25, '0.7s', '1.3s'],
|
||||
[35, '0.2s', '1.6s'],
|
||||
[45, '0.9s', '1.4s'],
|
||||
[55, '0.5s', '1.7s'],
|
||||
[65, '0.1s', '1.5s'],
|
||||
[75, '0.8s', '1.9s'],
|
||||
[85, '0.4s', '1.3s'],
|
||||
[95, '0.6s', '1.6s'],
|
||||
[10, '1.1s', '1.4s'],
|
||||
[30, '1.3s', '1.7s'],
|
||||
[50, '1.0s', '1.5s'],
|
||||
[70, '1.5s', '1.8s'],
|
||||
[90, '1.2s', '1.4s'],
|
||||
[20, '0.15s', '2.0s'],
|
||||
[40, '0.85s', '1.35s'],
|
||||
[60, '1.25s', '1.65s'],
|
||||
[80, '0.45s', '1.75s'],
|
||||
[8, '0.95s', '1.55s'],
|
||||
];
|
||||
|
||||
export default function NeonCityView(): JSX.Element {
|
||||
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{KEYFRAMES}</style>
|
||||
<div
|
||||
style={{
|
||||
minHeight: '100vh',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: 'linear-gradient(180deg, #0a0418 0%, #1a0b2e 40%, #0d0718 100%)',
|
||||
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
||||
}}
|
||||
>
|
||||
{/* Grid floor background */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: '-60%',
|
||||
left: '-50%',
|
||||
width: '200%',
|
||||
height: '100%',
|
||||
backgroundImage:
|
||||
'linear-gradient(rgba(236, 72, 153, 0.04) 1px, transparent 1px), linear-gradient(90deg, rgba(6, 182, 212, 0.04) 1px, transparent 1px)',
|
||||
backgroundSize: '60px 60px',
|
||||
animation: 'grid-move 8s linear infinite',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Neon tubes */}
|
||||
{NEON_TUBES.map(([top, color, delay, duration], i) => (
|
||||
<div
|
||||
key={`neon-${i}`}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: `${top}%`,
|
||||
left: '5%',
|
||||
right: '5%',
|
||||
height: '2px',
|
||||
background: color,
|
||||
color: color,
|
||||
boxShadow: `0 0 10px ${color}, 0 0 20px ${color}, 0 0 40px ${color}`,
|
||||
animation: `neon-pulse ${duration} ease-in-out infinite ${delay}, neon-flicker ${duration} ease-in-out infinite ${delay}`,
|
||||
borderRadius: '1px',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* City skyline buildings */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '40%',
|
||||
animation: 'city-breathe 6s ease-in-out infinite',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
{BUILDINGS.map(([width, heightPct, leftPct, glowColor], i) => (
|
||||
<div
|
||||
key={`building-${i}`}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: `${leftPct}%`,
|
||||
width: `${width}px`,
|
||||
height: `${heightPct}%`,
|
||||
background: 'rgba(5, 3, 15, 0.9)',
|
||||
borderRadius: '2px 2px 0 0',
|
||||
boxShadow: `0 -2px 10px ${glowColor}`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Rain drops */}
|
||||
{RAIN_DROPS.map(([left, delay, duration], i) => (
|
||||
<div
|
||||
key={`rain-${i}`}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: `${left}%`,
|
||||
top: 0,
|
||||
width: '1px',
|
||||
height: '25px',
|
||||
background: 'linear-gradient(180deg, transparent, rgba(6, 182, 212, 0.3))',
|
||||
animation: `rain-fall ${duration} linear infinite ${delay}`,
|
||||
pointerEvents: 'none',
|
||||
zIndex: 3,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Fog layer */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '35%',
|
||||
background: 'linear-gradient(180deg, transparent 0%, rgba(10, 4, 24, 0.6) 50%, rgba(10, 4, 24, 0.85) 100%)',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 4,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Top glow */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '-10%',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
width: '600px',
|
||||
height: '300px',
|
||||
background: 'radial-gradient(ellipse, rgba(168, 85, 247, 0.12) 0%, transparent 70%)',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 0,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Login card */}
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
zIndex: 10,
|
||||
width: '100%',
|
||||
maxWidth: 400,
|
||||
margin: '0 20px',
|
||||
background: 'rgba(10, 5, 25, 0.8)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
WebkitBackdropFilter: 'blur(20px)',
|
||||
border: '1px solid rgba(236, 72, 153, 0.2)',
|
||||
borderRadius: 16,
|
||||
padding: '44px 36px',
|
||||
boxShadow: '0 0 20px rgba(236, 72, 153, 0.1), 0 8px 40px rgba(0,0,0,0.5)',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{/* Top neon line decoration */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '2px',
|
||||
background: 'linear-gradient(90deg, #ec4899, #06b6d4, #a855f7, #ec4899)',
|
||||
backgroundSize: '200% 100%',
|
||||
animation: 'neon-pulse 3s ease-in-out infinite',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Brand area */}
|
||||
<div style={{ textAlign: 'center', marginBottom: 32 }}>
|
||||
{/* Logo */}
|
||||
<div
|
||||
style={{
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 12,
|
||||
background: 'linear-gradient(135deg, #ec4899, #06b6d4)',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: 20,
|
||||
boxShadow: '0 0 15px rgba(236, 72, 153, 0.5), 0 0 30px rgba(6, 182, 212, 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>
|
||||
|
||||
{/* Title */}
|
||||
<h1
|
||||
style={{
|
||||
fontSize: 24,
|
||||
fontWeight: 700,
|
||||
color: '#fff',
|
||||
margin: '0 0 8px',
|
||||
letterSpacing: '0.5px',
|
||||
animation: 'neon-glow-text 3s ease-in-out infinite',
|
||||
}}
|
||||
>
|
||||
SQL审核平台
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
<p
|
||||
style={{
|
||||
fontSize: 14,
|
||||
color: 'rgba(6, 182, 212, 0.8)',
|
||||
margin: 0,
|
||||
letterSpacing: '0.3px',
|
||||
}}
|
||||
>
|
||||
企业级数据库审核与合规管理
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Login form */}
|
||||
<form onSubmit={handleLogin}>
|
||||
{/* Username input */}
|
||||
<div style={{ marginBottom: 18 }}>
|
||||
<label
|
||||
htmlFor="username"
|
||||
style={{
|
||||
display: 'block',
|
||||
fontSize: 13,
|
||||
fontWeight: 500,
|
||||
color: 'rgba(255,255,255,0.7)',
|
||||
marginBottom: 8,
|
||||
letterSpacing: '0.3px',
|
||||
}}
|
||||
>
|
||||
用户名
|
||||
</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="请输入用户名"
|
||||
autoComplete="username"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 46,
|
||||
padding: '0 16px',
|
||||
fontSize: 14,
|
||||
color: '#fff',
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
border: '1px solid rgba(255,255,255,0.08)',
|
||||
borderRadius: 10,
|
||||
outline: 'none',
|
||||
boxSizing: 'border-box',
|
||||
transition: 'border-color 0.3s ease, box-shadow 0.3s ease',
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
e.currentTarget.style.borderColor = 'rgba(236, 72, 153, 0.6)';
|
||||
e.currentTarget.style.boxShadow = '0 0 10px rgba(236, 72, 153, 0.2)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.currentTarget.style.borderColor = 'rgba(255,255,255,0.08)';
|
||||
e.currentTarget.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Password input */}
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<label
|
||||
htmlFor="password"
|
||||
style={{
|
||||
display: 'block',
|
||||
fontSize: 13,
|
||||
fontWeight: 500,
|
||||
color: 'rgba(255,255,255,0.7)',
|
||||
marginBottom: 8,
|
||||
letterSpacing: '0.3px',
|
||||
}}
|
||||
>
|
||||
密码
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="请输入密码"
|
||||
autoComplete="current-password"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 46,
|
||||
padding: '0 16px',
|
||||
fontSize: 14,
|
||||
color: '#fff',
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
border: '1px solid rgba(255,255,255,0.08)',
|
||||
borderRadius: 10,
|
||||
outline: 'none',
|
||||
boxSizing: 'border-box',
|
||||
transition: 'border-color 0.3s ease, box-shadow 0.3s ease',
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
e.currentTarget.style.borderColor = 'rgba(6, 182, 212, 0.6)';
|
||||
e.currentTarget.style.boxShadow = '0 0 10px rgba(6, 182, 212, 0.2)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.currentTarget.style.borderColor = 'rgba(255,255,255,0.08)';
|
||||
e.currentTarget.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Error message */}
|
||||
{error && (
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 16,
|
||||
padding: '10px 14px',
|
||||
borderRadius: 8,
|
||||
background: 'rgba(239, 68, 68, 0.1)',
|
||||
border: '1px solid rgba(239, 68, 68, 0.2)',
|
||||
color: '#f87171',
|
||||
fontSize: 13,
|
||||
textShadow: '0 0 8px rgba(239, 68, 68, 0.4)',
|
||||
animation: 'neon-flicker 0.5s ease-in-out',
|
||||
}}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Login button */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!username || !password || loading}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 48,
|
||||
fontSize: 15,
|
||||
fontWeight: 600,
|
||||
color: '#fff',
|
||||
background: 'linear-gradient(135deg, #ec4899, #06b6d4)',
|
||||
border: 'none',
|
||||
borderRadius: 10,
|
||||
cursor: !username || !password || loading ? 'not-allowed' : 'pointer',
|
||||
opacity: !username || !password ? 0.6 : 1,
|
||||
boxShadow: '0 0 15px rgba(236, 72, 153, 0.4)',
|
||||
transition: 'all 0.3s ease',
|
||||
letterSpacing: '0.5px',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (username && password && !loading) {
|
||||
e.currentTarget.style.boxShadow = '0 0 25px rgba(236, 72, 153, 0.6), 0 0 40px rgba(6, 182, 212, 0.3)';
|
||||
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.boxShadow = '0 0 15px rgba(236, 72, 153, 0.4)';
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
style={{ animation: 'spin 1s linear infinite' }}
|
||||
>
|
||||
<style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
|
||||
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
||||
</svg>
|
||||
登录中...
|
||||
</span>
|
||||
) : (
|
||||
'登 录'
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* Copyright */}
|
||||
<p
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
fontSize: 12,
|
||||
color: 'rgba(255,255,255,0.25)',
|
||||
marginTop: 28,
|
||||
marginBottom: 0,
|
||||
}}
|
||||
>
|
||||
© SQL审核平台 · 安全合规的数据库管理
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user