xto-fronted 0.4.55 → 0.4.56
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.
- package/dist/components/Layout/MixTopMenu.vue.d.ts +1 -0
- package/dist/components/Layout/TopMenu.vue.d.ts +1 -0
- package/dist/index-BVpqTdd2.js +3132 -0
- package/dist/index-CgofFg1D.js +372 -0
- package/dist/index-DS1wfdxM.js +142 -0
- package/dist/index-YnSLCdjP.js +475 -0
- package/dist/index-s3vGq0ro.js +345 -0
- package/dist/index.es.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Layout/MixTopMenu.vue +198 -0
- package/src/components/Layout/TopMenu.vue +198 -0
|
@@ -18,6 +18,28 @@ const authStore = useAuthStore()
|
|
|
18
18
|
|
|
19
19
|
const activeMenu = computed(() => route.path)
|
|
20
20
|
|
|
21
|
+
// 扁平化菜单用于搜索
|
|
22
|
+
const flattenMenus = (menus: any[], parentTitle = ''): any[] => {
|
|
23
|
+
const result: any[] = []
|
|
24
|
+
menus.forEach(menu => {
|
|
25
|
+
if (menu.children && menu.children.length > 0) {
|
|
26
|
+
result.push(...flattenMenus(menu.children, menu.menuName))
|
|
27
|
+
} else {
|
|
28
|
+
result.push({ ...menu, parentTitle, title: menu.menuName, path: menu.menuUrl })
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
return result
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 搜索结果
|
|
35
|
+
const searchResults = computed(() => {
|
|
36
|
+
if (!searchKeyword.value.trim()) return []
|
|
37
|
+
const flatMenus = flattenMenus(menuStore.menuList)
|
|
38
|
+
return flatMenus.filter(menu =>
|
|
39
|
+
menu.title.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
|
|
21
43
|
// 菜单主题相关
|
|
22
44
|
const menuBgColor = computed(() => appStore.isDark ? '#1d1e1f' : '#fff')
|
|
23
45
|
const menuTextColor = computed(() => appStore.isDark ? '#cfd3dc' : '#303133')
|
|
@@ -29,6 +51,9 @@ const dropdownRef = ref<HTMLElement | null>(null)
|
|
|
29
51
|
const isFullscreen = ref(false)
|
|
30
52
|
const drawerVisible = ref(false)
|
|
31
53
|
const greyMode = ref(false)
|
|
54
|
+
const searchVisible = ref(false)
|
|
55
|
+
const searchKeyword = ref('')
|
|
56
|
+
const searchRef = ref<HTMLElement | null>(null)
|
|
32
57
|
|
|
33
58
|
// 菜单选择
|
|
34
59
|
const handleMenuSelect = (index: string) => {
|
|
@@ -142,6 +167,18 @@ const closeDropdowns = () => {
|
|
|
142
167
|
dropdownVisible.value = false
|
|
143
168
|
}
|
|
144
169
|
|
|
170
|
+
// 隐藏搜索
|
|
171
|
+
const hideSearch = () => {
|
|
172
|
+
searchVisible.value = false
|
|
173
|
+
searchKeyword.value = ''
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 搜索结果点击
|
|
177
|
+
const handleSearchItemClick = (path: string) => {
|
|
178
|
+
router.push(path)
|
|
179
|
+
hideSearch()
|
|
180
|
+
}
|
|
181
|
+
|
|
145
182
|
// 打开设置抽屉
|
|
146
183
|
const openSettingsDrawer = () => {
|
|
147
184
|
drawerVisible.value = true
|
|
@@ -228,17 +265,30 @@ const handleClickOutside = (event: MouseEvent) => {
|
|
|
228
265
|
if (dropdownRef.value && !dropdownRef.value.contains(event.target as Node)) {
|
|
229
266
|
closeDropdowns()
|
|
230
267
|
}
|
|
268
|
+
if (searchRef.value && !searchRef.value.contains(event.target as Node)) {
|
|
269
|
+
hideSearch()
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// 键盘快捷键
|
|
274
|
+
const handleKeydown = (event: KeyboardEvent) => {
|
|
275
|
+
if (event.key === 'Escape') {
|
|
276
|
+
hideSearch()
|
|
277
|
+
closeDropdowns()
|
|
278
|
+
}
|
|
231
279
|
}
|
|
232
280
|
|
|
233
281
|
onMounted(() => {
|
|
234
282
|
document.addEventListener('click', handleClickOutside)
|
|
235
283
|
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
|
284
|
+
document.addEventListener('keydown', handleKeydown)
|
|
236
285
|
greyMode.value = document.documentElement.classList.contains('grey-mode')
|
|
237
286
|
})
|
|
238
287
|
|
|
239
288
|
onUnmounted(() => {
|
|
240
289
|
document.removeEventListener('click', handleClickOutside)
|
|
241
290
|
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
|
291
|
+
document.removeEventListener('keydown', handleKeydown)
|
|
242
292
|
})
|
|
243
293
|
</script>
|
|
244
294
|
|
|
@@ -301,6 +351,41 @@ onUnmounted(() => {
|
|
|
301
351
|
|
|
302
352
|
<!-- 右侧操作区域 -->
|
|
303
353
|
<div class="top-menu__actions">
|
|
354
|
+
<!-- 搜索输入框 -->
|
|
355
|
+
<div class="top-menu__search" ref="searchRef">
|
|
356
|
+
<Icon name="search" :size="14" class="top-menu__search-icon" />
|
|
357
|
+
<input
|
|
358
|
+
v-model="searchKeyword"
|
|
359
|
+
type="text"
|
|
360
|
+
class="top-menu__search-input"
|
|
361
|
+
placeholder="搜索菜单..."
|
|
362
|
+
@focus="searchVisible = true"
|
|
363
|
+
/>
|
|
364
|
+
<!-- 搜索结果下拉 -->
|
|
365
|
+
<Transition name="search-dropdown">
|
|
366
|
+
<div v-if="searchVisible && (searchResults.length > 0 || searchKeyword)" class="top-menu__search-dropdown">
|
|
367
|
+
<div v-if="searchResults.length > 0" class="top-menu__search-results">
|
|
368
|
+
<div
|
|
369
|
+
v-for="item in searchResults"
|
|
370
|
+
:key="item.path"
|
|
371
|
+
class="top-menu__search-item"
|
|
372
|
+
@click="handleSearchItemClick(item.path)"
|
|
373
|
+
>
|
|
374
|
+
<span v-if="item.title !== '首页'" class="top-menu__search-icon-item">
|
|
375
|
+
<Icon v-if="iconExists(getMenuIcon(item.icon))" :name="getMenuIcon(item.icon)" :size="16" />
|
|
376
|
+
<span v-else class="top-menu__search-char">{{ getFirstChar(item.title) }}</span>
|
|
377
|
+
</span>
|
|
378
|
+
<span class="top-menu__search-item-title">{{ item.title }}</span>
|
|
379
|
+
<span v-if="item.parentTitle" class="top-menu__search-item-parent">{{ item.parentTitle }}</span>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
<div v-else class="top-menu__search-empty">
|
|
383
|
+
未找到匹配的菜单
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
</Transition>
|
|
387
|
+
</div>
|
|
388
|
+
|
|
304
389
|
<!-- 全屏切换 -->
|
|
305
390
|
<div class="top-menu__action" @click="toggleFullscreen" :title="isFullscreen ? '退出全屏' : '全屏'">
|
|
306
391
|
<Icon :name="isFullscreen ? 'fullscreen-exit' : 'fullscreen'" :size="16" />
|
|
@@ -571,6 +656,107 @@ onUnmounted(() => {
|
|
|
571
656
|
gap: 8px;
|
|
572
657
|
}
|
|
573
658
|
|
|
659
|
+
&__search {
|
|
660
|
+
position: relative;
|
|
661
|
+
display: flex;
|
|
662
|
+
align-items: center;
|
|
663
|
+
background-color: var(--color-fill-light);
|
|
664
|
+
border-radius: var(--border-radius-base);
|
|
665
|
+
padding: 0 12px;
|
|
666
|
+
height: 32px;
|
|
667
|
+
width: 200px;
|
|
668
|
+
|
|
669
|
+
&-icon {
|
|
670
|
+
color: var(--color-text-secondary);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
&-input {
|
|
674
|
+
flex: 1;
|
|
675
|
+
height: 100%;
|
|
676
|
+
font-size: 14px;
|
|
677
|
+
color: var(--color-text-primary);
|
|
678
|
+
background: transparent;
|
|
679
|
+
border: none;
|
|
680
|
+
outline: none;
|
|
681
|
+
padding-left: 8px;
|
|
682
|
+
|
|
683
|
+
&::placeholder {
|
|
684
|
+
color: var(--color-text-placeholder);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
&-dropdown {
|
|
689
|
+
position: absolute;
|
|
690
|
+
top: calc(100% + 4px);
|
|
691
|
+
left: 0;
|
|
692
|
+
right: 0;
|
|
693
|
+
min-width: 200px;
|
|
694
|
+
max-height: 300px;
|
|
695
|
+
overflow-y: auto;
|
|
696
|
+
background-color: var(--bg-color);
|
|
697
|
+
border-radius: var(--border-radius-base);
|
|
698
|
+
box-shadow: var(--box-shadow);
|
|
699
|
+
z-index: 100;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
&-results {
|
|
703
|
+
padding: 8px 0;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
&-item {
|
|
707
|
+
display: flex;
|
|
708
|
+
align-items: center;
|
|
709
|
+
gap: 8px;
|
|
710
|
+
padding: 8px 12px;
|
|
711
|
+
cursor: pointer;
|
|
712
|
+
transition: background-color 0.2s;
|
|
713
|
+
|
|
714
|
+
&:hover {
|
|
715
|
+
background-color: var(--color-fill);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
&-title {
|
|
719
|
+
font-size: 14px;
|
|
720
|
+
color: var(--color-text-primary);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
&-parent {
|
|
724
|
+
font-size: 12px;
|
|
725
|
+
color: var(--color-text-secondary);
|
|
726
|
+
margin-left: auto;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
&-icon-item {
|
|
731
|
+
display: inline-flex;
|
|
732
|
+
align-items: center;
|
|
733
|
+
justify-content: center;
|
|
734
|
+
width: 16px;
|
|
735
|
+
height: 16px;
|
|
736
|
+
flex-shrink: 0;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
&-char {
|
|
740
|
+
display: inline-flex;
|
|
741
|
+
align-items: center;
|
|
742
|
+
justify-content: center;
|
|
743
|
+
width: 16px;
|
|
744
|
+
height: 16px;
|
|
745
|
+
font-size: 12px;
|
|
746
|
+
font-weight: 600;
|
|
747
|
+
color: var(--color-primary);
|
|
748
|
+
background-color: var(--color-primary-light-8);
|
|
749
|
+
border-radius: 4px;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
&-empty {
|
|
753
|
+
padding: 16px 12px;
|
|
754
|
+
text-align: center;
|
|
755
|
+
color: var(--color-text-secondary);
|
|
756
|
+
font-size: 14px;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
574
760
|
&__action {
|
|
575
761
|
width: 32px;
|
|
576
762
|
height: 32px;
|
|
@@ -945,4 +1131,16 @@ onUnmounted(() => {
|
|
|
945
1131
|
opacity: 0;
|
|
946
1132
|
transform: translateY(-10px);
|
|
947
1133
|
}
|
|
1134
|
+
|
|
1135
|
+
// 搜索下拉动画
|
|
1136
|
+
.search-dropdown-enter-active,
|
|
1137
|
+
.search-dropdown-leave-active {
|
|
1138
|
+
transition: all 0.2s ease;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
.search-dropdown-enter-from,
|
|
1142
|
+
.search-dropdown-leave-to {
|
|
1143
|
+
opacity: 0;
|
|
1144
|
+
transform: translateY(-4px);
|
|
1145
|
+
}
|
|
948
1146
|
</style>
|