xto-fronted 0.3.3 → 0.3.4

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.
@@ -6,6 +6,8 @@ import { useUserStore } from '@/stores/user'
6
6
  import { useAuthStore } from '@/stores/auth'
7
7
  import { useMenuStore } from '@/stores/menu'
8
8
 
9
+ type LayoutMode = 'sidebar' | 'top' | 'mix'
10
+
9
11
  const route = useRoute()
10
12
  const router = useRouter()
11
13
  const appStore = useAppStore()
@@ -13,10 +15,30 @@ const userStore = useUserStore()
13
15
  const authStore = useAuthStore()
14
16
  const menuStore = useMenuStore()
15
17
 
16
- const isCollapsed = computed(() => appStore.isCollapsed)
17
18
  const dropdownVisible = ref(false)
19
+ const layoutDropdownVisible = ref(false)
18
20
  const dropdownRef = ref<HTMLElement | null>(null)
19
21
  const isFullscreen = ref(false)
22
+ const searchVisible = ref(false)
23
+ const searchKeyword = ref('')
24
+ const searchRef = ref<HTMLElement | null>(null)
25
+
26
+ // 布局模式选项
27
+ const layoutOptions: { value: LayoutMode; label: string; icon: string }[] = [
28
+ { value: 'sidebar', label: '左侧菜单', icon: '📋' },
29
+ { value: 'top', label: '顶部菜单', icon: '☰' },
30
+ { value: 'mix', label: '混合菜单', icon: '⚡' }
31
+ ]
32
+
33
+ // 主题色选项
34
+ const colorOptions = [
35
+ { value: '#409eff', label: '默认蓝' },
36
+ { value: '#1890ff', label: '科技蓝' },
37
+ { value: '#52c41a', label: '极光绿' },
38
+ { value: '#faad14', label: '日落橙' },
39
+ { value: '#f5222d', label: '薄暮红' },
40
+ { value: '#722ed1', label: '酱紫' }
41
+ ]
20
42
 
21
43
  // 面包屑
22
44
  const breadcrumbs = computed(() => {
@@ -27,6 +49,31 @@ const breadcrumbs = computed(() => {
27
49
  }))
28
50
  })
29
51
 
52
+ // 扁平化菜单用于搜索
53
+ const flattenMenus = (menus: any[], parentTitle = ''): any[] => {
54
+ const result: any[] = []
55
+ menus.forEach(menu => {
56
+ if (menu.children && menu.children.length > 0) {
57
+ result.push(...flattenMenus(menu.children, menu.title))
58
+ } else {
59
+ result.push({ ...menu, parentTitle })
60
+ }
61
+ })
62
+ return result
63
+ }
64
+
65
+ // 搜索结果
66
+ const searchResults = computed(() => {
67
+ if (!searchKeyword.value.trim()) return []
68
+ const flatMenus = flattenMenus(menuStore.menuList)
69
+ return flatMenus.filter(menu =>
70
+ menu.title.toLowerCase().includes(searchKeyword.value.toLowerCase())
71
+ )
72
+ })
73
+
74
+ // 当前布局模式
75
+ const currentLayout = computed(() => appStore.layout)
76
+
30
77
  // 切换折叠
