feat: 添加用户登录认证功能

- 新增登录页面(使用 shadcn-svelte 组件)
- 后端添加 JWT 认证 API (/api/auth/login, /api/auth/validate)
- 用户账号通过 server/config.yaml 配置
- 前端路由保护(未登录跳转登录页)
- 侧边栏显示当前用户信息
- 支持退出登录功能
This commit is contained in:
clz
2026-01-11 18:50:01 +08:00
parent 4884993d27
commit 829b3445bc
11 changed files with 639 additions and 16 deletions

179
web/src/lib/stores/auth.ts Normal file
View File

@@ -0,0 +1,179 @@
import { writable } from 'svelte/store';
import { browser } from '$app/environment';
export interface User {
username: string;
name: string;
email?: string;
avatar?: string;
role: 'admin' | 'user';
}
export interface AuthState {
isAuthenticated: boolean;
user: User | null;
token: string | null;
}
// API 基础路径
const API_BASE = '/api';
// 从 localStorage 恢复状态
function getInitialState(): AuthState {
if (browser) {
const stored = localStorage.getItem('auth');
if (stored) {
try {
const state = JSON.parse(stored);
if (state.isAuthenticated && state.token) {
return state;
}
localStorage.removeItem('auth');
} catch {
localStorage.removeItem('auth');
}
}
}
return {
isAuthenticated: false,
user: null,
token: null,
};
}
function createAuthStore() {
const { subscribe, set } = writable<AuthState>(getInitialState());
return {
subscribe,
// 通过后端 API 登录验证
loginAsync: async (username: string, password: string): Promise<{ success: boolean; error?: string }> => {
try {
const response = await fetch(`${API_BASE}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
});
const result = await response.json();
if (!response.ok || !result.success) {
return { success: false, error: result.error || '登录失败' };
}
const { token, user } = result.data;
const state: AuthState = {
isAuthenticated: true,
user: {
username: user.username,
name: user.name,
email: user.email,
role: user.role,
},
token,
};
if (browser) {
localStorage.setItem('auth', JSON.stringify(state));
}
set(state);
return { success: true };
} catch (error) {
console.error('Login error:', error);
return { success: false, error: '网络错误,请稍后重试' };
}
},
// 验证 token 是否有效
validateToken: async (): Promise<boolean> => {
if (!browser) return false;
const stored = localStorage.getItem('auth');
if (!stored) return false;
try {
const state = JSON.parse(stored);
if (!state.token) return false;
const response = await fetch(`${API_BASE}/auth/validate`, {
headers: {
'Authorization': `Bearer ${state.token}`,
},
});
if (!response.ok) {
localStorage.removeItem('auth');
set({ isAuthenticated: false, user: null, token: null });
return false;
}
return true;
} catch {
return false;
}
},
// 兼容旧的同步登录方法
login: (user: User, token: string) => {
const state: AuthState = {
isAuthenticated: true,
user,
token,
};
if (browser) {
localStorage.setItem('auth', JSON.stringify(state));
}
set(state);
},
logout: () => {
if (browser) {
localStorage.removeItem('auth');
}
set({
isAuthenticated: false,
user: null,
token: null,
});
},
// 检查是否已登录
check: (): boolean => {
if (browser) {
const stored = localStorage.getItem('auth');
if (stored) {
try {
const state = JSON.parse(stored);
return state.isAuthenticated === true && !!state.token;
} catch {
return false;
}
}
}
return false;
},
// 获取当前 token
getToken: (): string | null => {
if (browser) {
const stored = localStorage.getItem('auth');
if (stored) {
try {
const state = JSON.parse(stored);
return state.token;
} catch {
return null;
}
}
}
return null;
}
};
}
export const auth = createAuthStore();