feat(layout): 实现应用布局和侧边栏功能

- 添加侧边栏组件,支持展开/收缩和移动端适配
- 实现导航菜单,支持高亮当前路由
- 添加主题选择器组件
- 集成认证状态显示和登出功能
- 优化侧边栏在不同屏幕尺寸下的行为
- 添加多种图标支持,包括logo、菜单、主页等
- 创建NavItem类型定义,用于导航菜单项
- 扩展sidebarStore,增加手动控制状态管理
- 添加数据看板页面占位内容
- 更新全局布局文件以支持主题和侧边栏状态管理
This commit is contained in:
Chaos
2025-11-22 22:45:49 +08:00
parent 26fef2fd7a
commit 65cf80fb51
9 changed files with 325 additions and 81 deletions

View File

@@ -14,4 +14,110 @@
<symbol id="panel-right-close-solid" viewBox="0 0 24 24"> <symbol id="panel-right-close-solid" viewBox="0 0 24 24">
<path fill="currentColor" d="M9.367 2.25h5.266c1.092 0 1.958 0 2.655.057c.714.058 1.317.18 1.869.46a4.75 4.75 0 0 1 2.075 2.077c.281.55.403 1.154.461 1.868c.057.697.057 1.563.057 2.655v5.266c0 1.092 0 1.958-.057 2.655c-.058.714-.18 1.317-.46 1.869a4.75 4.75 0 0 1-2.076 2.075c-.552.281-1.155.403-1.869.461c-.697.057-1.563.057-2.655.057H9.367c-1.092 0-1.958 0-2.655-.057c-.714-.058-1.317-.18-1.868-.46a4.75 4.75 0 0 1-2.076-2.076c-.281-.552-.403-1.155-.461-1.869c-.057-.697-.057-1.563-.057-2.655V9.367c0-1.092 0-1.958.057-2.655c.058-.714.18-1.317.46-1.868a4.75 4.75 0 0 1 2.077-2.076c.55-.281 1.154-.403 1.868-.461c.697-.057 1.563-.057 2.655-.057m6.383 17.997a20 20 0 0 0 1.416-.049c.62-.05 1.005-.147 1.31-.302a3.25 3.25 0 0 0 1.42-1.42c.155-.305.251-.69.302-1.31c.051-.63.052-1.434.052-2.566V9.4c0-1.132 0-1.937-.052-2.566c-.05-.62-.147-1.005-.302-1.31a3.25 3.25 0 0 0-1.42-1.42c-.305-.155-.69-.251-1.31-.302a20 20 0 0 0-1.416-.05zM7.47 8.47a.75.75 0 0 0 0 1.06L9.94 12l-2.47 2.47a.75.75 0 1 0 1.06 1.06l3-3a.75.75 0 0 0 0-1.06l-3-3a.75.75 0 0 0-1.06 0" /> <path fill="currentColor" d="M9.367 2.25h5.266c1.092 0 1.958 0 2.655.057c.714.058 1.317.18 1.869.46a4.75 4.75 0 0 1 2.075 2.077c.281.55.403 1.154.461 1.868c.057.697.057 1.563.057 2.655v5.266c0 1.092 0 1.958-.057 2.655c-.058.714-.18 1.317-.46 1.869a4.75 4.75 0 0 1-2.076 2.075c-.552.281-1.155.403-1.869.461c-.697.057-1.563.057-2.655.057H9.367c-1.092 0-1.958 0-2.655-.057c-.714-.058-1.317-.18-1.868-.46a4.75 4.75 0 0 1-2.076-2.076c-.281-.552-.403-1.155-.461-1.869c-.057-.697-.057-1.563-.057-2.655V9.367c0-1.092 0-1.958.057-2.655c.058-.714.18-1.317.46-1.868a4.75 4.75 0 0 1 2.077-2.076c.55-.281 1.154-.403 1.868-.461c.697-.057 1.563-.057 2.655-.057m6.383 17.997a20 20 0 0 0 1.416-.049c.62-.05 1.005-.147 1.31-.302a3.25 3.25 0 0 0 1.42-1.42c.155-.305.251-.69.302-1.31c.051-.63.052-1.434.052-2.566V9.4c0-1.132 0-1.937-.052-2.566c-.05-.62-.147-1.005-.302-1.31a3.25 3.25 0 0 0-1.42-1.42c-.305-.155-.69-.251-1.31-.302a20 20 0 0 0-1.416-.05zM7.47 8.47a.75.75 0 0 0 0 1.06L9.94 12l-2.47 2.47a.75.75 0 1 0 1.06 1.06l3-3a.75.75 0 0 0 0-1.06l-3-3a.75.75 0 0 0-1.06 0" />
</symbol> </symbol>
<symbol id="panel-left-close" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
<path d="M9 3.5v17m7-5.5l-3-3l3-3" />
<path d="M3 9.4c0-2.24 0-3.36.436-4.216a4 4 0 0 1 1.748-1.748C6.04 3 7.16 3 9.4 3h5.2c2.24 0 3.36 0 4.216.436a4 4 0 0 1 1.748 1.748C21 6.04 21 7.16 21 9.4v5.2c0 2.24 0 3.36-.436 4.216a4 4 0 0 1-1.748 1.748C17.96 21 16.84 21 14.6 21H9.4c-2.24 0-3.36 0-4.216-.436a4 4 0 0 1-1.748-1.748C3 17.96 3 16.84 3 14.6z" />
</g>
</symbol>
<symbol id="panel-left-close-solid" viewBox="0 0 24 24">
<path fill="currentColor" d="M9.367 2.25h5.266c1.092 0 1.958 0 2.655.057c.714.058 1.317.18 1.869.46a4.75 4.75 0 0 1 2.075 2.077c.281.55.403 1.154.461 1.868c.057.697.057 1.563.057 2.655v5.266c0 1.092 0 1.958-.057 2.655c-.058.714-.18 1.317-.46 1.869a4.75 4.75 0 0 1-2.076 2.075c-.552.281-1.155.403-1.869.461c-.697.057-1.563.057-2.655.057H9.367c-1.092 0-1.958 0-2.655-.057c-.714-.058-1.317-.18-1.868-.46a4.75 4.75 0 0 1-2.076-2.076c-.281-.552-.403-1.155-.461-1.869c-.057-.697-.057-1.563-.057-2.655V9.367c0-1.092 0-1.958.057-2.655c.058-.714.18-1.317.46-1.868a4.75 4.75 0 0 1 2.077-2.076c.55-.281 1.154-.403 1.868-.461c.697-.057 1.563-.057 2.655-.057M6.834 3.802c-.62.05-1.005.147-1.31.302a3.25 3.25 0 0 0-1.42 1.42c-.155.305-.251.69-.302 1.31c-.051.63-.052 1.434-.052 2.566v5.2c0 1.133 0 1.937.052 2.566c.05.62.147 1.005.302 1.31a3.25 3.25 0 0 0 1.42 1.42c.305.155.69.251 1.31.302c.392.032.851.044 1.416.05V3.752c-.565.005-1.024.017-1.416.049M16.53 8.47a.75.75 0 0 0-1.06 0l-3 3a.75.75 0 0 0 0 1.06l3 3a.75.75 0 1 0 1.06-1.06L14.06 12l2.47-2.47a.75.75 0 0 0 0-1.06" />
</symbol>
<symbol id="starburst" viewBox="0 0 48 48">
<g fill="none">
<path fill="url(#SVGvr5ORdWH)" d="M25.183 2.58a1.5 1.5 0 0 0-2.368 0l-3.388 4.356l-5.112-2.078a1.5 1.5 0 0 0-2.051 1.184l-.756 5.467l-5.467.756a1.5 1.5 0 0 0-1.184 2.05l2.078 5.113l-4.356 3.388a1.5 1.5 0 0 0 0 2.368l4.356 3.388l-2.078 5.113a1.5 1.5 0 0 0 1.184 2.05l5.467.757l.756 5.466a1.5 1.5 0 0 0 2.05 1.184l5.113-2.078l3.388 4.356a1.5 1.5 0 0 0 2.368 0l3.388-4.356l5.113 2.078a1.5 1.5 0 0 0 2.05-1.184l.756-5.466l5.467-.757a1.5 1.5 0 0 0 1.184-2.05l-2.078-5.113l4.356-3.388a1.5 1.5 0 0 0 0-2.368l-4.356-3.388l2.078-5.113a1.5 1.5 0 0 0-1.184-2.05l-5.467-.756l-.756-5.467a1.5 1.5 0 0 0-2.05-1.184L28.57 6.936z" />
<path fill="url(#SVGRWDvEe1n)" fill-opacity="0.95" d="M24 14c.69 0 1.25.56 1.25 1.25v7.5h7.5a1.25 1.25 0 1 1 0 2.5h-7.5v7.5a1.25 1.25 0 1 1-2.5 0v-7.5h-7.5a1.25 1.25 0 1 1 0-2.5h7.5v-7.5c0-.69.56-1.25 1.25-1.25" />
<defs>
<radialGradient id="SVGvr5ORdWH" cx="0" cy="0" r="1" gradientTransform="rotate(-119.49 41.522 10.903)scale(97.2587 93.1572)" gradientUnits="userSpaceOnUse">
<stop stop-color="#ffc470" />
<stop offset=".251" stop-color="#ff835c" />
<stop offset=".55" stop-color="#f24a9d" />
<stop offset=".814" stop-color="#b339f0" />
</radialGradient>
<linearGradient id="SVGRWDvEe1n" x1="32.611" x2="11.626" y1="39.646" y2="26.053" gradientUnits="userSpaceOnUse">
<stop offset=".024" stop-color="#ffc8d7" />
<stop offset=".807" stop-color="#fff" />
</linearGradient>
</defs>
</g>
</symbol>
<symbol id="data-pie" viewBox="0 0 32 32">
<g fill="none">
<path fill="url(#SVGkSGRmbrV)" d="M15 7.012a1 1 0 0 0-1.047-1C7.855 6.3 3 11.333 3 17.5C3 23.851 8.149 29 14.5 29c6.168 0 11.201-4.855 11.487-10.953A1 1 0 0 0 24.988 17H17.5a2.5 2.5 0 0 1-2.5-2.5z" />
<path fill="url(#SVGOonY3dQP)" d="M18.047 3.013A1 1 0 0 0 17 4.012V14a1 1 0 0 0 1 1h9.988a1 1 0 0 0 1-1.047C28.71 8.037 23.962 3.29 18.046 3.013" />
<defs>
<linearGradient id="SVGkSGRmbrV" x1="25.988" x2="-10.793" y1="29" y2="-7.781" gradientUnits="userSpaceOnUse">
<stop stop-color="#6d37cd" />
<stop offset=".641" stop-color="#ea71ef" />
</linearGradient>
<linearGradient id="SVGOonY3dQP" x1="27.989" x2="19.398" y1="12.802" y2="3.012" gradientUnits="userSpaceOnUse">
<stop stop-color="#e23cb4" />
<stop offset="1" stop-color="#ea71ef" />
</linearGradient>
</defs>
</g>
</symbol>
<symbol id="home" viewBox="0 0 48 48">
<g fill="none">
<path fill="url(#SVGHYC0XdPj)" d="M18.067 27h12v16h-12z" />
<path fill="url(#SVGAip0Sdul)" d="M26.461 7.855a3.78 3.78 0 0 0-4.787 0L8.499 18.597a3.91 3.91 0 0 0-1.432 3.031v17.485C7.067 41.26 8.78 43 10.892 43h8.175V30.5a2.5 2.5 0 0 1 2.5-2.5h5a2.5 2.5 0 0 1 2.5 2.5V43h8.175c2.113 0 3.825-1.74 3.825-3.887V21.628a3.91 3.91 0 0 0-1.43-3.031z" />
<path fill="url(#SVGDweprdys)" fill-rule="evenodd" d="m24.068 9.329l-16 13.215a2.054 2.054 0 0 1-2.852-.262a1.956 1.956 0 0 1 .267-2.794L22.28 5.628a2.83 2.83 0 0 1 3.523-.024l16.805 13.588a1.957 1.957 0 0 1 .307 2.79a2.054 2.054 0 0 1-2.848.3z" clip-rule="evenodd" />
<defs>
<linearGradient id="SVGHYC0XdPj" x1="24.067" x2="13.481" y1="27" y2="44.65" gradientUnits="userSpaceOnUse">
<stop stop-color="#944600" />
<stop offset="1" stop-color="#cd8e02" />
</linearGradient>
<linearGradient id="SVGAip0Sdul" x1="10.313" x2="45.173" y1="5.24" y2="32" gradientUnits="userSpaceOnUse">
<stop stop-color="#ffd394" />
<stop offset="1" stop-color="#ffb357" />
</linearGradient>
<linearGradient id="SVGDweprdys" x1="17.817" x2="25.308" y1=".725" y2="22.452" gradientUnits="userSpaceOnUse">
<stop stop-color="#ff921f" />
<stop offset="1" stop-color="#eb4824" />
</linearGradient>
</defs>
</g>
</symbol>
<symbol id="menu" viewBox="0 0 24 24">
<path fill="currentColor" d="M3.75 6.5a.75.75 0 0 1 .75-.75h15a.75.75 0 0 1 0 1.5h-15a.75.75 0 0 1-.75-.75m0 5.5a.75.75 0 0 1 .75-.75h15a.75.75 0 0 1 0 1.5h-15a.75.75 0 0 1-.75-.75m0 5.5a.75.75 0 0 1 .75-.75h15a.75.75 0 0 1 0 1.5h-15a.75.75 0 0 1-.75-.75" />
</symbol>
<symbol id="logo" viewBox="0 0 375 374.999991">
<defs>
<clipPath id="fc5bc8767a">
<path d="M 58.5 0 L 316.5 0 C 332.015625 0 346.894531 6.164062 357.867188 17.132812 C 368.835938 28.105469 375 42.984375 375 58.5 L 375 316.5 C 375 332.015625 368.835938 346.894531 357.867188 357.867188 C 346.894531 368.835938 332.015625 375 316.5 375 L 58.5 375 C 26.191406 375 0 348.808594 0 316.5 L 0 58.5 C 0 26.191406 26.191406 0 58.5 0 Z M 58.5 0 " clip-rule="nonzero"/>
</clipPath>
<clipPath id="b295c49c3e">
<path d="M 0 0 L 375 0 L 375 375 L 0 375 Z M 0 0 " clip-rule="nonzero"/>
</clipPath>
<clipPath id="bfbc279fc9">
<path d="M 58.5 0 L 316.5 0 C 332.015625 0 346.894531 6.164062 357.867188 17.132812 C 368.835938 28.105469 375 42.984375 375 58.5 L 375 316.5 C 375 332.015625 368.835938 346.894531 357.867188 357.867188 C 346.894531 368.835938 332.015625 375 316.5 375 L 58.5 375 C 26.191406 375 0 348.808594 0 316.5 L 0 58.5 C 0 26.191406 26.191406 0 58.5 0 Z M 58.5 0 " clip-rule="nonzero"/>
</clipPath>
<clipPath id="063da92238">
<rect x="0" width="375" y="0" height="375"/>
</clipPath>
<clipPath id="cfbb605b8a">
<path d="M 75 88.359375 L 300 88.359375 L 300 286.359375 L 75 286.359375 Z M 75 88.359375 " clip-rule="nonzero"/>
</clipPath>
</defs>
<g clip-path="url(#fc5bc8767a)">
<g transform="matrix(1, 0, 0, 1, 0, 0)">
<g clip-path="url(#063da92238)">
<g clip-path="url(#b295c49c3e)">
<g clip-path="url(#bfbc279fc9)">
<rect x="-82.5" width="540" fill="#1e1e1e" height="539.999987" y="-82.499998" fill-opacity="1"/>
</g>
</g>
</g>
</g>
</g>
<g clip-path="url(#cfbb605b8a)">
<path fill="#ffffff" d="M 294.644531 174.898438 L 291.878906 170.023438 L 291.832031 170.023438 L 273.359375 138.019531 L 249.835938 97.03125 L 247.199219 92.507812 C 245.707031 89.945312 242.964844 88.371094 240.003906 88.371094 L 221.074219 88.371094 C 214.949219 88.371094 210.917969 94.753906 213.546875 100.285156 L 223.050781 120.25 C 223.140625 120.445312 223.242188 120.636719 223.351562 120.824219 L 243.175781 155.304688 L 251.59375 169.8125 C 257.941406 180.757812 257.964844 194.253906 251.660156 205.222656 L 244.746094 217.246094 L 244.5625 217.246094 L 242.058594 221.632812 C 238.851562 227.257812 230.734375 227.230469 227.558594 221.585938 L 155.027344 92.621094 C 153.550781 89.996094 150.773438 88.371094 147.765625 88.371094 L 135.160156 88.371094 C 132.257812 88.371094 129.5625 89.882812 128.050781 92.359375 L 125.230469 96.972656 L 101.675781 138.019531 L 78.15625 178.835938 C 75.042969 184.171875 75.003906 190.757812 78.050781 196.132812 L 78.132812 196.277344 L 81.566406 202.160156 L 101.675781 237.128906 L 125.203125 277.953125 L 127.835938 282.472656 C 129.332031 285.03125 132.070312 286.605469 135.035156 286.605469 L 156.164062 286.605469 C 162.601562 286.605469 166.609375 279.613281 163.351562 274.0625 L 151.742188 254.253906 L 131.859375 219.671875 L 121.308594 201.484375 C 116.289062 192.828125 116.289062 182.148438 121.308594 173.496094 L 131.851562 155.320312 L 133.03125 153.261719 C 136.242188 147.679688 144.296875 147.691406 147.492188 153.28125 L 221.234375 282.40625 C 222.71875 285.003906 225.476562 286.605469 228.46875 286.605469 L 240.003906 286.605469 C 242.964844 286.605469 245.707031 285.03125 247.199219 282.472656 L 249.820312 277.976562 L 273.363281 237.121094 L 296.882812 196.136719 C 299.996094 190.796875 300.015625 184.195312 296.921875 178.839844 L 294.644531 174.898438 " fill-opacity="1" fill-rule="nonzero"/>
</g>
</symbol>
</svg> </svg>

