Compare commits

..

2 Commits

Author SHA1 Message Date
Chaos
f7cc0b675a Merge remote-tracking branch 'origin/master'
# Conflicts:
#	src/lib/widget/ThemeSelector.svelte
#	src/routes/app/dashboard/+page.svelte
2025-11-22 08:03:37 +08:00
Chaos
4493b9cc7a feat(sidebar): 实现响应式侧边栏状态管理
- 添加媒体查询监听以自动切换侧边栏显示状态
- 引入sidebarStore统一管理侧边栏开闭逻辑
- 移除页面内联侧边栏展开控制逻辑
- 添加Sprite图标组件客户端渲染条件判断
- 更新侧边栏样式类名以支持过渡动画效果
- 移除用户信息展示相关冗余代码块
2025-11-22 08:02:49 +08:00
12 changed files with 156 additions and 43 deletions

13
package-lock.json generated
View File

@@ -1141,7 +1141,6 @@
"integrity": "sha512-/rnwfSWS3qwUSzvHynUTORF9xSJi7PCR9yXkxUOnRrNqyKmCmh3FPHH+E9BbgqxXfTevGXBqgnlh9kMb+9T5XA==", "integrity": "sha512-/rnwfSWS3qwUSzvHynUTORF9xSJi7PCR9yXkxUOnRrNqyKmCmh3FPHH+E9BbgqxXfTevGXBqgnlh9kMb+9T5XA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@standard-schema/spec": "^1.0.0", "@standard-schema/spec": "^1.0.0",
"@sveltejs/acorn-typescript": "^1.0.5", "@sveltejs/acorn-typescript": "^1.0.5",
@@ -1181,7 +1180,6 @@
"integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
"debug": "^4.4.1", "debug": "^4.4.1",
@@ -1514,7 +1512,6 @@
"integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
@@ -1565,7 +1562,6 @@
"integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==", "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/scope-manager": "8.47.0",
"@typescript-eslint/types": "8.47.0", "@typescript-eslint/types": "8.47.0",
@@ -1784,7 +1780,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -2147,7 +2142,6 @@
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -3253,7 +3247,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -3281,7 +3274,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@@ -3415,7 +3407,6 @@
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },
@@ -3432,7 +3423,6 @@
"integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"prettier": "^3.0.0", "prettier": "^3.0.0",
"svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
@@ -3762,7 +3752,6 @@
"integrity": "sha512-d1R+3pFa39LXoHCsxHmV//D2pSFZlEMlnxCVQ54TlrQv+4o5pewJO0/Pc5MUp+j71PJrOrPJHTvREZJHn+ymDQ==", "integrity": "sha512-d1R+3pFa39LXoHCsxHmV//D2pSFZlEMlnxCVQ54TlrQv+4o5pewJO0/Pc5MUp+j71PJrOrPJHTvREZJHn+ymDQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@jridgewell/remapping": "^2.3.4", "@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/sourcemap-codec": "^1.5.0",
@@ -3930,7 +3919,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -3993,7 +3981,6 @@
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",

View File

@@ -0,0 +1,8 @@
<svg>
<symbol id="panel-right-close" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
<path d="M15 3.5v17M8 9l3 3l-3 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>
</svg>

After

Width:  |  Height:  |  Size: 580 B

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import type { IconId } from '$lib/types/icon-ids.ts';
export let id: IconId;
export let size: number | string ;
export let className: string = '';
$: dimensions = typeof size === 'number' ? `${size}px` : size;
</script>
<svg {...$$restProps}
role="img"
class={className}
aria-hidden="true"
width={dimensions?dimensions:24}
height={dimensions?dimensions:24}
>
<use href={`#${id}`} />
</svg>
<style>
.app-icon {
/* 确保图标与文本基线对齐 */
vertical-align: middle;
/* 防止用户选择图标,提高用户体验 */
user-select: none;
/* 默认显示为行内块级元素 */
display: inline-block;
/* 确保它能响应 CSS 动画 */
transition: color 0.2s, transform 0.2s;
}
</style>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
import SpriteSvg from '$lib/assets/sprite.svg'
</script>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;" >
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html SpriteSvg}
</svg>

View File

@@ -0,0 +1,33 @@
import { writable } from 'svelte/store';
interface SidebarState {
isOpen: boolean;
isExpanded: boolean;
}
export const sidebarStore = writable<SidebarState>({
isOpen: false,
isExpanded: false,
})
/**
* 切换侧边栏打开、隐藏(偏移隐藏)状态
*/
export const toggleSidebar = () => {
sidebarStore.update(state => ({
...state,
isOpen: !state.isOpen,
}));
}
/**
* 切换侧边栏展开状态
*/
export const toggleSidebarOpen = () => {
sidebarStore.update(state => ({
...state,
isExpanded: !state.isExpanded,
}));
}

View File

@@ -0,0 +1,3 @@
export type IconId = {
[key: string]: string;
};

View File

