- 添加侧边栏组件,支持展开/收缩和移动端适配 - 实现导航菜单,支持高亮当前路由 - 添加主题选择器组件 - 集成认证状态显示和登出功能 - 优化侧边栏在不同屏幕尺寸下的行为 - 添加多种图标支持,包括logo、菜单、主页等 - 创建NavItem类型定义,用于导航菜单项 - 扩展sidebarStore,增加手动控制状态管理 - 添加数据看板页面占位内容 - 更新全局布局文件以支持主题和侧边栏状态管理
154 lines
4.2 KiB
Svelte
154 lines
4.2 KiB
Svelte
<script lang="ts">
|
|
import { goto } from '$app/navigation';
|
|
import { resolve } from '$app/paths';
|
|
import { authService } from '$lib/api/services/authService.ts';
|
|
import ThemeSelector from '$lib/widget/ThemeSelector.svelte';
|
|
import Icon from '$lib/components/icon/Icon.svelte';
|
|
import { authStore } from '$lib/stores/authStore.ts';
|
|
import { sidebarStore, toggleSidebar, toggleSidebarOpen } from '$lib/stores/sidebarStore';
|
|
import { page } from '$app/state';
|
|
|
|
import { fly, fade } from 'svelte/transition';
|
|
import type { NavItem } from '$lib/types/layout.ts';
|
|
|
|
let { children } = $props();
|
|
|
|
const rawNavItems: NavItem[] = [
|
|
{
|
|
id: 'dashboard',
|
|
label: '仪表盘',
|
|
icon: 'home',
|
|
href: '/app/dashboard',
|
|
isActive: true
|
|
},
|
|
{
|
|
id: 'statistics',
|
|
label: '数据看板',
|
|
icon: 'data-pie',
|
|
href: '/app/statistics',
|
|
}
|
|
];
|
|
|
|
/**
|
|
* 处理移动端关闭逻辑
|
|
* 仅在屏幕宽度小于 768px (Tailwind 的 md 断点) 时触发关闭
|
|
*/
|
|
function handleMobileClose() {
|
|
// 获取当前窗口宽度
|
|
const isMobile = window.innerWidth < 768;
|
|
|
|
// 如果是移动端且侧边栏是打开状态,则关闭它
|
|
if (isMobile && $sidebarStore.isOpen) {
|
|
toggleSidebarOpen();
|
|
}
|
|
}
|
|
|
|
|
|
// 2. 使用 $derived 生成响应式的新数组
|
|
// Svelte 5 会自动追踪 $page.url.pathname 的变化
|
|
let navItems = $derived(rawNavItems.map(item => {
|
|
// 处理根路径 '/' 的特殊逻辑,防止所有页面都高亮首页
|
|
const isActive = item.href === '/'
|
|
? page.url.pathname === '/'
|
|
: page.url.pathname.startsWith(item.href);
|
|
|
|
return {
|
|
...item,
|
|
isActive // 将计算结果合并进去
|
|
};
|
|
}));
|
|
|
|
|
|
</script>
|
|
|
|
<div class="flex h-screen bg-base-300 overflow-hidden relative">
|
|
|
|
{#if $sidebarStore.isOpen}
|
|
<div
|
|
role="button"
|
|
tabindex="0"
|
|
class="fixed inset-0 bg-black/50 z-20 md:hidden cursor-pointer backdrop-blur-sm"
|
|
onclick={handleMobileClose}
|
|
onkeydown={(e) => e.key === 'Escape' && handleMobileClose()}
|
|
transition:fade={{ duration: 200 }}
|
|
></div>
|
|
|
|
<aside
|
|
in:fly={{duration: 200, x: -100}}
|
|
out:fly={{duration: 200, x: -100}}
|
|
class="flex-shrink-0 flex flex-col bg-base-200 border-r border-base-100/70 fixed h-full md:relative z-30
|
|
transition-all duration-500 ease-in-out {$sidebarStore.isExpanded ? 'w-56' : 'w-16'}"
|
|
>
|
|
<div class="h-12 p-4 transition-all duration-1000">
|
|
<a href={resolve("/app/dashboard")} class="flex items-center gap-2" onclick={handleMobileClose}>
|
|
<Icon id="logo" size="32" className="flex-shrink-0" />
|
|
<p class="truncate flex-shrink-1"> </p>
|
|
{#if $sidebarStore.isExpanded}
|
|
<p class="truncate font-bold font-serif">IT DTMS</p>
|
|
{/if}
|
|
</a>
|
|
</div>
|
|
|
|
<ul class="menu menu-vertical transition-all duration-1000 w-full">
|
|
{#each navItems as item(item.id)}
|
|
<li class="w-full {item.isActive ? 'menu-active' : ''}">
|
|
<a href={resolve(item.href)} onclick={handleMobileClose}>
|
|
<Icon id={item.icon} size="24" />
|
|
{#if $sidebarStore.isExpanded}
|
|
<p class="truncate">{item.label}</p>
|
|
{/if}
|
|
</a>
|
|
</li>
|
|
{/each}
|
|
</ul>
|
|
|
|
<div class="absolute bottom-2 right-3">
|
|
<button
|
|
class="btn btn-square btn-ghost"
|
|
onclick={toggleSidebar}
|
|
>
|
|
<Icon id="menu" size="24" />
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
|
|
{/if}
|
|
|
|
<div class="w-full overflow-y-auto">
|
|
<header class="w-full h-18 flex justify-between items-center px-4 bg-base-300">
|
|
<div class="md:hidden">
|
|
<button
|
|
class="btn btn-square btn-ghost"
|
|
onclick={toggleSidebarOpen}
|
|
>
|
|
<Icon id="menu" size="24" />
|
|
</button>
|
|
</div>
|
|
<div class=""></div>
|
|
<div class="flex justify-center items-center gap-4">
|
|
<ThemeSelector/>
|
|
{#if $authStore.isAuthenticated}
|
|
<button
|
|
tabindex="0"
|
|
class="rounded-full bg-primary h-12 w-12"
|
|
onclick={()=>{
|
|
console.log("退出登录")
|
|
authService.logout()
|
|
}}
|
|
aria-label="Logout"
|
|
></button>
|
|
{:else}
|
|
<div class="flex items-center">
|
|
<div class="w-24">
|
|
<button class="btn btn-primary btn-wide" onclick={()=>{goto(resolve("/auth/login"))}}>登录</button>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</header>
|
|
|
|
<main class="">
|
|
{@render children()}
|
|
</main>
|
|
</div>
|
|
</div> |