View File

@@ -4,28 +4,44 @@ import { writable } from 'svelte/store';
interface SidebarState { interface SidebarState {
isOpen: boolean; isOpen: boolean;
isExpanded: boolean; isExpanded: boolean;
isManualOverride: boolean;
} }
export const sidebarStore = writable<SidebarState>({ export const sidebarStore = writable<SidebarState>({
isOpen: false, isOpen: false,
isExpanded: false, isExpanded: false,
isManualOverride: false,
}) })
/** /**
* 切换侧边栏打开、隐藏(偏移隐藏)状态 * 切换侧边栏打开、隐藏(偏移隐藏)状态
*/ */
export const toggleSidebar = () => { export const toggleSidebarOpen = () => {
sidebarStore.update(state => ({ sidebarStore.update(state => ({
...state, ...state,
isOpen: !state.isOpen, isOpen: !state.isOpen,
isManualOverride: true,
}));
}
/**
* 重置手动控制状态
*/
export const resetManualOverride = () => {
sidebarStore.update(state => ({
...state,
isManualOverride: false,
})); }));
} }
/** /**
* 切换侧边栏展开状态 * 切换侧边栏展开状态
*/ */
export const toggleSidebarOpen = () => { export const toggleSidebar = () => {
sidebarStore.update(state => ({ sidebarStore.update(state => ({
...state, ...state,
isExpanded: !state.isExpanded, isExpanded: !state.isExpanded,

View File

@@ -1,4 +1,11 @@
export type IconId = export type IconId =
"panel-right-close" | "panel-right-close" |
"panel-right-close-solid" "panel-right-close-solid"|
"panel-left-close"|
"panel-left-close-solid"|
"data-pie"|
"starburst"|
"home"|
"menu"|
"logo"
; ;

13
src/lib/types/layout.ts Normal file
View File

@@ -0,0 +1,13 @@
import type { IconId } from '$lib/types/icon-ids.ts';
import type { RouteId } from '$app/types';
export interface NavItem {
id: string;
icon: IconId;
label: string;
href: RouteId;
isActive?: boolean;
isDisabled?: boolean;
isHidden?: boolean;
subItems?: NavItem[];
}

View File

@@ -3,7 +3,6 @@
import { DAISYUI_THEME_OPTIONS, type DaisyUIThemeID } from '$lib/types/theme.ts'; import { DAISYUI_THEME_OPTIONS, type DaisyUIThemeID } from '$lib/types/theme.ts';
import ThemePreview from '$lib/widget/ThemePreview.svelte'; import ThemePreview from '$lib/widget/ThemePreview.svelte';
// ... 逻辑保持不变 ...
const handleThemeChange = (themeValue: DaisyUIThemeID) => { const handleThemeChange = (themeValue: DaisyUIThemeID) => {
themeStore.set(themeValue); themeStore.set(themeValue);
}; };

View File

@@ -4,7 +4,6 @@
import { themeStore } from '$lib/stores/themeStore.ts'; import { themeStore } from '$lib/stores/themeStore.ts';
let { children } = $props(); let { children } = $props();
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { sidebarStore } from '$lib/stores/sidebarStore.ts'; import { sidebarStore } from '$lib/stores/sidebarStore.ts';
import Sprite from '$lib/components/icon/Sprite.svelte'; import Sprite from '$lib/components/icon/Sprite.svelte';
@@ -15,32 +14,51 @@
const handleMediaQueryChange = (event: MediaQueryListEvent) => { const handleMediaQueryChange = (event: MediaQueryListEvent) => {
const isCurrentlyDesktop = event.matches; const isCurrentlyDesktop = event.matches;
console.log(isCurrentlyDesktop);
sidebarStore.update((store) => ({ sidebarStore.update((store) => {
if (!store.isManualOverride) {
return {
...store, ...store,
isOpen: isCurrentlyDesktop isOpen: isCurrentlyDesktop
})); };
} }
let isMounted = $state(false); // 客户端渲染标志
if (isCurrentlyDesktop) {
return {
...store,
isOpen: true,
isManualOverride: false // PC端时恢复自动模式
};
}
return store;
});
}
let isMounted = $state(false);
onMount(()=>{ onMount(()=>{
isMounted = true; isMounted = true;
const isDesktop = window.matchMedia(MD_BREAKPOINT).matches; const mediaQuery = window.matchMedia(MD_BREAKPOINT);
console.log(isDesktop); const isDesktop = mediaQuery.matches;
sidebarStore.update((store) => ({ sidebarStore.update((store) => ({
...store, ...store,
isOpen: isDesktop, isOpen: isDesktop,
isManualOverride: false,
})); }));
const mediaQuery = window.matchMedia(MD_BREAKPOINT);
mediaQuery.addEventListener('change', handleMediaQueryChange); mediaQuery.addEventListener('change', handleMediaQueryChange);
return () => { return () => {
mediaQuery.removeEventListener('change', handleMediaQueryChange); mediaQuery.removeEventListener('change', handleMediaQueryChange);
} }
}) })
</script> </script>
<svelte:head> <svelte:head>

View File

@@ -1,8 +1,154 @@
<script lang="ts"> <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(); 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> </script>
<main class=""> <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()} {@render children()}
</main> </main>
</div>
</div>

View File

@@ -1,68 +1,6 @@
<script> <script>
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
import { authStore } from '$lib/stores/authStore.ts';
import { authService } from '$lib/api/services/authService.ts';
import ThemeSelector from '$lib/widget/ThemeSelector.svelte';
import { sidebarStore, toggleSidebarOpen } from '$lib/stores/sidebarStore';
import Icon from '$lib/components/icon/Icon.svelte';
</script> </script>
<div class="flex h-screen bg-base-300 font-sans overflow-hidden">
<aside class=" opacity-0 md:opacity-100 flex-shrink-0 flex flex-col bg-base-200 border-r border-gray-700/30
transition-all duration-500 ease-in-out relative
{$sidebarStore.isOpen ? 'translate-x-0' : '-translate-x-full'}
{$sidebarStore.isExpanded ? 'w-[280px]' : 'w-[72px]'}
">
<div class="h-16 flex items-center px-4 justify-start">
<button
on:click={toggleSidebarOpen}
class="p-2 hover:bg-gray-700/50 rounded-full transition-colors"
aria-label="Toggle Menu"
>
<Icon id={$sidebarStore.isExpanded ? "panel-right-close" :"panel-right-close-solid" }
on:hoever={}
size="24" />
</button>
</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
</aside>
<div class="w-full">
<header class="w-full h-18 flex justify-end items-center px-4 bg-base-300 gap-4 ">
<ThemeSelector/>
{#if $authStore.isAuthenticated}
<button
tabindex="0"
class="rounded-full bg-primary h-12 w-12 "
on:click={()=>{
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" on:click={()=>{goto(resolve("/auth/login"))}}>登录</button>
</div>
</div>
{/if}
</header>
</div>
</div>

View File

@@ -0,0 +1 @@
这是数据