31
78
  const toggleCollapse = () => {
32
79
  appStore.toggleCollapse()
@@ -37,6 +84,22 @@ const toggleTheme = () => {
37
84
  appStore.toggleTheme()
38
85
  }
39
86
 
87
+ // 切换布局模式
88
+ const handleLayoutChange = (mode: LayoutMode) => {
89
+ appStore.setLayout(mode)
90
+ layoutDropdownVisible.value = false
91
+ }
92
+
93
+ // 切换灰色模式
94
+ const toggleGreyMode = () => {
95
+ const html = document.documentElement
96
+ if (html.classList.contains('grey-mode')) {
97
+ html.classList.remove('grey-mode')
98
+ } else {
99
+ html.classList.add('grey-mode')
100
+ }
101
+ }
102
+
40
103
  // 切换全屏
41
104
  const toggleFullscreen = () => {
42
105
  if (!document.fullscreenElement) {
@@ -54,30 +117,58 @@ const handleFullscreenChange = () => {
54
117
  // 切换下拉菜单
55
118
  const toggleDropdown = () => {
56
119
  dropdownVisible.value = !dropdownVisible.value
120
+ layoutDropdownVisible.value = false
121
+ }
122
+
123
+ // 切换布局下拉菜单
124
+ const toggleLayoutDropdown = () => {
125
+ layoutDropdownVisible.value = !layoutDropdownVisible.value
126
+ dropdownVisible.value = false
57
127
  }
58
128
 
59
129
  // 关闭下拉菜单
60
- const closeDropdown = () => {
130
+ const closeDropdowns = () => {
61
131
  dropdownVisible.value = false
132
+ layoutDropdownVisible.value = false
133
+ }
134
+
135
+ // 显示搜索
136
+ const showSearch = () => {
137
+ searchVisible.value = true
138
+ }
139
+
140
+ // 隐藏搜索
141
+ const hideSearch = () => {
142
+ searchVisible.value = false
143
+ searchKeyword.value = ''
144
+ }
145
+
146
+ // 搜索结果点击
147
+ const handleSearchItemClick = (path: string) => {
148
+ router.push(path)
149
+ hideSearch()
150
+ }
151
+
152
+ // 设置主题色
153
+ const handleColorChange = (color: string) => {
154
+ appStore.setPrimaryColor(color)
62
155
  }
63
156
 
64
157
  // 个人信息
65
158
  const handleProfile = () => {
66
- closeDropdown()
67
- // TODO: 跳转到个人信息页面
68
- alert('个人信息功能开发中...')
159
+ closeDropdowns()
160
+ router.push('/profile')
69
161
  }
70
162
 
71
163
  // 修改密码
72
164
  const handleChangePassword = () => {
73
- closeDropdown()
74
- // TODO: 打开修改密码弹窗
75
- alert('修改密码功能开发中...')
165
+ closeDropdowns()
166
+ router.push('/change-password')
76
167
  }
77
168
 
78
169
  // 退出登录
79
170
  const handleLogout = () => {
80
- closeDropdown()
171
+ closeDropdowns()
81
172
  authStore.logout()
82
173
  userStore.clearUserInfo()
83
174
  menuStore.clearMenu()
@@ -87,18 +178,37 @@ const handleLogout = () => {
87
178
  // 点击外部关闭下拉菜单
88
179
  const handleClickOutside = (event: MouseEvent) => {
89
180
  if (dropdownRef.value && !dropdownRef.value.contains(event.target as Node)) {
90
- closeDropdown()
181
+ closeDropdowns()
182
+ }
183
+ if (searchRef.value && !searchRef.value.contains(event.target as Node)) {
184
+ hideSearch()
185
+ }
186
+ }
187
+
188
+ // 键盘快捷键
189
+ const handleKeydown = (event: KeyboardEvent) => {
190
+ if (event.key === 'Escape') {
191
+ hideSearch()
192
+ closeDropdowns()
193
+ }
194
+ // Ctrl+K 打开搜索
195
+ if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
196
+ event.preventDefault()
197
+ showSearch()
91
198
  }
92
199
  }
93
200
 
94
201
  onMounted(() => {
95
202
  document.addEventListener('click', handleClickOutside)
96
203
  document.addEventListener('fullscreenchange', handleFullscreenChange)
204
+ document.addEventListener('keydown', handleKeydown)
205
+ appStore.initTheme()
97
206
  })
98
207
 
99
208
  onUnmounted(() => {
100
209
  document.removeEventListener('click', handleClickOutside)
101
210
  document.removeEventListener('fullscreenchange', handleFullscreenChange)
211
+ document.removeEventListener('keydown', handleKeydown)
102
212
  })
103
213
  </script>
104
214
 
@@ -106,13 +216,18 @@ onUnmounted(() => {
106
216
  <div class="header">
107
217
  <!-- 左侧 -->
108
218
  <div class="header__left">
219
+ <!-- 折叠按钮 -->
109
220
  <div class="header__collapse" @click="toggleCollapse">
110
- <span v-if="isCollapsed">☰</span>
111
- <span v-else>☰</span>
221
+ <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
222
+ <path v-if="appStore.isCollapsed" d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
223
+ <path v-else d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
224
+ </svg>
112
225
  </div>
113
- <div class="header__breadcrumb">
226
+
227
+ <!-- 面包屑 -->
228
+ <div v-if="appStore.showBreadcrumb" class="header__breadcrumb">
114
229
  <span v-for="(item, index) in breadcrumbs" :key="item.path">
115
- <span v-if="index > 0"> / </span>
230
+ <span v-if="index > 0" class="breadcrumb-separator">/</span>
116
231
  <span :class="{ 'is-current': index === breadcrumbs.length - 1 }">
117
232
  {{ item.title }}
118
233
  </span>
@@ -122,8 +237,15 @@ onUnmounted(() => {
122
237
 
123
238
  <!-- 右侧 -->
124
239
  <div class="header__right">
240
+ <!-- 搜索按钮 -->
241
+ <div class="header__action" @click="showSearch" title="搜索 (Ctrl+K)">
242
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
243
+ <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
244
+ </svg>
245
+ </div>
246
+
125
247
  <!-- 全屏切换 -->
126
- <div class="header__action" @click="toggleFullscreen" :title="isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'">
248
+ <div class="header__action" @click="toggleFullscreen" :title="isFullscreen ? '退出全屏' : '全屏'">
127
249
  <svg v-if="isFullscreen" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
128
250
  <path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
129
251
  </svg>
@@ -132,10 +254,58 @@ onUnmounted(() => {
132
254
  </svg>
133
255
  </div>
134
256
 
257
+ <!-- 布局切换 -->
258
+ <div class="header__action header__layout" ref="dropdownRef">
259
+ <div class="header__action-trigger" @click.stop="toggleLayoutDropdown">
260
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
261
+ <path d="M3 3h18v18H3V3zm2 2v6h14V5H5zm0 8v6h6v-6H5zm8 0v6h6v-6h-6z"/>
262
+ </svg>
263
+ </div>
264
+
265
+ <!-- 布局下拉菜单 -->
266
+ <Transition name="dropdown">
267
+ <div v-if="layoutDropdownVisible" class="header__layout-dropdown">
268
+ <div class="header__layout-title">布局模式</div>
269
+ <div class="header__layout-options">
270
+ <div
271
+ v-for="option in layoutOptions"
272
+ :key="option.value"
273
+ :class="['header__layout-option', { 'is-active': currentLayout === option.value }]"
274
+ @click="handleLayoutChange(option.value)"
275
+ >
276
+ <span class="layout-icon">{{ option.icon }}</span>
277
+ <span>{{ option.label }}</span>
278
+ </div>
279
+ </div>
280
+ <div class="header__layout-divider"></div>
281
+ <div class="header__layout-title">主题色</div>
282
+ <div class="header__color-options">
283
+ <div
284
+ v-for="color in colorOptions"
285
+ :key="color.value"
286
+ :class="['header__color-option', { 'is-active': appStore.primaryColor === color.value }]"
287
+ :style="{ backgroundColor: color.value }"
288
+ :title="color.label"
289
+ @click="handleColorChange(color.value)"
290
+ ></div>
291
+ </div>
292
+ <div class="header__layout-divider"></div>
293
+ <div class="header__layout-item" @click="toggleGreyMode">
294
+ <span class="layout-icon">🌫️</span>
295
+ <span>灰色模式</span>
296
+ </div>
297
+ </div>
298
+ </Transition>
299
+ </div>
300
+
135
301
  <!-- 主题切换 -->
136
302
  <div class="header__action" @click="toggleTheme" title="切换主题">
137
- <span v-if="appStore.isDark">🌙</span>
138
- <span v-else>☀️</span>
303
+ <svg v-if="appStore.isDark" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
304
+ <path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-3.03 0-5.5-2.47-5.5-5.5 0-1.82.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
305
+ </svg>
306
+ <svg v-else viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
307
+ <path d="M6.76 4.84l-1.4-1.4-1.41 1.41 1.4 1.4 1.41-1.41zm10.49 10.49l-1.4-1.4-1.41 1.41 1.4 1.4 1.41-1.41zM12 4c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7zm1-9h-2v4h2v-4zm0-6h-2v2h2V5zM4.84 17.24l-1.41 1.41 1.4 1.4 1.41-1.41-1.4-1.4z"/>
308
+ </svg>
139
309
  </div>
140
310
 
141
311
  <!-- 用户头像 -->
@@ -157,7 +327,7 @@ onUnmounted(() => {
157
327
  </div>
158
328
  <div class="header__dropdown-info">
159
329
  <div class="header__dropdown-name">{{ userStore.nickname }}</div>
160
- <div class="header__dropdown-role">{{ userStore.roles.join(', ') }}</div>
330
+ <div class="header__dropdown-role">{{ userStore.roles?.join(', ') }}</div>
161
331
  </div>
162
332
  </div>
163
333
  <div class="header__dropdown-divider"></div>
@@ -180,6 +350,45 @@ onUnmounted(() => {
180
350
  </Transition>
181
351
  </div>
182
352
  </div>
353
+
354
+ <!-- 全局搜索弹窗 -->
355
+ <Transition name="search">
356
+ <div v-if="searchVisible" class="header__search-modal" ref="searchRef">
357
+ <div class="search-container">
358
+ <div class="search-input-wrapper">
359
+ <svg class="search-icon" viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
360
+ <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
361
+ </svg>
362
+ <input
363
+ v-model="searchKeyword"
364
+ type="text"
365
+ class="search-input"
366
+ placeholder="搜索菜单..."
367
+ autofocus
368
+ @keyup.enter="searchResults[0] && handleSearchItemClick(searchResults[0].path)"
369
+ />
370
+ <span class="search-shortcut">ESC 关闭</span>
371
+ </div>
372
+ <div v-if="searchResults.length > 0" class="search-results">
373
+ <div
374
+ v-for="(item, index) in searchResults"
375
+ :key="item.path"
376
+ :class="['search-result-item', { 'is-first': index === 0 }]"
377
+ @click="handleSearchItemClick(item.path)"
378
+ >
379
+ <span class="search-result-icon">{{ item.icon || '📄' }}</span>
380
+ <div class="search-result-info">
381
+ <span class="search-result-title">{{ item.title }}</span>
382
+ <span v-if="item.parentTitle" class="search-result-parent">{{ item.parentTitle }}</span>
383
+ </div>
384
+ </div>
385
+ </div>
386
+ <div v-else-if="searchKeyword" class="search-empty">
387
+ 未找到匹配的菜单
388
+ </div>
389
+ </div>
390
+ </div>
391
+ </Transition>
183
392
  </div>
184
393
  </template>
185
394
 
@@ -190,6 +399,8 @@ onUnmounted(() => {
190
399
  justify-content: space-between;
191
400
  padding: 0 20px;
192
401
  height: 100%;
402
+ background-color: var(--bg-color);
403
+ border-bottom: 1px solid var(--color-border-lighter);
193
404
 
194
405
  &__left {
195
406
  display: flex;
@@ -204,8 +415,8 @@ onUnmounted(() => {
204
415
  align-items: center;
205
416
  justify-content: center;
206
417
  cursor: pointer;
207
- font-size: 18px;
208
418
  color: var(--color-text-regular);
419
+ transition: color 0.2s;
209
420
 
210
421
  &:hover {
211
422
  color: var(--color-primary);
@@ -216,6 +427,11 @@ onUnmounted(() => {
216
427
  font-size: 14px;
217
428
  color: var(--color-text-secondary);
218
429
 
430
+ .breadcrumb-separator {
431
+ margin: 0 8px;
432
+ color: var(--color-text-placeholder);
433
+ }
434
+
219
435
  .is-current {
220
436
  color: var(--color-text-primary);
221
437
  font-weight: 500;
@@ -225,7 +441,7 @@ onUnmounted(() => {
225
441
  &__right {
226
442
  display: flex;
227
443
  align-items: center;
228
- gap: 15px;
444
+ gap: 8px;
229
445
  }
230
446
 
231
447
  &__action {
@@ -236,7 +452,107 @@ onUnmounted(() => {
236
452
  justify-content: center;
237
453
  cursor: pointer;
238
454
  border-radius: var(--border-radius-base);
239
- font-size: 16px;
455
+ color: var(--color-text-regular);
456
+ transition: all 0.2s;
457
+
458
+ &:hover {
459
+ background-color: var(--color-fill);
460
+ color: var(--color-primary);
461
+ }
462
+ }
463
+
464
+ &__layout {
465
+ position: relative;
466
+ }
467
+
468
+ &__layout-dropdown {
469
+ position: absolute;
470
+ top: calc(100% + 8px);
471
+ right: 0;
472
+ min-width: 200px;
473
+ background-color: var(--bg-color);
474
+ border-radius: var(--border-radius-base);
475
+ box-shadow: var(--box-shadow);
476
+ padding: 12px;
477
+ z-index: 100;
478
+ }
479
+
480
+ &__layout-title {
481
+ font-size: 12px;
482
+ color: var(--color-text-secondary);
483
+ margin-bottom: 8px;
484
+ padding-left: 4px;
485
+ }
486
+
487
+ &__layout-options {
488
+ display: flex;
489
+ flex-direction: column;
490
+ gap: 4px;
491
+ }
492
+
493
+ &__layout-option {
494
+ display: flex;
495
+ align-items: center;
496
+ gap: 8px;
497
+ padding: 8px 12px;
498
+ cursor: pointer;
499
+ border-radius: var(--border-radius-base);
500
+ font-size: 14px;
501
+ color: var(--color-text-regular);
502
+ transition: all 0.2s;
503
+
504
+ &:hover {
505
+ background-color: var(--color-fill);
506
+ }
507
+
508
+ &.is-active {
509
+ color: var(--color-primary);
510
+ background-color: var(--color-primary-light-9);
511
+ }
512
+
513
+ .layout-icon {
514
+ font-size: 16px;
515
+ }
516
+ }
517
+
518
+ &__color-options {
519
+ display: flex;
520
+ gap: 8px;
521
+ padding: 8px 0;
522
+ }
523
+
524
+ &__color-option {
525
+ width: 20px;
526
+ height: 20px;
527
+ border-radius: 50%;
528
+ cursor: pointer;
529
+ transition: transform 0.2s;
530
+
531
+ &:hover {
532
+ transform: scale(1.2);
533
+ }
534
+
535
+ &.is-active {
536
+ box-shadow: 0 0 0 2px var(--bg-color), 0 0 0 4px currentColor;
537
+ }
538
+ }
539
+
540
+ &__layout-divider {
541
+ height: 1px;
542
+ background-color: var(--color-border-lighter);
543
+ margin: 8px 0;
544
+ }
545
+
546
+ &__layout-item {
547
+ display: flex;
548
+ align-items: center;
549
+ gap: 8px;
550
+ padding: 8px 12px;
551
+ cursor: pointer;
552
+ border-radius: var(--border-radius-base);
553
+ font-size: 14px;
554
+ color: var(--color-text-regular);
555
+ transition: all 0.2s;
240
556
 
241
557
  &:hover {
242
558
  background-color: var(--color-fill);
@@ -245,6 +561,7 @@ onUnmounted(() => {
245
561
 
246
562
  &__user {
247
563
  position: relative;
564
+ margin-left: 8px;
248
565
 
249
566
  &-trigger {
250
567
  display: flex;
@@ -263,6 +580,10 @@ onUnmounted(() => {
263
580
  &-name {
264
581
  font-size: 14px;
265
582
  color: var(--color-text-primary);
583
+ max-width: 100px;
584
+ overflow: hidden;
585
+ text-overflow: ellipsis;
586
+ white-space: nowrap;
266
587
  }
267
588
 
268
589
  &-arrow {
@@ -374,6 +695,114 @@ onUnmounted(() => {
374
695
  font-size: 16px;
375
696
  }
376
697
  }
698
+
699
+ &__search-modal {
700
+ position: fixed;
701
+ top: 0;
702
+ left: 0;
703
+ right: 0;
704
+ bottom: 0;
705
+ background-color: rgba(0, 0, 0, 0.5);
706
+ display: flex;
707
+ align-items: flex-start;
708
+ justify-content: center;
709
+ padding-top: 100px;
710
+ z-index: 200;
711
+ }
712
+ }
713
+
714
+ // 搜索容器
715
+ .search-container {
716
+ width: 600px;
717
+ max-width: 90vw;
718
+ background-color: var(--bg-color);
719
+ border-radius: var(--border-radius-large);
720
+ box-shadow: var(--box-shadow-dark);
721
+ overflow: hidden;
722
+ }
723
+
724
+ .search-input-wrapper {
725
+ display: flex;
726
+ align-items: center;
727
+ padding: 16px 20px;
728
+ border-bottom: 1px solid var(--color-border-lighter);
729
+
730
+ .search-icon {
731
+ color: var(--color-text-secondary);
732
+ margin-right: 12px;
733
+ }
734
+
735
+ .search-input {
736
+ flex: 1;
737
+ font-size: 16px;
738
+ color: var(--color-text-primary);
739
+ background: transparent;
740
+ border: none;
741
+ outline: none;
742
+
743
+ &::placeholder {
744
+ color: var(--color-text-placeholder);
745
+ }
746
+ }
747
+
748
+ .search-shortcut {
749
+ font-size: 12px;
750
+ color: var(--color-text-secondary);
751
+ padding: 4px 8px;
752
+ background-color: var(--color-fill);
753
+ border-radius: var(--border-radius-base);
754
+ }
755
+ }
756
+
757
+ .search-results {
758
+ max-height: 400px;
759
+ overflow-y: auto;
760
+ padding: 8px 0;
761
+ }
762
+
763
+ .search-result-item {
764
+ display: flex;
765
+ align-items: center;
766
+ gap: 12px;
767
+ padding: 12px 20px;
768
+ cursor: pointer;
769
+ transition: background-color 0.2s;
770
+
771
+ &:hover {
772
+ background-color: var(--color-fill);
773
+ }
774
+
775
+ &.is-first {
776
+ background-color: var(--color-primary-light-9);
777
+ }
778
+
779
+ .search-result-icon {
780
+ font-size: 20px;
781
+ }
782
+
783
+ .search-result-info {
784
+ flex: 1;
785
+ display: flex;
786
+ flex-direction: column;
787
+ gap: 2px;
788
+ }
789
+
790
+ .search-result-title {
791
+ font-size: 14px;
792
+ color: var(--color-text-primary);
793
+ }
794
+
795
+ .search-result-parent {
796
+ font-size: 12px;
797
+ color: var(--color-text-secondary);
798
+ }
799
+ }
800
+
801
+ .search-empty {
802
+ padding: 40px 20px;
803
+ text-align: center;
804
+ color: var(--color-text-secondary);
805
+ font-size: 14px;
377
806
  }
378
807
 
379
808
  // 下拉动画
@@ -387,4 +816,20 @@ onUnmounted(() => {
387
816
  opacity: 0;
388
817
  transform: translateY(-10px);
389
818
  }
819
+
820
+ // 搜索弹窗动画
821
+ .search-enter-active,
822
+ .search-leave-active {
823
+ transition: all 0.2s ease;
824
+ }
825
+
826
+ .search-enter-from,
827
+ .search-leave-to {
828
+ opacity: 0;
829
+ }
830
+
831
+ // 灰色模式
832
+ :root.grey-mode {
833
+ filter: grayscale(100%);
834
+ }
390
835
  </style>