refactor(layout): 重构应用布局结构
- 将原有布局中的侧边栏和头部组件拆分为独立的 AppSidebar 和 AppHeader 组件 - 移除内联的导航逻辑和样式,交由专用组件管理 - 更新图标库,优化部分图标的显示效果 - 简化认证存储逻辑,增强状态持久化与安全性 - 优化侧边栏状态管理机制,提高响应式体验 - 改进登录流程错误处理,增加网络异常提示 - 调整路由组件结构,提升代码可维护性
This commit is contained in:
@@ -1,154 +1,21 @@
|
||||
<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';
|
||||
import AppHeader from '$lib/components/layout/app/AppHeader.svelte';
|
||||
import AppSidebar from '$lib/components/layout/app/AppSidebar.svelte';
|
||||
|
||||
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>
|
||||
<AppSidebar />
|
||||
|
||||
<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>
|
||||
<div class="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
|
||||
<ul class="menu pt-6 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>
|
||||
<AppHeader />
|
||||
|
||||
<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="">
|
||||
<main class="flex-1 overflow-y-auto p-4">
|
||||
{@render children()}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
Reference in New Issue
Block a user