feat(layout): 实现应用布局和侧边栏功能
- 添加侧边栏组件,支持展开/收缩和移动端适配 - 实现导航菜单,支持高亮当前路由 - 添加主题选择器组件 - 集成认证状态显示和登出功能 - 优化侧边栏在不同屏幕尺寸下的行为 - 添加多种图标支持,包括logo、菜单、主页等 - 创建NavItem类型定义,用于导航菜单项 - 扩展sidebarStore,增加手动控制状态管理 - 添加数据看板页面占位内容 - 更新全局布局文件以支持主题和侧边栏状态管理
This commit is contained in:
@@ -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>
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
13
src/lib/types/layout.ts
Normal 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[];
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
<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="">
|
<main class="">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</main>
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -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>
|
|
||||||
1
src/routes/app/statistics/+page.svelte
Normal file
1
src/routes/app/statistics/+page.svelte
Normal file
@@ -0,0 +1 @@
|
|||||||
|
这是数据
|
||||||
Reference in New Issue
Block a user