feat: 添加用户登录认证功能
- 新增登录页面(使用 shadcn-svelte 组件) - 后端添加 JWT 认证 API (/api/auth/login, /api/auth/validate) - 用户账号通过 server/config.yaml 配置 - 前端路由保护(未登录跳转登录页) - 侧边栏显示当前用户信息 - 支持退出登录功能
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { checkHealth } from '$lib/api';
|
||||
import { auth, type User as AuthUser } from '$lib/stores/auth';
|
||||
import * as Sidebar from '$lib/components/ui/sidebar';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import * as Avatar from '$lib/components/ui/avatar';
|
||||
@@ -37,6 +40,17 @@
|
||||
let themeMode = $state<ThemeMode>('system');
|
||||
let serverOnline = $state(true);
|
||||
let checkingHealth = $state(true);
|
||||
let isAuthenticated = $state(false);
|
||||
let currentUser = $state<AuthUser | null>(null);
|
||||
|
||||
// 订阅认证状态
|
||||
$effect(() => {
|
||||
const unsubscribe = auth.subscribe(state => {
|
||||
isAuthenticated = state.isAuthenticated;
|
||||
currentUser = state.user;
|
||||
});
|
||||
return unsubscribe;
|
||||
});
|
||||
|
||||
async function checkServerHealth() {
|
||||
checkingHealth = true;
|
||||
@@ -44,10 +58,23 @@
|
||||
checkingHealth = false;
|
||||
}
|
||||
|
||||
// 登出
|
||||
function handleLogout() {
|
||||
auth.logout();
|
||||
goto('/login');
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
themeMode = loadThemeFromStorage();
|
||||
applyThemeToDocument(themeMode);
|
||||
|
||||
// 检查登录状态,未登录则跳转到登录页
|
||||
const pathname = $page.url.pathname;
|
||||
if (!auth.check() && pathname !== '/login' && pathname !== '/health') {
|
||||
goto('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查服务器状态
|
||||
checkServerHealth();
|
||||
// 每 30 秒检查一次
|
||||
@@ -83,12 +110,12 @@
|
||||
{ href: '/help', label: '帮助', icon: HelpCircle },
|
||||
];
|
||||
|
||||
// 用户数据
|
||||
const user = {
|
||||
name: '用户',
|
||||
email: 'user@example.com',
|
||||
avatar: ''
|
||||
};
|
||||
// 用户数据(从认证状态中获取)
|
||||
let user = $derived({
|
||||
name: currentUser?.username || '用户',
|
||||
email: currentUser?.email || 'user@example.com',
|
||||
avatar: currentUser?.avatar || ''
|
||||
});
|
||||
|
||||
function isActive(href: string, pathname: string): boolean {
|
||||
if (href === '/') return pathname === '/';
|
||||
@@ -106,8 +133,14 @@
|
||||
};
|
||||
return titles[pathname] || 'BillAI';
|
||||
}
|
||||
|
||||
// 检查是否是登录页面(登录页不显示侧边栏)
|
||||
let isLoginPage = $derived($page.url.pathname === '/login');
|
||||
</script>
|
||||
|
||||
{#if isLoginPage}
|
||||
{@render children()}
|
||||
{:else}
|
||||
<Sidebar.Provider>
|
||||
<Sidebar.Root collapsible="offcanvas">
|
||||
<!-- Header: Logo + App Name -->
|
||||
@@ -249,7 +282,7 @@
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleLogout}>
|
||||
<LogOut class="mr-2 size-4" />
|
||||
退出登录
|
||||
</DropdownMenu.Item>
|
||||
@@ -266,7 +299,7 @@
|
||||
<Sidebar.Trigger class="-ml-1" />
|
||||
<Separator orientation="vertical" class="mr-2 h-4" />
|
||||
<h1 class="text-lg font-semibold">{getPageTitle($page.url.pathname)}</h1>
|
||||
<div class="flex-1" />
|
||||
<div class="flex-1"></div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
class="flex items-center gap-1.5 text-sm hover:opacity-80 transition-opacity"
|
||||
@@ -299,3 +332,4 @@
|
||||
</main>
|
||||
</Sidebar.Inset>
|
||||
</Sidebar.Provider>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user