feat: 5种专业登录页风格 + 外观设置 + 文档全面更新
- 新增 5 种登录页组件: SplitView/CenteredCard/SidePanel/FullImage/MinimalDark - 提取 useLogin hook 共享登录逻辑 - LoginPage 改为风格分发器, localStorage(login-style) 控制渲染 - SettingsPage 新增外观设置 tab: 主题切换 + 登录风格选择器(含缩略图预览) - AppearanceSettings 组件: 5 种风格卡片式切换 - 文档全面更新为 Go/Gin 架构: - ARCHITECTURE.md: Go 项目结构/handler/GORM 模型/中间件/LDAP/驱动矩阵 - FEATURES.md: v0.3.1 功能清单含登录主题系统 - ROADMAP.md: Python→Go 迁移历史 + Sprint 计划
This commit is contained in:
1 parent
f2e93a09c0
commit
14e394fb09
12 files changed
+1147
-639
No files matched your search
@@ -0,0 +1,151 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
import { LOGIN_STYLE_OPTIONS, type LoginStyle } from '@/pages/LoginPage';
|
||||
import { Sun, Moon, Monitor, Check } from 'lucide-react';
|
||||
|
||||
export default function AppearanceSettings() {
|
||||
const { theme, setTheme, isDark } = useTheme();
|
||||
const [loginStyle, setLoginStyle] = useState<LoginStyle>('split');
|
||||
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem('login-style') as LoginStyle | null;
|
||||
if (stored) setLoginStyle(stored);
|
||||
}, []);
|
||||
|
||||
const handleThemeChange = (t: 'dark' | 'light') => {
|
||||
setTheme(t);
|
||||
};
|
||||
|
||||
const handleLoginStyleChange = (id: LoginStyle) => {
|
||||
setLoginStyle(id);
|
||||
localStorage.setItem('login-style', id);
|
||||
};
|
||||
|
||||
const themeOptions = [
|
||||
{ id: 'dark' as const, icon: Moon, label: '深色模式' },
|
||||
{ id: 'light' as const, icon: Sun, label: '浅色模式' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* 主题模式 */}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold mb-3">主题模式</h3>
|
||||
<div className="flex gap-3">
|
||||
{themeOptions.map(opt => (
|
||||
<button
|
||||
key={opt.id}
|
||||
onClick={() => handleThemeChange(opt.id)}
|
||||
className={`flex items-center gap-2 px-5 py-3 rounded-xl text-sm font-medium transition-all ${
|
||||
theme === opt.id
|
||||
? 'bg-blue-500/15 text-blue-400 ring-1 ring-blue-500/30'
|
||||
: 'bg-[var(--dg-bg-tertiary)] text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)]'
|
||||
}`}
|
||||
>
|
||||
<opt.icon className="w-4 h-4" />
|
||||
{opt.label}
|
||||
{theme === opt.id && <Check className="w-3.5 h-3.5 ml-1" />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 登录页风格 */}
|
||||
<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 => (
|
||||
<button
|
||||
key={opt.id}
|
||||
onClick={() => handleLoginStyleChange(opt.id)}
|
||||
className={`group relative rounded-xl p-4 text-left transition-all ${
|
||||
loginStyle === opt.id
|
||||
? 'bg-blue-500/10 ring-1 ring-blue-500/30'
|
||||
: 'bg-[var(--dg-bg-tertiary)] hover:bg-[var(--dg-bg-hover)]'
|
||||
}`}
|
||||
>
|
||||
{/* 预览缩略图 */}
|
||||
<div className={`w-full aspect-[4/3] rounded-lg mb-3 overflow-hidden ${
|
||||
opt.id === 'split' ? 'flex' :
|
||||
opt.id === 'centered' ? 'flex items-center justify-center dg-gradient-br' :
|
||||
opt.id === 'sidepanel' ? 'flex' :
|
||||
opt.id === 'fullimage' ? 'flex items-center justify-center' :
|
||||
'flex items-center justify-center bg-[#0a0a0a]'
|
||||
}`}>
|
||||
{opt.id === 'split' && (
|
||||
<>
|
||||
<div className="flex-1 bg-[var(--dg-bg-primary)] p-1.5">
|
||||
<div className="w-3 h-1 rounded-sm bg-blue-500/30 mb-1" />
|
||||
<div className="grid grid-cols-2 gap-0.5">
|
||||
{[0,1,2,3].map(i => <div key={i} className="h-1.5 rounded-sm bg-[var(--dg-bg-tertiary)]" />)}
|
||||
</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>
|
||||
</>
|
||||
)}
|
||||
{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="w-4 h-1 rounded-sm bg-white/40" />
|
||||
<div className="w-6 h-1 rounded-sm bg-white/20" />
|
||||
<div className="w-full h-1 rounded-sm bg-white/15" />
|
||||
<div className="w-full h-1 rounded-sm bg-white/15" />
|
||||
<div className="w-full h-1.5 rounded-sm bg-white/30" />
|
||||
</div>
|
||||
)}
|
||||
{opt.id === 'sidepanel' && (
|
||||
<>
|
||||
<div className="w-2/5 dg-gradient-br p-1.5 flex flex-col justify-center items-center gap-0.5">
|
||||
<div className="w-3 h-3 rounded-full bg-white/20" />
|
||||
<div className="w-4 h-0.5 rounded-sm bg-white/15" />
|
||||
</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>
|
||||
</>
|
||||
)}
|
||||
{opt.id === 'fullimage' && (
|
||||
<div className="w-full h-full" style={{
|
||||
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)',
|
||||
}}>
|
||||
<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">
|
||||
<div className="w-3 h-0.5 rounded-sm bg-white/30" />
|
||||
<div className="w-full h-0.5 rounded-sm bg-white/10" />
|
||||
<div className="w-full h-0.5 rounded-sm bg-white/10" />
|
||||
<div className="w-full h-1 rounded-sm bg-blue-500/40" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{opt.id === 'minimal' && (
|
||||
<div className="w-full h-full flex flex-col items-center justify-center gap-1 p-2">
|
||||
<div className="w-4 h-1 rounded-sm bg-white/20" />
|
||||
<div className="w-6 h-0.5 rounded-sm bg-white/10" />
|
||||
<div className="w-full h-1 rounded-sm bg-white/8" />
|
||||
<div className="w-full h-1 rounded-sm bg-white/8" />
|
||||
<div className="w-full h-1.5 rounded-sm bg-white/15" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 选中标记 */}
|
||||
{loginStyle === 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)]">{opt.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user