372 lines
26 KiB
TypeScript
372 lines
26 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
|
import { useTheme, THEME_OPTIONS, type Theme } from '@/hooks/useTheme';
|
|
import { LOGIN_STYLE_OPTIONS, type LoginStyle } from '@/pages/LoginPage';
|
|
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<LoginStyle, { bg: string; accent: string; label: string }> = {
|
|
split: { bg: 'linear-gradient(135deg, #080c14 0%, #0c1420 100%)', accent: '#00d4aa', label: '晶格科技' },
|
|
centered: { bg: 'linear-gradient(180deg, #0a0a1a 0%, #1a1035 50%, #2d1810 100%)', accent: '#fbbf24', label: '晨曦微光' },
|
|
sidepanel: { bg: 'linear-gradient(135deg, rgba(88,166,255,0.08), rgba(137,87,229,0.08))', accent: '#3b82f6', label: '功能展示' },
|
|
fullimage: { bg: '#0d1117', accent: '#6366f1', label: 'GitHub/AWS' },
|
|
minimal: { bg: 'linear-gradient(160deg, #0a0a1a 0%, #0f0a2a 40%, #150825 70%, #0a0a1a 100%)', accent: '#ec4899', label: 'Premium Dark' },
|
|
aurora: { bg: 'linear-gradient(180deg, #050810 0%, #0a0e1a 30%, #0d1425 60%, #080c14 100%)', accent: '#10b981', label: '极光之境' },
|
|
cosmos: { bg: '#030508', accent: '#8b5cf6', label: '星际漫游' },
|
|
inkwash: { bg: 'linear-gradient(180deg, #f5f0e8 0%, #ebe4d6 100%)', accent: '#c9a96e', label: '水墨山水' },
|
|
neoncity: { bg: 'linear-gradient(180deg, #0a0418 0%, #1a0b2e 40%, #0d0718 100%)', accent: '#ec4899', label: '霓虹都市' },
|
|
gradientmesh: { bg: '#0a0a0f', accent: '#6366f1', label: '渐变网格' },
|
|
};
|
|
|
|
export default function AppearanceSettings() {
|
|
const { theme, setTheme } = useTheme();
|
|
const [loginStyle, setLoginStyle] = useState<LoginStyle>('split');
|
|
const [saving, setSaving] = useState(false);
|
|
const [loading, setLoading] = useState(true);
|
|
const [dirty, setDirty] = useState(false);
|
|
|
|
useEffect(() => {
|
|
api.get<{ theme?: string; login_style?: string }>('/user/preferences')
|
|
.then(prefs => {
|
|
if (prefs?.theme) {
|
|
const t = prefs.theme;
|
|
if (t === 'dark' || t === 'light') {
|
|
setTheme(t === 'dark' ? 'ocean-deep' : 'light-professional');
|
|
} else if (THEME_OPTIONS.some(o => o.id === t)) {
|
|
setTheme(t as Theme);
|
|
}
|
|
}
|
|
if (prefs?.login_style) setLoginStyle(prefs.login_style as LoginStyle);
|
|
})
|
|
.catch(() => {
|
|
const stored = localStorage.getItem('login-style') as LoginStyle | null;
|
|
if (stored) setLoginStyle(stored);
|
|
})
|
|
.finally(() => setLoading(false));
|
|
}, []);
|
|
|
|
const handleThemeChange = (t: Theme) => {
|
|
setTheme(t);
|
|
setDirty(true);
|
|
};
|
|
|
|
const handleLoginStyleChange = (id: LoginStyle) => {
|
|
setLoginStyle(id);
|
|
setDirty(true);
|
|
};
|
|
|
|
const handleSave = useCallback(async () => {
|
|
setSaving(true);
|
|
try {
|
|
await api.put('/user/preferences', {
|
|
theme,
|
|
login_style: loginStyle,
|
|
});
|
|
localStorage.setItem('login-style', loginStyle);
|
|
setDirty(false);
|
|
toast('success', '外观设置已保存');
|
|
} catch {
|
|
toast('error', '保存失败');
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
}, [theme, loginStyle]);
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center py-12">
|
|
<Loader2 className="w-5 h-5 animate-spin text-[var(--dg-text-muted)]" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-10">
|
|
{/* ===== Theme Mode Section ===== */}
|
|
<div>
|
|
<h3 className="text-sm font-semibold mb-1">界面配色</h3>
|
|
<p className="text-xs text-[var(--dg-text-muted)] mb-4">选择界面配色方案,即时预览效果</p>
|
|
<div className="grid grid-cols-5 gap-3">
|
|
{THEME_OPTIONS.map(opt => (
|
|
<button
|
|
key={opt.id}
|
|
onClick={() => handleThemeChange(opt.id)}
|
|
className={`group relative rounded-xl p-4 text-left transition-all bg-[var(--dg-bg-tertiary)] hover:bg-[var(--dg-bg-hover)] ${
|
|
theme === opt.id ? 'ring-2 ring-blue-500/50' : ''
|
|
}`}
|
|
>
|
|
{/* Color preview swatch */}
|
|
<div
|
|
className="w-full h-8 rounded-lg mb-3"
|
|
style={{
|
|
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} 60%, #ffffff) 50%, #f8fafc 100%)`,
|
|
}}
|
|
/>
|
|
|
|
{/* Active check mark */}
|
|
{theme === opt.id && (
|
|
<div className="absolute top-2 right-2 w-5 h-5 rounded-full bg-blue-500 flex items-center justify-center">
|
|
<Check className="w-3 h-3 text-white" />
|
|
</div>
|
|
)}
|
|
|
|
<h4 className="text-sm font-medium mb-0.5">{opt.name}</h4>
|
|
<p className="text-xs text-[var(--dg-text-muted)] leading-snug">{opt.description}</p>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* ===== Login Page Style Section ===== */}
|
|
<div>
|
|
<h3 className="text-sm font-semibold mb-1">登录页风格</h3>
|
|
<p className="text-xs text-[var(--dg-text-muted)] mb-4">选择登录页面的视觉风格,保存后退出登录即可预览</p>
|
|
<div className="grid grid-cols-5 gap-3">
|
|
{LOGIN_STYLE_OPTIONS.map(opt => {
|
|
const preview = LOGIN_PREVIEW_COLORS[opt.id];
|
|
return (
|
|
<button
|
|
key={opt.id}
|
|
onClick={() => handleLoginStyleChange(opt.id)}
|
|
className={`group relative rounded-xl p-4 text-left transition-all ${
|
|
loginStyle === opt.id
|
|
? 'ring-2 ring-indigo-500/50 bg-[var(--dg-bg-tertiary)]'
|
|
: 'bg-[var(--dg-bg-tertiary)] hover:bg-[var(--dg-bg-hover)]'
|
|
}`}
|
|
>
|
|
{/* Login style miniature preview */}
|
|
<div
|
|
className="w-full aspect-[4/3] rounded-lg mb-3 overflow-hidden relative"
|
|
style={{ background: preview.bg }}
|
|
>
|
|
{/* Abstract mini layout */}
|
|
{opt.id === 'split' && (
|
|
<>
|
|
<div className="absolute inset-0 flex">
|
|
{/* Grid lines preview */}
|
|
<div className="flex-1 relative overflow-hidden" style={{ background: '#080c14' }}>
|
|
<div className="absolute inset-0" style={{ backgroundImage: 'linear-gradient(rgba(0,212,170,0.08) 1px, transparent 1px), linear-gradient(90deg, rgba(0,212,170,0.08) 1px, transparent 1px)', backgroundSize: '10px 10px' }} />
|
|
{[20, 45, 70].map(t => <div key={t} className="absolute rounded-full" style={{ width: 3, height: 3, background: '#00d4aa', top: `${t}%`, left: `${30 + t * 0.5}%`, boxShadow: '0 0 4px #00d4aa' }} />)}
|
|
<div className="absolute bottom-2 left-2 flex flex-col gap-0.5">
|
|
<div className="w-5 h-1 rounded-sm" style={{ background: 'rgba(226,232,240,0.4)' }} />
|
|
<div className="w-7 h-0.5 rounded-sm" style={{ background: 'rgba(0,212,170,0.3)' }} />
|
|
</div>
|
|
</div>
|
|
<div className="w-2/5 p-2 flex flex-col justify-center gap-1" style={{ background: '#0c1017' }}>
|
|
<div className="h-0.5 rounded-sm" style={{ background: 'rgba(148,163,184,0.15)' }} />
|
|
<div className="h-0.5 rounded-sm" style={{ background: 'rgba(148,163,184,0.15)' }} />
|
|
<div className="h-1 rounded-sm mt-0.5" style={{ background: '#00d4aa', opacity: 0.5 }} />
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
{opt.id === 'centered' && (
|
|
<div className="absolute inset-0 flex items-center justify-center" style={{ background: 'linear-gradient(180deg, #0a0a1a 0%, #1a1035 50%, #2d1810 100%)' }}>
|
|
{/* Dawn rays */}
|
|
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-full h-3/4" style={{ background: 'radial-gradient(ellipse at 50% 100%, rgba(251,191,36,0.12), transparent 70%)' }} />
|
|
<div className="w-3/4 rounded-lg p-2 flex flex-col items-center gap-1 relative" style={{ background: 'rgba(10,10,26,0.6)', backdropFilter: 'blur(4px)', border: '1px solid rgba(251,191,36,0.1)' }}>
|
|
<div className="w-4 h-1 rounded-sm" style={{ background: 'rgba(251,191,36,0.5)' }} />
|
|
<div className="w-6 h-0.5 rounded-sm" style={{ background: 'rgba(251,191,36,0.2)' }} />
|
|
<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-1 rounded-sm mt-0.5" style={{ background: '#fbbf24', opacity: 0.5 }} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
{opt.id === 'sidepanel' && (
|
|
<div className="absolute inset-0">
|
|
<div className="absolute inset-0" style={{ background: 'radial-gradient(ellipse at 20% 20%, rgba(88,166,255,0.08), transparent 50%), radial-gradient(ellipse at 80% 80%, rgba(137,87,229,0.08), transparent 50%)' }} />
|
|
<div className="absolute inset-0 flex">
|
|
{/* Left: brand + feature cards */}
|
|
<div className="flex-1 p-1.5 flex flex-col justify-center gap-0.5">
|
|
<div className="flex items-center gap-0.5 mb-0.5">
|
|
<div className="w-2 h-2 rounded-sm" style={{ background: 'linear-gradient(135deg, #3b82f6, #8b5cf6)' }} />
|
|
<div className="w-6 h-0.5 rounded-sm" style={{ background: 'var(--dg-text-secondary, rgba(255,255,255,0.5))' }} />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-0.5">
|
|
{[0,1,2,3].map(i => (
|
|
<div key={i} className="h-2 rounded-sm" style={{ background: 'var(--dg-bg-tertiary, rgba(255,255,255,0.05))' }} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
{/* Right: login form */}
|
|
<div className="w-[30%] p-1 flex flex-col justify-center gap-0.5" style={{ background: 'var(--dg-bg-secondary, rgba(255,255,255,0.03))' }}>
|
|
<div className="w-3 h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.3)' }} />
|
|
<div className="w-full h-0.5 rounded-sm" style={{ background: 'var(--dg-bg-tertiary, rgba(255,255,255,0.08))' }} />
|
|
<div className="w-full h-0.5 rounded-sm" style={{ background: 'var(--dg-bg-tertiary, rgba(255,255,255,0.08))' }} />
|
|
<div className="w-full h-0.5 rounded-sm mt-0.5" style={{ background: '#3b82f6', opacity: 0.5 }} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{opt.id === 'fullimage' && (
|
|
<div className="absolute inset-0">
|
|
{/* Grid dots */}
|
|
<div className="absolute inset-0" style={{
|
|
backgroundImage: 'radial-gradient(circle, rgba(99,102,241,0.15) 0.5px, transparent 0.5px)',
|
|
backgroundSize: '12px 12px',
|
|
}} />
|
|
{/* Center card */}
|
|
<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>
|
|
)}
|
|
{opt.id === 'minimal' && (
|
|
<div className="absolute inset-0" style={{ background: 'linear-gradient(160deg, #0a0a1a, #0f0a2a, #150825)' }}>
|
|
{/* Flowing curve ribbons */}
|
|
<div className="absolute" style={{ top: '15%', left: '-20%', width: '140%', height: '40%', background: 'linear-gradient(135deg, rgba(236,72,153,0.2), rgba(168,85,247,0.15))', borderRadius: '50%', filter: 'blur(8px)', transform: 'rotate(-12deg)' }} />
|
|
<div className="absolute" style={{ top: '35%', left: '-10%', width: '120%', height: '30%', background: 'linear-gradient(135deg, rgba(129,140,248,0.15), rgba(192,132,252,0.1))', borderRadius: '50%', filter: 'blur(6px)', transform: 'rotate(5deg)' }} />
|
|
<div className="absolute" style={{ top: '55%', left: '5%', width: '90%', height: '15%', background: 'linear-gradient(90deg, transparent, rgba(244,114,182,0.2), rgba(192,132,252,0.15), transparent)', borderRadius: '50%', filter: 'blur(4px)', transform: 'rotate(-8deg)' }} />
|
|
{/* Center card */}
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<div className="w-3/4 rounded-md p-1.5 flex flex-col items-center gap-0.5" style={{ background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.06)', backdropFilter: 'blur(4px)' }}>
|
|
<div className="w-3 h-1 rounded-sm" style={{ background: 'rgba(255,255,255,0.3)' }} />
|
|
<div className="w-5 h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.12)' }} />
|
|
<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-1 rounded-sm mt-0.5" style={{ background: 'linear-gradient(90deg, #ec4899, #a855f7)', opacity: 0.5 }} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{opt.id === 'aurora' && (
|
|
<div className="absolute inset-0" style={{ background: 'linear-gradient(180deg, #050810 0%, #0a0e1a 30%, #0d1425 60%, #080c14 100%)' }}>
|
|
<div className="absolute" style={{ top: '20%', left: '-10%', width: '120%', height: '25%', background: 'linear-gradient(90deg, transparent, rgba(124,58,237,0.25), rgba(16,185,129,0.2), transparent)', borderRadius: '50%', filter: 'blur(10px)', transform: 'rotate(-5deg)' }} />
|
|
<div className="absolute" style={{ top: '35%', left: '-5%', width: '110%', height: '20%', background: 'linear-gradient(90deg, transparent, rgba(6,182,212,0.2), rgba(168,85,247,0.15), transparent)', borderRadius: '50%', filter: 'blur(8px)', transform: 'rotate(3deg)' }} />
|
|
{[15, 30, 55, 75].map(t => (
|
|
<div key={t} className="absolute rounded-full" style={{ width: 2, height: 2, background: 'rgba(255,255,255,0.6)', top: `${t}%`, left: `${20 + t}%`, boxShadow: '0 0 3px rgba(255,255,255,0.4)' }} />
|
|
))}
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<div className="w-3/4 rounded-md p-1.5 flex flex-col items-center gap-0.5" style={{ background: 'rgba(8,12,25,0.6)', border: '1px solid rgba(255,255,255,0.06)', backdropFilter: 'blur(4px)' }}>
|
|
<div className="w-3 h-1 rounded-sm" style={{ background: 'rgba(16,185,129,0.5)' }} />
|
|
<div className="w-5 h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.12)' }} />
|
|
<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-1 rounded-sm mt-0.5" style={{ background: 'linear-gradient(90deg, #7c3aed, #059669)', opacity: 0.5 }} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{opt.id === 'cosmos' && (
|
|
<div className="absolute inset-0" style={{ background: '#030508' }}>
|
|
<div className="absolute" style={{ top: '10%', left: '10%', width: '50%', height: '50%', background: 'radial-gradient(circle, rgba(59,130,246,0.15), transparent 70%)', filter: 'blur(8px)' }} />
|
|
<div className="absolute" style={{ bottom: '10%', right: '5%', width: '45%', height: '45%', background: 'radial-gradient(circle, rgba(139,92,246,0.12), transparent 70%)', filter: 'blur(8px)' }} />
|
|
{[10, 25, 40, 60, 80].map(t => (
|
|
<div key={t} className="absolute rounded-full" style={{ width: t % 3 === 0 ? 2.5 : 1.5, height: t % 3 === 0 ? 2.5 : 1.5, background: t % 2 === 0 ? 'rgba(147,197,253,0.8)' : '#fff', top: `${t}%`, left: `${15 + t * 0.8}%`, boxShadow: '0 0 3px rgba(255,255,255,0.3)' }} />
|
|
))}
|
|
<div className="absolute" style={{ top: '5%', right: '20%', width: '20%', height: 1, background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.6), transparent)', transform: 'rotate(-25deg)' }} />
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<div className="w-3/4 rounded-md p-1.5 flex flex-col items-center gap-0.5" style={{ background: 'rgba(5,8,16,0.7)', border: '1px solid rgba(255,255,255,0.05)', backdropFilter: 'blur(4px)' }}>
|
|
<div className="w-3 h-1 rounded-sm" style={{ background: 'rgba(139,92,246,0.5)' }} />
|
|
<div className="w-5 h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.12)' }} />
|
|
<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-1 rounded-sm mt-0.5" style={{ background: 'linear-gradient(90deg, #4f46e5, #8b5cf6)', opacity: 0.5 }} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{opt.id === 'inkwash' && (
|
|
<div className="absolute inset-0" style={{ background: 'linear-gradient(180deg, #f5f0e8 0%, #ebe4d6 100%)' }}>
|
|
<div className="absolute bottom-0 left-0 right-0" style={{ height: '35%', background: 'linear-gradient(to top, rgba(180,175,165,0.5) 0%, transparent 60%)', borderRadius: '50% 50% 0 0 / 20% 20% 0 0' }} />
|
|
<div className="absolute bottom-0 left-0 right-0" style={{ height: '20%', background: 'linear-gradient(to top, rgba(120,115,108,0.5) 0%, transparent 50%)', borderRadius: '50% 50% 0 0 / 30% 30% 0 0' }} />
|
|
<div className="absolute" style={{ top: '5%', left: '5%', width: '30%', height: '25%', background: 'radial-gradient(circle, rgba(60,58,52,0.08), transparent 70%)', filter: 'blur(8px)' }} />
|
|
<div className="absolute bottom-[30%] left-[10%] right-[10%] h-px" style={{ background: 'linear-gradient(90deg, transparent, rgba(201,169,110,0.4), transparent)' }} />
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<div className="w-3/4 rounded-md p-1.5 flex flex-col items-center gap-0.5" style={{ background: 'rgba(255,253,248,0.85)', border: '1px solid rgba(201,169,110,0.2)', backdropFilter: 'blur(4px)' }}>
|
|
<div className="w-3 h-1 rounded-sm" style={{ background: 'rgba(45,42,36,0.5)' }} />
|
|
<div className="w-5 h-0.5 rounded-sm" style={{ background: 'rgba(138,132,120,0.4)' }} />
|
|
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(45,42,36,0.06)' }} />
|
|
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(45,42,36,0.06)' }} />
|
|
<div className="w-full h-1 rounded-sm mt-0.5" style={{ background: 'linear-gradient(90deg, #2d2a24, #4a453d)', opacity: 0.5 }} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{opt.id === 'neoncity' && (
|
|
<div className="absolute inset-0" style={{ background: 'linear-gradient(180deg, #0a0418 0%, #1a0b2e 40%, #0d0718 100%)' }}>
|
|
<div className="absolute" style={{ top: '25%', left: 0, right: 0, height: 1, background: 'linear-gradient(90deg, transparent, rgba(236,72,153,0.4), transparent)', boxShadow: '0 0 4px rgba(236,72,153,0.3)' }} />
|
|
<div className="absolute" style={{ top: '50%', left: 0, right: 0, height: 1, background: 'linear-gradient(90deg, transparent, rgba(6,182,212,0.3), transparent)', boxShadow: '0 0 4px rgba(6,182,212,0.2)' }} />
|
|
{[10, 25, 40, 55, 70, 85].map((l, i) => (
|
|
<div key={i} className="absolute bottom-0" style={{ left: `${l}%`, width: `${8 + i * 3}px`, height: `${20 + (i % 3) * 12}%`, background: 'rgba(5,3,15,0.9)' }} />
|
|
))}
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<div className="w-3/4 rounded-md p-1.5 flex flex-col items-center gap-0.5" style={{ background: 'rgba(10,5,25,0.8)', border: '1px solid rgba(236,72,153,0.2)', backdropFilter: 'blur(4px)' }}>
|
|
<div className="w-3 h-1 rounded-sm" style={{ background: 'rgba(236,72,153,0.5)' }} />
|
|
<div className="w-5 h-0.5 rounded-sm" style={{ background: 'rgba(6,182,212,0.3)' }} />
|
|
<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-1 rounded-sm mt-0.5" style={{ background: 'linear-gradient(90deg, #ec4899, #06b6d4)', opacity: 0.5 }} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{opt.id === 'gradientmesh' && (
|
|
<div className="absolute inset-0" style={{ background: '#0a0a0f' }}>
|
|
<div className="absolute" style={{ top: '-20%', left: '-15%', width: '60%', height: '60%', background: 'radial-gradient(circle, rgba(99,102,241,0.2), transparent 70%)', filter: 'blur(10px)' }} />
|
|
<div className="absolute" style={{ bottom: '-15%', right: '-10%', width: '55%', height: '55%', background: 'radial-gradient(circle, rgba(244,63,94,0.15), transparent 70%)', filter: 'blur(10px)' }} />
|
|
<div className="absolute" style={{ top: '30%', right: '10%', width: '35%', height: '35%', background: 'radial-gradient(circle, rgba(16,185,129,0.1), transparent 70%)', filter: 'blur(8px)' }} />
|
|
<div className="absolute inset-0" style={{ backgroundImage: 'radial-gradient(circle, rgba(255,255,255,0.04) 0.5px, transparent 0.5px)', backgroundSize: '14px 14px' }} />
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<div className="w-3/4 rounded-md p-1.5 flex flex-col items-center gap-0.5" style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.08)', backdropFilter: 'blur(4px)' }}>
|
|
<div className="w-3 h-1 rounded-sm" style={{ background: 'rgba(99,102,241,0.5)' }} />
|
|
<div className="w-5 h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.12)' }} />
|
|
<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-1 rounded-sm mt-0.5" style={{ background: 'linear-gradient(90deg, #6366f1, #ec4899)', opacity: 0.5 }} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Active check mark */}
|
|
{loginStyle === opt.id && (
|
|
<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" />
|
|
</div>
|
|
)}
|
|
|
|
<h4 className="text-sm font-medium mb-0.5">{opt.name}</h4>
|
|
<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>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Save Button */}
|
|
<div className="flex items-center gap-3 pt-2">
|
|
<button
|
|
onClick={handleSave}
|
|
disabled={saving || !dirty}
|
|
className={`flex items-center gap-2 px-5 py-2.5 rounded-lg text-sm font-medium transition-all ${
|
|
dirty
|
|
? 'dg-gradient-bg text-white hover:opacity-90'
|
|
: 'bg-[var(--dg-bg-tertiary)] text-[var(--dg-text-muted)] cursor-not-allowed'
|
|
}`}
|
|
>
|
|
{saving ? (
|
|
<><Loader2 className="w-4 h-4 animate-spin" /> 保存中...</>
|
|
) : (
|
|
<><Save className="w-4 h-4" /> 保存设置</>
|
|
)}
|
|
</button>
|
|
{dirty && (
|
|
<span className="text-xs text-amber-400">有未保存的更改</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|