@@ -9,10 +9,10 @@
}; };
</script> </script>
<div class="dropdown dropdown-end"> <div class="dropdown dropdown-center md:dropdown-end ">
<div tabindex="0" role="button" class="rounded btn btn-ghost p-2 overflow-hidden flex items-center gap-2"> <div tabindex="0" role="button" class="rounded hover:bg-base-100 active:bg-base-200 p-2 overflow-hidden flex items-center gap-2">
<ThemePreview themeId={$themeStore} /> <ThemePreview themeId={$themeStore} />
<svg width="12px" height="12px" class="mt-px text-base-content fill-current opacity-60 sm:inline-block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z"></path></svg> <svg width="12px" height="12px" class="mt-px text-base-content size-2 fill-current opacity-60 sm:inline-block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path d="M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z"></path></svg>
</div> </div>
<ul class="dropdown-content shadow bg-base-200 border border-base-content/10 z-[1] rounded-box w-64 max-h-80 overflow-x-hidden overflow-y-auto flex flex-col "> <ul class="dropdown-content shadow bg-base-200 border border-base-content/10 z-[1] rounded-box w-64 max-h-80 overflow-x-hidden overflow-y-auto flex flex-col ">
@@ -30,7 +30,7 @@
handleThemeChange(theme.value); handleThemeChange(theme.value);
} }
}} }}
class="gap-3 w-full flex hover:bg-base-300 active:bg-base-100 p-2 items-center {theme.value === $themeStore ? 'active' : ''}" class="gap-3 w-full flex hover:bg-base-300 active:bg-primary p-2 items-center {theme.value === $themeStore ? 'active' : ''}"
> >
<ThemePreview themeId={theme.value} /> <ThemePreview themeId={theme.value} />
<div class=" ">{theme.name}</div> <div class=" ">{theme.name}</div>

View File

@@ -1,12 +1,53 @@
<script lang="ts"> <script lang="ts">
import './layout.css'; import './layout.css';
import favicon from '$lib/assets/favicon.svg'; import favicon from '$lib/assets/favicon.svg?url';
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 { sidebarStore } from '$lib/stores/sidebarStore.ts';
import Sprite from '$lib/components/icon/Sprite.svelte';
const MD_BREAKPOINT = '(min-width: 768px)';
const handleMediaQueryChange = (event: MediaQueryListEvent) => {
const isCurrentlyDesktop = event.matches;
console.log(isCurrentlyDesktop);
sidebarStore.update((store) => ({
...store,
isOpen: isCurrentlyDesktop
}));
}
let isMounted = $state(false); // 客户端渲染标志
onMount(()=>{
isMounted = true;
const isDesktop = window.matchMedia(MD_BREAKPOINT).matches;
console.log(isDesktop);
sidebarStore.update((store) => ({
...store,
isOpen: isDesktop,
}));
const mediaQuery = window.matchMedia(MD_BREAKPOINT);
mediaQuery.addEventListener('change', handleMediaQueryChange);
return () => {
mediaQuery.removeEventListener('change', handleMediaQueryChange);
}
})
</script> </script>
<svelte:head> <svelte:head>
<link rel="icon" href={favicon} /> <link rel="icon" href={favicon} />
{#if isMounted}
<Sprite />
{/if}
</svelte:head> </svelte:head>
<div data-theme={$themeStore} class="text-base-content"> <div data-theme={$themeStore} class="text-base-content">

View File

@@ -3,5 +3,6 @@
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
redirect(302, `${resolve}/app/dashboard`); redirect(302, `${resolve}/app/dashboard`);
</script> </script>

View File

@@ -2,28 +2,27 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import { authStore } from '$lib/stores/authStore.ts'; import { authStore } from '$lib/stores/authStore.ts';
import { userStore } from '$lib/stores/userStore.ts';
import { authService } from '$lib/api/services/authService.ts'; import { authService } from '$lib/api/services/authService.ts';
import ThemeSelector from '$lib/widget/ThemeSelector.svelte'; import ThemeSelector from '$lib/widget/ThemeSelector.svelte';
import { sidebarStore, toggleSidebarOpen } from '$lib/stores/sidebarStore';
let isExpanded = false;
function toggleSidebar() {
isExpanded = !isExpanded;
}
</script> </script>
<div class=" text-base-content flex h-screen bg-base-300 font-sans overflow-hidden"> <div class="flex h-screen bg-base-300 font-sans overflow-hidden">
<aside class="flex-shrink-0 flex flex-col border-r bg-base-200 border-gray-700/30 <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-200 ease-in-out relative transition-all duration-500 ease-in-out relative
{isExpanded ? 'w-[280px]' : 'w-[72px]'}"> {$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"> <div class="h-16 flex items-center px-4 justify-start">
<button <button
on:click={toggleSidebar} on:click={toggleSidebarOpen}
class="p-2 bg-base-content rounded-full transition-colors" class="p-2 hover:bg-gray-700/50 rounded-full transition-colors"
aria-label="Toggle Menu" aria-label="Toggle Menu"
> >
123 123
@@ -34,9 +33,7 @@
<div>1</div> <div>1</div>
<div>1</div> <div>1</div>
<div>1</div> <div>1</div>
{$userStore.name}
{$userStore.id}
{$userStore.nickname}
</aside> </aside>
<div class="w-full"> <div class="w-full">
@@ -55,9 +52,8 @@
</button> </button>
{:else } {:else }
<div class="flex items-center"> <div class="flex items-center">
<div class="w-24"> <div class="w-24">
<button class="btn btn-primary btn-wide " on:click={()=>{goto(resolve("/auth/login"))}}>登录</button> <button class="btn btn-primary btn-wide" on:click={()=>{goto(resolve("/auth/login"))}}>登录</button>
</div> </div>
</div> </div>
{/if} {/if}

View File

@@ -11,10 +11,6 @@
"sourceMap": true, "sourceMap": true,
"strict": true, "strict": true,
"moduleResolution": "bundler" "moduleResolution": "bundler"
} },
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
} }

View File

@@ -3,5 +3,8 @@ import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
export default defineConfig({ export default defineConfig({
plugins: [tailwindcss(), sveltekit()] plugins: [
tailwindcss(),
sveltekit(),
]
}); });