feat: 外观设置后端持久化 + 保存按钮 + 跨浏览器同步

后端:
- User 模型新增 Preferences JSON 字段 (theme + login_style)
- 新增 GET/PUT /api/v1/user/preferences (auth) 个人偏好读写
- 新增 GET /api/v1/public/login-style (public) 登录页风格公开端点
- SQLite users 表手动添加 preferences 列

前端:
- AppearanceSettings 从后端加载偏好 + 保存按钮 + dirty 状态提示
- LoginPage 先从后端公开接口获取 login_style (跨浏览器一致)
- useLogin 登录成功后从后端加载偏好并应用主题
- 偏好同时存 localStorage(即时) + 后端DB(跨设备)
This commit is contained in:
logan committed 2026-05-26 22:09:47 +08:00
1 parent 8469df359f
commit 9055cc77ec
7 files changed
+369 -119

No files matched your search

+11 -2
View File
@@ -1,6 +1,8 @@
import { useState, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '@/hooks/useAuth';
import { useTheme } from '@/hooks/useTheme';
import api from '@/api/request';
export interface LoginState {
username: string;
@@ -15,6 +17,7 @@ export interface LoginState {
export function useLogin(): LoginState {
const navigate = useNavigate();
const { login } = useAuth();
const { setTheme } = useTheme();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
@@ -26,13 +29,19 @@ export function useLogin(): LoginState {
setLoading(true);
setError('');
const ok = await login(username, password);
setLoading(false);
if (ok) {
// Apply user preferences from backend (cross-browser sync)
try {
const prefs = await api.get<{ theme?: string; login_style?: string }>('/user/preferences');
if (prefs?.theme) setTheme(prefs.theme as 'dark' | 'light');
if (prefs?.login_style) localStorage.setItem('login-style', prefs.login_style);
} catch { /* ignore */ }
navigate('/dashboard');
} else {
setError('用户名或密码错误');
}
}, [username, password, login, navigate]);
setLoading(false);
}, [username, password, login, navigate, setTheme]);
return { username, password, loading, error, setUsername, setPassword, handleLogin };
}