feat: server connect mongo

This commit is contained in:
CHE LIANG ZHAO
2026-01-08 23:42:01 +08:00
parent ccd2d0386a
commit c1ffe2e822
17 changed files with 1455 additions and 338 deletions

View File

@@ -1,11 +1,13 @@
<script lang="ts">
import '../app.css';
import { page } from '$app/stores';
import { onMount } from 'svelte';
import * as Sidebar from '$lib/components/ui/sidebar';
import { Button } from '$lib/components/ui/button';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import * as Avatar from '$lib/components/ui/avatar';
import { Separator } from '$lib/components/ui/separator';
// Icons
import Upload from '@lucide/svelte/icons/upload';
import ClipboardCheck from '@lucide/svelte/icons/clipboard-check';
import FileText from '@lucide/svelte/icons/file-text';
@@ -13,30 +15,65 @@
import Settings from '@lucide/svelte/icons/settings';
import HelpCircle from '@lucide/svelte/icons/help-circle';
import Search from '@lucide/svelte/icons/search';
import Moon from '@lucide/svelte/icons/moon';
import Sun from '@lucide/svelte/icons/sun';
import ChevronsUpDown from '@lucide/svelte/icons/chevrons-up-down';
import Wallet from '@lucide/svelte/icons/wallet';
import LogOut from '@lucide/svelte/icons/log-out';
import User from '@lucide/svelte/icons/user';
import Bell from '@lucide/svelte/icons/bell';
import Sparkles from '@lucide/svelte/icons/sparkles';
// Theme
import {
type ThemeMode,
themeConfig,
getNextTheme,
applyThemeToDocument,
loadThemeFromStorage,
saveThemeToStorage
} from '$lib/config/theme';
let { children } = $props();
let { children } = $props();
let darkMode = $state(false);
let themeMode = $state<ThemeMode>('system');
function toggleDarkMode() {
darkMode = !darkMode;
if (darkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
onMount(() => {
themeMode = loadThemeFromStorage();
applyThemeToDocument(themeMode);
// 监听系统主题变化
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = () => applyThemeToDocument(themeMode);
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
});
function cycleTheme() {
themeMode = getNextTheme(themeMode);
saveThemeToStorage(themeMode);
applyThemeToDocument(themeMode);
}
const mainNavItems = [
{ href: '/', label: '上传', icon: Upload },
{ href: '/review', label: '复核', icon: ClipboardCheck },
{ href: '/bills', label: '账单', icon: FileText },
{ href: '/analysis', label: '分析', icon: BarChart3 },
// 主导航
const navMain = [
{ href: '/', label: '上传账单', icon: Upload },
{ href: '/review', label: '智能复核', icon: ClipboardCheck },
{ href: '/bills', label: '账单管理', icon: FileText },
{ href: '/analysis', label: '数据分析', icon: BarChart3 },
];
// 次级导航(底部)
const navSecondary = [
{ href: '/settings', label: '设置', icon: Settings },
{ href: '/help', label: '帮助', icon: HelpCircle },
];
// 用户数据
const user = {
name: '用户',
email: 'user@example.com',
avatar: ''
};
function isActive(href: string, pathname: string): boolean {
if (href === '/') return pathname === '/';
return pathname.startsWith(href);
@@ -44,57 +81,33 @@
</script>
<Sidebar.Provider>
<Sidebar.Root>
<Sidebar.Root collapsible="offcanvas">
<!-- Header: Logo + App Name -->
<Sidebar.Header>
<Sidebar.Menu>
<Sidebar.MenuItem>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Sidebar.MenuButton
{...props}
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
<span class="text-lg">💰</span>
</div>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">BillAI</span>
<span class="truncate text-xs text-muted-foreground">智能账单分析</span>
</div>
<ChevronsUpDown class="ml-auto" />
</Sidebar.MenuButton>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content
class="w-[--bits-dropdown-menu-anchor-width] min-w-56 rounded-lg"
side="bottom"
align="end"
sideOffset={4}
>
<DropdownMenu.Label class="text-xs text-muted-foreground">
版本信息
</DropdownMenu.Label>
<DropdownMenu.Item>
<span class="mr-2">📦</span>
v0.1.0 Beta
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<Sidebar.MenuButton class="!p-1.5">
{#snippet child({ props })}
<a href="/" {...props} class="flex items-center gap-2">
<div class="flex size-8 items-center justify-center rounded-lg bg-gradient-to-br from-orange-500 to-amber-500 text-white">
<Wallet class="size-5" />
</div>
<span class="text-base font-semibold">BillAI</span>
</a>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
</Sidebar.Menu>
</Sidebar.Header>
<Sidebar.Content>
<!-- 主导航 -->
<Sidebar.Group>
<Sidebar.GroupLabel>主要功能</Sidebar.GroupLabel>
<Sidebar.GroupContent>
<Sidebar.Menu>
{#each mainNavItems as item}
{#each navMain as item}
<Sidebar.MenuItem>
<Sidebar.MenuButton
isActive={isActive(item.href, $page.url.pathname)}
>
<Sidebar.MenuButton isActive={isActive(item.href, $page.url.pathname)}>
{#snippet child({ props })}
<a href={item.href} {...props}>
<item.icon class="size-4" />
@@ -108,50 +121,41 @@
</Sidebar.GroupContent>
</Sidebar.Group>
<Sidebar.Group>
<Sidebar.GroupLabel>系统</Sidebar.GroupLabel>
<!-- 次级导航 (底部) -->
<Sidebar.Group class="mt-auto">
<Sidebar.GroupContent>
<Sidebar.Menu>
<!-- 主题切换 -->
<Sidebar.MenuItem>
<Sidebar.MenuButton>
{#snippet child({ props })}
<button {...props} onclick={toggleDarkMode}>
{#if darkMode}
<Sun class="size-4" />
<span>浅色模式</span>
{:else}
<Moon class="size-4" />
<span>深色模式</span>
{/if}
{@const theme = themeConfig[themeMode]}
<button {...props} onclick={cycleTheme}>
<theme.icon class="size-4" />
<span>{theme.label}</span>
</button>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
<Sidebar.MenuItem>
<Sidebar.MenuButton>
{#snippet child({ props })}
<a href="/settings" {...props}>
<Settings class="size-4" />
<span>设置</span>
</a>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
<Sidebar.MenuItem>
<Sidebar.MenuButton>
{#snippet child({ props })}
<a href="/help" {...props}>
<HelpCircle class="size-4" />
<span>帮助</span>
</a>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
{#each navSecondary as item}
<Sidebar.MenuItem>
<Sidebar.MenuButton>
{#snippet child({ props })}
<a href={item.href} {...props}>
<item.icon class="size-4" />
<span>{item.label}</span>
</a>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
{/each}
</Sidebar.Menu>
</Sidebar.GroupContent>
</Sidebar.Group>
</Sidebar.Content>
<!-- Footer: 用户信息 -->
<Sidebar.Footer>
<Sidebar.Menu>
<Sidebar.MenuItem>
@@ -163,13 +167,13 @@
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar.Root class="h-8 w-8 rounded-lg">
<Avatar.Fallback class="rounded-lg bg-gradient-to-br from-primary to-chart-1 text-primary-foreground">
U
<Avatar.Fallback class="rounded-lg bg-gradient-to-br from-violet-500 to-purple-600 text-white font-medium">
{user.name.charAt(0).toUpperCase()}
</Avatar.Fallback>
</Avatar.Root>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">用户</span>
<span class="truncate text-xs text-muted-foreground">user@example.com</span>
<span class="truncate font-semibold">{user.name}</span>
<span class="truncate text-xs text-muted-foreground">{user.email}</span>
</div>
<ChevronsUpDown class="ml-auto size-4" />
</Sidebar.MenuButton>
@@ -184,23 +188,43 @@
<DropdownMenu.Label class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar.Root class="h-8 w-8 rounded-lg">
<Avatar.Fallback class="rounded-lg bg-gradient-to-br from-primary to-chart-1 text-primary-foreground">
U
<Avatar.Fallback class="rounded-lg bg-gradient-to-br from-violet-500 to-purple-600 text-white font-medium">
{user.name.charAt(0).toUpperCase()}
</Avatar.Fallback>
</Avatar.Root>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">用户</span>
<span class="truncate text-xs text-muted-foreground">user@example.com</span>
<span class="truncate font-semibold">{user.name}</span>
<span class="truncate text-xs text-muted-foreground">{user.email}</span>
</div>
</div>
</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item>
<Settings class="mr-2 size-4" />
账户设置
<Sparkles class="mr-2 size-4" />
升级到 Pro
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item>
<User class="mr-2 size-4" />
账户
</DropdownMenu.Item>
<DropdownMenu.Item>
<Settings class="mr-2 size-4" />
设置
</DropdownMenu.Item>
<DropdownMenu.Item>
<Bell class="mr-2 size-4" />
通知
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Item>
<LogOut class="mr-2 size-4" />
退出登录
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Sidebar.MenuItem>
@@ -210,7 +234,7 @@
</Sidebar.Root>
<Sidebar.Inset>
<header class="flex h-16 shrink-0 items-center gap-2 border-b px-4">
<header class="flex h-14 shrink-0 items-center gap-2 border-b px-4">
<Sidebar.Trigger class="-ml-1" />
<Separator orientation="vertical" class="mr-2 h-4" />
<div class="flex items-center gap-2">
@@ -229,7 +253,7 @@
</header>
<main class="flex-1 p-6">
{@render children()}
{@render children()}
</main>
</Sidebar.Inset>
</Sidebar.Provider>