@zhin.js/client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,899 @@
1
+ <template>
2
+ <div class="layout-container">
3
+ <!-- PC端固定左侧菜单 -->
4
+ <div v-if="!isMobile" class="layout-sidebar-fixed">
5
+ <div class="layout-sidebar-header">
6
+ <div class="layout-brand">
7
+ <i class="pi pi-bolt text-primary text-2xl mr-2"></i>
8
+ <span class="layout-brand-text">Zhin Bot</span>
9
+ </div>
10
+ </div>
11
+
12
+ <div class="layout-sidebar-content">
13
+ <PanelMenu
14
+ :model="processedMenus"
15
+ class="layout-menu"
16
+ @item-click="onMenuClick"
17
+ >
18
+ <template #item="{ item }">
19
+ <router-link
20
+ v-if="item.path && !item.items"
21
+ v-slot="{ href, navigate, isActive }"
22
+ :to="item.path"
23
+ custom
24
+ >
25
+ <a
26
+ v-ripple
27
+ :href="href"
28
+ :class="['layout-menu-item', { 'layout-menu-item-active': isActive }]"
29
+ @click="navigate"
30
+ >
31
+ <i :class="item.icon || 'pi pi-circle'" class="layout-menu-icon"></i>
32
+ <span class="layout-menu-label">{{ item.label }}</span>
33
+ <Badge v-if="item.badge" :value="item.badge" class="ml-auto" />
34
+ </a>
35
+ </router-link>
36
+ <span
37
+ v-else-if="item.items"
38
+ :class="['layout-menu-category', { 'layout-menu-category-expanded': item.expanded }]"
39
+ >
40
+ <i :class="item.icon || 'pi pi-folder'" class="layout-menu-icon"></i>
41
+ <span class="layout-menu-label">{{ item.label }}</span>
42
+ <i class="pi pi-chevron-right layout-menu-arrow"></i>
43
+ </span>
44
+ </template>
45
+ </PanelMenu>
46
+
47
+ <!-- 菜单底部信息 -->
48
+ <div class="layout-sidebar-footer">
49
+ <div class="layout-system-info">
50
+ <div class="text-sm text-color-secondary mb-2">系统信息</div>
51
+ <div class="flex justify-content-between mb-1">
52
+ <span class="text-xs">运行时间</span>
53
+ <span class="text-xs">{{ uptime }}</span>
54
+ </div>
55
+ <div class="flex justify-content-between">
56
+ <span class="text-xs">内存使用</span>
57
+ <span class="text-xs">{{ memoryUsage }}</span>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </div>
63
+
64
+ <!-- 顶部导航栏 -->
65
+ <header class="layout-topbar" :class="{ 'layout-topbar-with-sidebar': !isMobile }">
66
+ <!-- 移动端左侧内容 -->
67
+ <div v-if="isMobile" class="layout-topbar-start">
68
+ <Button
69
+ icon="pi pi-bars"
70
+ class="layout-menu-toggle"
71
+ text
72
+ @click="toggleMobileSidebar"
73
+ :aria-label="mobileSidebarVisible ? '收起菜单' : '展开菜单'"
74
+ />
75
+ <div class="layout-brand">
76
+ <i class="pi pi-bolt text-primary text-2xl mr-2"></i>
77
+ <span class="layout-brand-text">Zhin Bot</span>
78
+ </div>
79
+ </div>
80
+
81
+ <!-- 右侧操作按钮 -->
82
+ <div class="layout-topbar-actions">
83
+ <!-- 全屏切换 -->
84
+ <Button
85
+ :icon="isFullscreen ? 'pi pi-window-minimize' : 'pi pi-window-maximize'"
86
+ text
87
+ rounded
88
+ @click="toggleFullscreen"
89
+ v-tooltip="isFullscreen ? '退出全屏' : '全屏'"
90
+ />
91
+
92
+ <!-- 主题切换 -->
93
+ <Button
94
+ :icon="isDark ? 'pi pi-sun' : 'pi pi-moon'"
95
+ text
96
+ rounded
97
+ @click="toggleTheme"
98
+ v-tooltip="isDark ? '浅色主题' : '深色主题'"
99
+ />
100
+
101
+ <!-- 用户菜单 -->
102
+ <Button
103
+ icon="pi pi-user"
104
+ text
105
+ rounded
106
+ @click="toggleUserMenu"
107
+ aria-haspopup="true"
108
+ aria-controls="user-menu"
109
+ v-tooltip="'用户菜单'"
110
+ />
111
+ <OverlayPanel ref="userMenu" id="user-menu">
112
+ <div class="user-menu">
113
+ <div class="user-info mb-3">
114
+ <i class="pi pi-user-circle text-4xl text-primary"></i>
115
+ <div class="ml-3">
116
+ <div class="font-medium">管理员</div>
117
+ <div class="text-sm text-color-secondary">admin@zhin.bot</div>
118
+ </div>
119
+ </div>
120
+ <Divider />
121
+ <div class="flex flex-column gap-2">
122
+ <Button icon="pi pi-cog" label="系统设置" text class="justify-content-start" />
123
+ <Button icon="pi pi-question-circle" label="帮助文档" text class="justify-content-start" />
124
+ <Button icon="pi pi-sign-out" label="退出登录" text class="justify-content-start" />
125
+ </div>
126
+ </div>
127
+ </OverlayPanel>
128
+ </div>
129
+ </header>
130
+
131
+ <!-- 移动端覆盖式侧边栏 -->
132
+ <Sidebar
133
+ v-if="isMobile"
134
+ v-model:visible="mobileSidebarVisible"
135
+ class="layout-mobile-sidebar"
136
+ position="left"
137
+ >
138
+ <template #header>
139
+ <div class="layout-brand mb-4">
140
+ <i class="pi pi-bolt text-primary text-2xl mr-2"></i>
141
+ <span class="layout-brand-text">Zhin Bot</span>
142
+ </div>
143
+ </template>
144
+
145
+ <!-- 移动端菜单内容 -->
146
+ <div class="layout-mobile-sidebar-content">
147
+ <PanelMenu
148
+ :model="processedMenus"
149
+ class="layout-menu"
150
+ @item-click="onMobileMenuClick"
151
+ >
152
+ <template #item="{ item }">
153
+ <router-link
154
+ v-if="item.path && !item.items"
155
+ v-slot="{ href, navigate, isActive }"
156
+ :to="item.path"
157
+ custom
158
+ >
159
+ <a
160
+ v-ripple
161
+ :href="href"
162
+ :class="['layout-menu-item', { 'layout-menu-item-active': isActive }]"
163
+ @click="navigate"
164
+ >
165
+ <i :class="item.icon || 'pi pi-circle'" class="layout-menu-icon"></i>
166
+ <span class="layout-menu-label">{{ item.label }}</span>
167
+ <Badge v-if="item.badge" :value="item.badge" class="ml-auto" />
168
+ </a>
169
+ </router-link>
170
+ <span
171
+ v-else-if="item.items"
172
+ :class="['layout-menu-category', { 'layout-menu-category-expanded': item.expanded }]"
173
+ >
174
+ <i :class="item.icon || 'pi pi-folder'" class="layout-menu-icon"></i>
175
+ <span class="layout-menu-label">{{ item.label }}</span>
176
+ <i class="pi pi-chevron-right layout-menu-arrow"></i>
177
+ </span>
178
+ </template>
179
+ </PanelMenu>
180
+ </div>
181
+ </Sidebar>
182
+
183
+ <!-- 主内容区域 -->
184
+ <main class="layout-main" :class="{ 'layout-main-with-sidebar': !isMobile }">
185
+ <div class="layout-content">
186
+ <!-- 面包屑导航 -->
187
+ <div class="layout-breadcrumb">
188
+ <Breadcrumb :model="breadcrumbItems" />
189
+ </div>
190
+
191
+ <!-- 页面内容 -->
192
+ <div class="layout-content-wrapper">
193
+ <router-view v-slot="{ Component }">
194
+ <Transition name="page-fade" mode="out-in">
195
+ <component :is="Component" />
196
+ </Transition>
197
+ </router-view>
198
+ </div>
199
+ </div>
200
+ </main>
201
+
202
+ <!-- 返回顶部按钮 -->
203
+ <Transition name="fade">
204
+ <Button
205
+ v-show="showBackToTop"
206
+ icon="pi pi-chevron-up"
207
+ class="layout-back-to-top"
208
+ rounded
209
+ @click="scrollToTop"
210
+ v-tooltip="'返回顶部'"
211
+ />
212
+ </Transition>
213
+ </div>
214
+ </template>
215
+
216
+ <script setup lang="ts">
217
+ import { useCommonStore } from '@zhin.js/client'
218
+ import { computed, ref, onMounted, onUnmounted } from 'vue'
219
+ import { useRouter, useRoute } from 'vue-router'
220
+
221
+ // 组件引用
222
+ const userMenu = ref()
223
+
224
+ // 响应式状态
225
+ const isMobile = ref(false)
226
+ const mobileSidebarVisible = ref(false)
227
+ const isFullscreen = ref(false)
228
+ const isDark = ref(false)
229
+ const showBackToTop = ref(false)
230
+ const uptime = ref('0分钟')
231
+ const memoryUsage = ref('0MB')
232
+
233
+ // 路由相关
234
+ const router = useRouter()
235
+ const route = useRoute()
236
+
237
+ // 获取菜单数据
238
+ const menus = computed(() => {
239
+ return (useCommonStore().store as any).menus || []
240
+ })
241
+
242
+ // 处理菜单数据,转换为PanelMenu格式
243
+ const processedMenus = computed(() => {
244
+ return menus.value.map(menu => ({
245
+ ...menu,
246
+ label: menu.name,
247
+ icon: menu.icon || 'pi pi-circle',
248
+ items: menu.children?.map(child => ({
249
+ ...child,
250
+ label: child.name,
251
+ path: child.path,
252
+ icon: child.icon || 'pi pi-circle'
253
+ }))
254
+ }))
255
+ })
256
+
257
+ // 面包屑导航
258
+ const breadcrumbItems = computed(() => {
259
+ const pathSegments = route.path.split('/').filter(Boolean)
260
+ const items = [{ label: '首页', to: '/' }]
261
+
262
+ let currentPath = ''
263
+ pathSegments.forEach(segment => {
264
+ currentPath += `/${segment}`
265
+ const menu = findMenuByPath(currentPath)
266
+ if (menu) {
267
+ items.push({
268
+ label: menu.name,
269
+ to: currentPath
270
+ })
271
+ }
272
+ })
273
+
274
+ return items
275
+ })
276
+
277
+ // 查找菜单项
278
+ const findMenuByPath = (path: string) => {
279
+ for (const menu of menus.value) {
280
+ if (menu.path === path) return menu
281
+ if (menu.children) {
282
+ for (const child of menu.children) {
283
+ if (child.path === path) return child
284
+ }
285
+ }
286
+ }
287
+ return null
288
+ }
289
+
290
+ // 响应式检测
291
+ const checkMobile = () => {
292
+ const wasMobile = isMobile.value
293
+ isMobile.value = window.innerWidth < 768
294
+
295
+ // 当从移动端切换到桌面端时,关闭移动端侧边栏
296
+ if (wasMobile && !isMobile.value && mobileSidebarVisible.value) {
297
+ mobileSidebarVisible.value = false
298
+ }
299
+ }
300
+
301
+ // 滚动检测
302
+ const checkScroll = () => {
303
+ showBackToTop.value = window.scrollY > 300
304
+ }
305
+
306
+ // 系统信息更新
307
+ const updateSystemInfo = () => {
308
+ // 模拟系统信息更新
309
+ const now = Date.now()
310
+ const startTime = now - Math.random() * 3600000 // 模拟运行时间
311
+ uptime.value = `${Math.floor((now - startTime) / 60000)}分钟`
312
+ memoryUsage.value = `${(Math.random() * 100 + 50).toFixed(1)}MB`
313
+ }
314
+
315
+ // 事件处理
316
+ const toggleMobileSidebar = () => {
317
+ mobileSidebarVisible.value = !mobileSidebarVisible.value
318
+ }
319
+
320
+ const toggleUserMenu = (event: Event) => {
321
+ userMenu.value?.toggle(event)
322
+ }
323
+
324
+ const toggleFullscreen = () => {
325
+ if (!document.fullscreenElement) {
326
+ document.documentElement.requestFullscreen()
327
+ isFullscreen.value = true
328
+ } else {
329
+ document.exitFullscreen()
330
+ isFullscreen.value = false
331
+ }
332
+ }
333
+
334
+ const toggleTheme = () => {
335
+ isDark.value = !isDark.value
336
+ // 这里可以实现主题切换逻辑
337
+ const element = document.documentElement
338
+ if (isDark.value) {
339
+ element.classList.add('dark-theme')
340
+ } else {
341
+ element.classList.remove('dark-theme')
342
+ }
343
+ }
344
+
345
+ const onMenuClick = (event: any) => {
346
+ // PC端菜单点击不需要处理,保持菜单显示
347
+ }
348
+
349
+ const onMobileMenuClick = (event: any) => {
350
+ // 移动端菜单点击后关闭侧边栏
351
+ mobileSidebarVisible.value = false
352
+ }
353
+
354
+ const scrollToTop = () => {
355
+ window.scrollTo({
356
+ top: 0,
357
+ behavior: 'smooth'
358
+ })
359
+ }
360
+
361
+ // 生命周期
362
+ onMounted(() => {
363
+ checkMobile()
364
+ updateSystemInfo()
365
+
366
+ window.addEventListener('resize', checkMobile)
367
+ window.addEventListener('scroll', checkScroll)
368
+
369
+ // 定期更新系统信息
370
+ const timer = setInterval(updateSystemInfo, 30000)
371
+
372
+ onUnmounted(() => {
373
+ window.removeEventListener('resize', checkMobile)
374
+ window.removeEventListener('scroll', checkScroll)
375
+ clearInterval(timer)
376
+ })
377
+ })
378
+ </script>
379
+
380
+ <style scoped>
381
+ /* ============================================================================ */
382
+ /* 布局容器 */
383
+ /* ============================================================================ */
384
+ .layout-container {
385
+ display: flex;
386
+ min-height: 100vh;
387
+ background: var(--surface-ground);
388
+ }
389
+
390
+ /* ============================================================================ */
391
+ /* PC端固定侧边栏 */
392
+ /* ============================================================================ */
393
+ .layout-sidebar-fixed {
394
+ position: fixed;
395
+ top: 0;
396
+ left: 0;
397
+ width: 280px;
398
+ height: 100vh;
399
+ background: var(--surface-card);
400
+ border-right: 1px solid var(--surface-border);
401
+ box-shadow: 2px 0 12px rgba(0, 0, 0, 0.08);
402
+ z-index: 1000;
403
+ display: flex;
404
+ flex-direction: column;
405
+ }
406
+
407
+ .layout-sidebar-header {
408
+ padding: 1.5rem;
409
+ border-bottom: 1px solid var(--surface-border);
410
+ background: var(--surface-50);
411
+ }
412
+
413
+ .layout-sidebar-content {
414
+ flex: 1;
415
+ display: flex;
416
+ flex-direction: column;
417
+ overflow: hidden;
418
+ }
419
+
420
+ /* ============================================================================ */
421
+ /* 顶部导航栏 */
422
+ /* ============================================================================ */
423
+ .layout-topbar {
424
+ position: fixed;
425
+ top: 0;
426
+ left: 0;
427
+ right: 0;
428
+ z-index: 999;
429
+ height: 60px;
430
+ padding: 0 1rem;
431
+ backdrop-filter: blur(10px);
432
+ background: rgba(var(--surface-0-rgb), 0.9);
433
+ border-bottom: 1px solid var(--surface-border);
434
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
435
+ display: flex;
436
+ align-items: center;
437
+ justify-content: space-between;
438
+ }
439
+
440
+ .layout-topbar-with-sidebar {
441
+ left: 280px;
442
+ }
443
+
444
+ .layout-topbar-start {
445
+ display: flex;
446
+ align-items: center;
447
+ }
448
+
449
+ .layout-menu-toggle {
450
+ margin-right: 1rem;
451
+ }
452
+
453
+ .layout-brand {
454
+ display: flex;
455
+ align-items: center;
456
+ font-size: 1.25rem;
457
+ font-weight: 600;
458
+ color: var(--primary-color);
459
+ }
460
+
461
+ .layout-brand-text {
462
+ background: linear-gradient(135deg, var(--primary-color), var(--primary-color-text));
463
+ -webkit-background-clip: text;
464
+ -webkit-text-fill-color: transparent;
465
+ background-clip: text;
466
+ }
467
+
468
+ .layout-topbar-actions {
469
+ display: flex;
470
+ align-items: center;
471
+ gap: 0.5rem;
472
+ }
473
+
474
+ /* 用户菜单样式 */
475
+ .user-menu {
476
+ width: 250px;
477
+ padding: 1rem;
478
+ }
479
+
480
+ .user-info {
481
+ display: flex;
482
+ align-items: center;
483
+ }
484
+
485
+ /* ============================================================================ */
486
+ /* 移动端侧边栏 */
487
+ /* ============================================================================ */
488
+ .layout-mobile-sidebar {
489
+ width: 280px !important;
490
+ }
491
+
492
+ .layout-mobile-sidebar-content {
493
+ height: 100%;
494
+ display: flex;
495
+ flex-direction: column;
496
+ }
497
+
498
+
499
+
500
+ /* 菜单样式 */
501
+ .layout-menu {
502
+ flex: 1;
503
+ border: none;
504
+ border-radius: 0;
505
+ overflow-y: auto;
506
+ }
507
+
508
+ .layout-menu :deep(.p-panelmenu-panel) {
509
+ border: none;
510
+ border-radius: 0;
511
+ }
512
+
513
+ .layout-menu :deep(.p-panelmenu-header) {
514
+ border: none;
515
+ border-radius: 0;
516
+ background: transparent;
517
+ }
518
+
519
+ .layout-menu :deep(.p-panelmenu-content) {
520
+ border: none;
521
+ background: transparent;
522
+ padding: 0;
523
+ }
524
+
525
+ /* 菜单项样式 */
526
+ .layout-menu-item {
527
+ display: flex;
528
+ align-items: center;
529
+ width: 100%;
530
+ padding: 0.75rem 1.5rem;
531
+ color: var(--text-color);
532
+ text-decoration: none;
533
+ transition: all 0.2s ease;
534
+ border-left: 3px solid transparent;
535
+ }
536
+
537
+ .layout-menu-item:hover {
538
+ background: var(--surface-hover);
539
+ color: var(--primary-color);
540
+ border-left-color: var(--primary-color);
541
+ }
542
+
543
+ .layout-menu-item-active {
544
+ background: var(--primary-50);
545
+ color: var(--primary-color);
546
+ border-left-color: var(--primary-color);
547
+ font-weight: 600;
548
+ }
549
+
550
+ .layout-menu-icon {
551
+ width: 1.5rem;
552
+ margin-right: 0.75rem;
553
+ font-size: 1rem;
554
+ }
555
+
556
+ .layout-menu-label {
557
+ flex: 1;
558
+ font-size: 0.875rem;
559
+ }
560
+
561
+ /* 菜单分类样式 */
562
+ .layout-menu-category {
563
+ display: flex;
564
+ align-items: center;
565
+ padding: 0.5rem 1.5rem;
566
+ color: var(--text-color-secondary);
567
+ font-size: 0.75rem;
568
+ font-weight: 600;
569
+ text-transform: uppercase;
570
+ letter-spacing: 0.5px;
571
+ margin-top: 1rem;
572
+ }
573
+
574
+ .layout-menu-arrow {
575
+ margin-left: auto;
576
+ font-size: 0.75rem;
577
+ transition: transform 0.2s ease;
578
+ }
579
+
580
+ .layout-menu-category-expanded .layout-menu-arrow {
581
+ transform: rotate(90deg);
582
+ }
583
+
584
+ /* 侧边栏底部 */
585
+ .layout-sidebar-footer {
586
+ margin-top: auto;
587
+ padding: 1rem;
588
+ border-top: 1px solid var(--surface-border);
589
+ background: var(--surface-50);
590
+ }
591
+
592
+ .layout-system-info {
593
+ padding: 0.75rem;
594
+ background: var(--surface-card);
595
+ border-radius: 8px;
596
+ border: 1px solid var(--surface-border);
597
+ }
598
+
599
+ /* ============================================================================ */
600
+ /* 主内容区域 */
601
+ /* ============================================================================ */
602
+ .layout-main {
603
+ flex: 1;
604
+ margin-top: 60px;
605
+ min-height: calc(100vh - 60px);
606
+ background: var(--surface-ground);
607
+ transition: all 0.3s ease;
608
+ }
609
+
610
+ .layout-main-with-sidebar {
611
+ margin-left: 280px;
612
+ }
613
+
614
+ .layout-content {
615
+ padding: 2rem;
616
+ max-width: 1200px;
617
+ margin: 0 auto;
618
+ }
619
+
620
+ /* 面包屑导航 */
621
+ .layout-breadcrumb {
622
+ margin-bottom: 2rem;
623
+ padding: 1rem;
624
+ background: var(--surface-card);
625
+ border-radius: 12px;
626
+ border: 1px solid var(--surface-border);
627
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
628
+ }
629
+
630
+ /* 内容包装器 */
631
+ .layout-content-wrapper {
632
+ background: var(--surface-card);
633
+ border-radius: 12px;
634
+ border: 1px solid var(--surface-border);
635
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.05);
636
+ overflow: hidden;
637
+ min-height: 400px;
638
+ }
639
+
640
+ /* 返回顶部按钮 */
641
+ .layout-back-to-top {
642
+ position: fixed;
643
+ bottom: 2rem;
644
+ right: 2rem;
645
+ z-index: 1000;
646
+ width: 48px;
647
+ height: 48px;
648
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
649
+ }
650
+
651
+ /* ============================================================================ */
652
+ /* 响应式设计 */
653
+ /* ============================================================================ */
654
+
655
+ /* 平板设备 */
656
+ @media (max-width: 1024px) {
657
+ .layout-content {
658
+ padding: 1.5rem;
659
+ }
660
+
661
+ .layout-sidebar-fixed {
662
+ width: 260px;
663
+ }
664
+
665
+ .layout-topbar-with-sidebar {
666
+ left: 260px;
667
+ }
668
+
669
+ .layout-main-with-sidebar {
670
+ margin-left: 260px;
671
+ }
672
+ }
673
+
674
+ /* 移动设备 */
675
+ @media (max-width: 768px) {
676
+ .layout-container {
677
+ flex-direction: column;
678
+ }
679
+
680
+ .layout-topbar {
681
+ left: 0;
682
+ padding: 0 0.75rem;
683
+ }
684
+
685
+ .layout-brand-text {
686
+ display: none;
687
+ }
688
+
689
+ .layout-main {
690
+ margin-left: 0;
691
+ }
692
+
693
+ .layout-main-with-sidebar {
694
+ margin-left: 0;
695
+ }
696
+
697
+ .layout-content {
698
+ padding: 1rem;
699
+ }
700
+
701
+ .layout-breadcrumb {
702
+ padding: 0.75rem;
703
+ margin-bottom: 1rem;
704
+ }
705
+
706
+ .layout-back-to-top {
707
+ bottom: 1rem;
708
+ right: 1rem;
709
+ width: 40px;
710
+ height: 40px;
711
+ }
712
+
713
+ .layout-topbar-actions {
714
+ gap: 0.25rem;
715
+ }
716
+
717
+ /* 移动端用户菜单 */
718
+ .user-menu {
719
+ width: 220px;
720
+ padding: 0.75rem;
721
+ }
722
+ }
723
+
724
+ /* 小屏手机 */
725
+ @media (max-width: 480px) {
726
+ .layout-content {
727
+ padding: 0.75rem;
728
+ }
729
+
730
+ .layout-breadcrumb {
731
+ padding: 0.5rem;
732
+ }
733
+
734
+ .layout-topbar {
735
+ padding: 0 0.5rem;
736
+ }
737
+
738
+ .user-menu {
739
+ width: 200px;
740
+ }
741
+ }
742
+
743
+ /* ============================================================================ */
744
+ /* 动画效果 */
745
+ /* ============================================================================ */
746
+
747
+ /* 页面切换动画 */
748
+ .page-fade-enter-active,
749
+ .page-fade-leave-active {
750
+ transition: all 0.3s ease;
751
+ }
752
+
753
+ .page-fade-enter-from {
754
+ opacity: 0;
755
+ transform: translateX(10px);
756
+ }
757
+
758
+ .page-fade-leave-to {
759
+ opacity: 0;
760
+ transform: translateX(-10px);
761
+ }
762
+
763
+ .fade-enter-active,
764
+ .fade-leave-active {
765
+ transition: all 0.3s ease;
766
+ }
767
+
768
+ .fade-enter-from {
769
+ opacity: 0;
770
+ transform: scale(0.95);
771
+ }
772
+
773
+ .fade-leave-to {
774
+ opacity: 0;
775
+ transform: scale(0.95);
776
+ }
777
+
778
+ /* 按钮悬停效果 */
779
+ .layout-topbar :deep(.p-button) {
780
+ transition: all 0.2s ease;
781
+ }
782
+
783
+ .layout-topbar :deep(.p-button:hover) {
784
+ transform: translateY(-1px);
785
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
786
+ }
787
+
788
+ /* 侧边栏滚动条样式 */
789
+ .layout-menu::-webkit-scrollbar {
790
+ width: 4px;
791
+ }
792
+
793
+ .layout-menu::-webkit-scrollbar-track {
794
+ background: transparent;
795
+ }
796
+
797
+ .layout-menu::-webkit-scrollbar-thumb {
798
+ background: var(--surface-300);
799
+ border-radius: 2px;
800
+ }
801
+
802
+ .layout-menu::-webkit-scrollbar-thumb:hover {
803
+ background: var(--surface-400);
804
+ }
805
+
806
+ /* ============================================================================ */
807
+ /* 深色主题支持 */
808
+ /* ============================================================================ */
809
+ :root.dark-theme {
810
+ --surface-ground: #0f172a;
811
+ --surface-card: #1e293b;
812
+ --surface-hover: #334155;
813
+ --surface-border: #475569;
814
+ --surface-50: #1a202c;
815
+ --text-color: #e2e8f0;
816
+ --text-color-secondary: #94a3b8;
817
+ --primary-50: rgba(59, 130, 246, 0.1);
818
+ }
819
+
820
+ .dark-theme .layout-topbar {
821
+ background: rgba(15, 23, 42, 0.8);
822
+ }
823
+
824
+ .dark-theme .layout-sidebar {
825
+ background: #1e293b;
826
+ }
827
+
828
+ .dark-theme .layout-content-wrapper {
829
+ background: #1e293b;
830
+ }
831
+
832
+ /* ============================================================================ */
833
+ /* 加载状态和空状态样式 */
834
+ /* ============================================================================ */
835
+ .layout-loading {
836
+ display: flex;
837
+ align-items: center;
838
+ justify-content: center;
839
+ height: 200px;
840
+ color: var(--text-color-secondary);
841
+ }
842
+
843
+ .layout-empty {
844
+ text-align: center;
845
+ padding: 3rem;
846
+ color: var(--text-color-secondary);
847
+ }
848
+
849
+ /* ============================================================================ */
850
+ /* 高级视觉效果 */
851
+ /* ============================================================================ */
852
+
853
+ /* 毛玻璃效果 */
854
+ .layout-topbar::before {
855
+ content: '';
856
+ position: absolute;
857
+ top: 0;
858
+ left: 0;
859
+ right: 0;
860
+ bottom: 0;
861
+ background: inherit;
862
+ backdrop-filter: blur(10px);
863
+ z-index: -1;
864
+ }
865
+
866
+ /* 阴影层次 */
867
+ .layout-content-wrapper {
868
+ box-shadow:
869
+ 0 1px 3px rgba(0, 0, 0, 0.05),
870
+ 0 4px 16px rgba(0, 0, 0, 0.03),
871
+ 0 8px 32px rgba(0, 0, 0, 0.02);
872
+ }
873
+
874
+ /* 聚焦状态 */
875
+ .layout-menu-item:focus-visible {
876
+ outline: 2px solid var(--primary-color);
877
+ outline-offset: -2px;
878
+ border-radius: 4px;
879
+ }
880
+
881
+ /* 高对比度支持 */
882
+ @media (prefers-contrast: high) {
883
+ .layout-sidebar {
884
+ border-right: 2px solid var(--surface-border);
885
+ }
886
+
887
+ .layout-menu-item {
888
+ border-left-width: 4px;
889
+ }
890
+ }
891
+
892
+ /* 减少动画支持 */
893
+ @media (prefers-reduced-motion: reduce) {
894
+ * {
895
+ transition: none !important;
896
+ animation: none !important;
897
+ }
898
+ }
899
+ </style>