hrp-ui-base 1.0.2 → 1.0.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.
Files changed (85) hide show
  1. package/dist/components.cjs +1 -1
  2. package/dist/components.es.js +3595 -1224
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.es.js +86 -69
  5. package/dist/style.css +1 -1
  6. package/package.json +2 -1
  7. package/src/api/notice/NoticeController.ts +73 -0
  8. package/src/api/notice/bo/NoticePageBO.ts +24 -0
  9. package/src/api/notice/bo/NoticeVO.ts +31 -0
  10. package/src/assets/layout/avatar-default.svg +6 -0
  11. package/src/assets/layout/collect-active.svg +7 -0
  12. package/src/assets/layout/collect-default.svg +7 -0
  13. package/src/assets/layout/download-active.svg +8 -0
  14. package/src/assets/layout/download-default.svg +8 -0
  15. package/src/assets/layout/export-dark.svg +7 -0
  16. package/src/assets/layout/export-default.svg +7 -0
  17. package/src/assets/layout/font-active.svg +6 -0
  18. package/src/assets/layout/font-default.svg +6 -0
  19. package/src/assets/layout/help-active.svg +10 -0
  20. package/src/assets/layout/help-default.svg +10 -0
  21. package/src/assets/layout/home-active.svg +6 -0
  22. package/src/assets/layout/home.svg +6 -0
  23. package/src/assets/layout/menu-expand.svg +6 -0
  24. package/src/assets/layout/menu-icon.png +0 -0
  25. package/src/assets/layout/menu-position-active.svg +6 -0
  26. package/src/assets/layout/menu-position-default.svg +6 -0
  27. package/src/assets/layout/message-active.svg +9 -0
  28. package/src/assets/layout/message-default.svg +9 -0
  29. package/src/assets/layout/moon.svg +8 -0
  30. package/src/assets/layout/router-all-dark.svg +11 -0
  31. package/src/assets/layout/router-all-default.svg +17 -0
  32. package/src/assets/layout/search-active.svg +20 -0
  33. package/src/assets/layout/search-default.svg +20 -0
  34. package/src/assets/layout/star-active.svg +1 -0
  35. package/src/assets/layout/star-default.svg +3 -0
  36. package/src/assets/layout/sun-active.svg +6 -0
  37. package/src/assets/layout/sun-default.svg +6 -0
  38. package/src/assets/layout/switch-moon.svg +3 -0
  39. package/src/assets/layout/switch-sun.svg +41 -0
  40. package/src/assets/layout/todo-active.svg +12 -0
  41. package/src/assets/layout/todo-default.svg +12 -0
  42. package/src/assets/layout/user-dark.svg +13 -0
  43. package/src/assets/layout/user-default.svg +13 -0
  44. package/src/components/base-layout/index.vue +198 -0
  45. package/src/components/layout/SysHeader.vue +90 -0
  46. package/src/components/layout/SysHeaderLeft.vue +116 -0
  47. package/src/components/layout/SysHeaderRight.vue +112 -0
  48. package/src/components/layout/SysHeaderTabs.vue +289 -0
  49. package/src/components/layout/components/avatar-component.vue +77 -0
  50. package/src/components/layout/components/dark-component.vue +82 -0
  51. package/src/components/layout/components/download-component.vue +49 -0
  52. package/src/components/layout/components/font-size-component.vue +66 -0
  53. package/src/components/layout/components/menu-position-component.vue +97 -0
  54. package/src/components/layout/components/message-component.vue +64 -0
  55. package/src/components/layout/components/todo-component.vue +55 -0
  56. package/src/components/layout/index.ts +24 -0
  57. package/src/components/layout/message/message-dictionary.ts +27 -0
  58. package/src/components/layout/message/message-icon//345/205/250/351/203/250/351/200/232/347/237/245-/350/223/235.svg +8 -0
  59. package/src/components/layout/message/message-icon//345/205/250/351/203/250/351/200/232/347/237/245-/351/273/221.svg +8 -0
  60. package/src/components/layout/message/message-icon//345/205/250/351/203/250/351/200/232/347/237/245.svg +8 -0
  61. package/src/components/layout/message/message-icon//345/205/254/345/221/212/351/200/232/347/237/245-/350/223/235.svg +12 -0
  62. package/src/components/layout/message/message-icon//345/205/254/345/221/212/351/200/232/347/237/245-/351/273/221.svg +12 -0
  63. package/src/components/layout/message/message-icon//345/205/254/345/221/212/351/200/232/347/237/245.svg +12 -0
  64. package/src/components/layout/message/message-icon//345/256/241/346/211/271/351/200/232/347/237/245-/350/223/235.svg +8 -0
  65. package/src/components/layout/message/message-icon//345/256/241/346/211/271/351/200/232/347/237/245-/351/273/221.svg +8 -0
  66. package/src/components/layout/message/message-icon//345/256/241/346/211/271/351/200/232/347/237/245.svg +8 -0
  67. package/src/components/layout/message/message-icon//345/257/274/345/207/272/351/200/232/347/237/245-/350/223/235.svg +13 -0
  68. package/src/components/layout/message/message-icon//345/257/274/345/207/272/351/200/232/347/237/245-/351/273/221.svg +13 -0
  69. package/src/components/layout/message/message-icon//345/257/274/345/207/272/351/200/232/347/237/245.svg +13 -0
  70. package/src/components/layout/message/message-icon//346/234/252/350/257/273/351/200/232/347/237/245-/350/223/235.svg +11 -0
  71. package/src/components/layout/message/message-icon//346/234/252/350/257/273/351/200/232/347/237/245-/351/273/221.svg +11 -0
  72. package/src/components/layout/message/message-icon//346/234/252/350/257/273/351/200/232/347/237/245.svg +11 -0
  73. package/src/components/layout/message/message-icon//347/251/272/347/212/266/346/200/201.svg +17 -0
  74. package/src/components/layout/message/message-icon//347/263/273/347/273/237/351/200/232/347/237/245-/350/223/235.svg +13 -0
  75. package/src/components/layout/message/message-icon//347/263/273/347/273/237/351/200/232/347/237/245-/351/273/221.svg +13 -0
  76. package/src/components/layout/message/message-icon//347/263/273/347/273/237/351/200/232/347/237/245.svg +13 -0
  77. package/src/components/layout/message/message-notification-drawer.vue +529 -0
  78. package/src/components/layout/personalization-guide-dialog.vue +255 -0
  79. package/src/components/layout/sideMenu-global.scss +115 -0
  80. package/src/components/layout/sideMenu.scss +312 -0
  81. package/src/components/layout/sideMenu.vue +542 -0
  82. package/src/components/layout/sideMenuSonList.vue +185 -0
  83. package/src/components/layout/styles/icon.scss +72 -0
  84. package/src/components/layout/types.ts +98 -0
  85. package/src/components.ts +19 -4
@@ -0,0 +1,542 @@
1
+ <template>
2
+ <div
3
+ id="header-sidebar"
4
+ ref="sidebarRef"
5
+ :class="[
6
+ 'menu-position-' + menuPosition,
7
+ { 'is-left-collapsed': isLeftMenuCollapsed },
8
+ ]"
9
+ @mouseleave="onMenuAreaLeave"
10
+ @click.stop
11
+ >
12
+ <!-- 左箭头 -->
13
+ <div
14
+ v-show="menuPosition === 'top' && showArrows"
15
+ class="scroll-arrow scroll-arrow-left"
16
+ @click="scrollLeft"
17
+ >
18
+ <el-icon><ArrowLeft /></el-icon>
19
+ </div>
20
+
21
+ <!-- 菜单滚动区域 -->
22
+ <div
23
+ class="menu-scroll-wrapper"
24
+ ref="scrollWrapperRef"
25
+ @wheel="handleWheel"
26
+ >
27
+ <div class="menu-scroll-content" ref="scrollContentRef">
28
+ <template
29
+ v-for="(item, index) in sidebarRouters"
30
+ :key="item.menuId + index"
31
+ >
32
+ <div
33
+ :title="isLeftMenuCollapsed ? item.menuName : undefined"
34
+ class="menu-tab-item"
35
+ :class="{ 'is-active': activeIndex === index || isMenuExpanded(index) || collapsedFlyoutIndex === index || (!drawerVisible && selectedIndex === index) }"
36
+ @mouseenter="handleMenuEnter(item, index)"
37
+ @click="handleMenuClick(item, index, $event)"
38
+ >
39
+ <span v-if="menuPosition === 'left'" class="menu-tab-icon">
40
+ <img
41
+ v-if="getMenuIconDisplay(item).imageName"
42
+ :key="getMenuIconDisplay(item).imageName"
43
+ class="menu-tab-icon-img"
44
+ :src="getMenuIconUrl(getMenuIconDisplay(item).imageName)"
45
+ :alt="getMenuIconDisplay(item).imageName"
46
+ @error="handleMenuTabIconError($event, item)"
47
+ />
48
+ <span v-else class="menu-tab-icon-fallback">{{ getMenuShortName(getMenuIconDisplay(item).fallbackName) }}</span>
49
+ </span>
50
+ <span v-if="!isLeftMenuCollapsed" class="menu-tab-name">{{ item.menuName }}</span>
51
+ <el-icon
52
+ v-if="shouldShowMenuArrow(item)"
53
+ class="menu-tab-arrow"
54
+ :class="{ 'is-open': menuPosition === 'left' ? isMenuExpanded(index) : activeIndex === index && drawerVisible }"
55
+ ><ArrowDown /></el-icon>
56
+ </div>
57
+ <div
58
+ v-if="menuPosition === 'left' && !isLeftMenuCollapsed && isMenuExpanded(index) && item.children && item.children.length > 0"
59
+ class="left-inline-submenu"
60
+ >
61
+ <template
62
+ v-for="child in item.children"
63
+ :key="child.menuId || child.url || child.menuName"
64
+ >
65
+ <div
66
+ v-if="child.children && child.children.length > 0"
67
+ class="left-submenu-group"
68
+ >
69
+ <div
70
+ class="left-submenu-group-title"
71
+ :class="{ 'is-active': isActive(child.url) }"
72
+ @click.stop="handleSubMenuClick(child)"
73
+ >
74
+ {{ child.menuName }}
75
+ </div>
76
+ <div
77
+ v-for="grandchild in child.children"
78
+ :key="grandchild.menuId || grandchild.url || grandchild.menuName"
79
+ class="left-submenu-item with-icon"
80
+ :class="{ 'is-active': isActive(grandchild.url) }"
81
+ @click.stop="handleSubMenuClick(grandchild)"
82
+ >
83
+ <span class="left-page-icon">
84
+ <img
85
+ v-if="!failedMenuIconSet.has(grandchild.menuName)"
86
+ class="left-page-icon-img"
87
+ :src="getMenuIconUrl(grandchild.menuName)"
88
+ :alt="grandchild.menuName"
89
+ @error="handleMenuIconError($event, grandchild.menuName)"
90
+ />
91
+ <span v-else class="left-page-icon-fallback">{{ getMenuShortName(grandchild.menuName) }}</span>
92
+ </span>
93
+ <span>{{ grandchild.menuName }}</span>
94
+ </div>
95
+ </div>
96
+ <div
97
+ v-else
98
+ class="left-submenu-item with-icon"
99
+ :class="{ 'is-active': isActive(child.url) }"
100
+ @click.stop="handleSubMenuClick(child)"
101
+ >
102
+ <span class="left-page-icon">
103
+ <img
104
+ v-if="!failedMenuIconSet.has(child.menuName)"
105
+ class="left-page-icon-img"
106
+ :src="getMenuIconUrl(child.menuName)"
107
+ :alt="child.menuName"
108
+ @error="handleMenuIconError($event, child.menuName)"
109
+ />
110
+ <span v-else class="left-page-icon-fallback">{{ getMenuShortName(child.menuName) }}</span>
111
+ </span>
112
+ <span>{{ child.menuName }}</span>
113
+ </div>
114
+ </template>
115
+ </div>
116
+ </template>
117
+ </div>
118
+ </div>
119
+
120
+ <!-- 右箭头 -->
121
+ <div
122
+ v-show="menuPosition === 'top' && showArrows"
123
+ class="scroll-arrow scroll-arrow-right"
124
+ @click="scrollRight"
125
+ >
126
+ <el-icon><ArrowRight /></el-icon>
127
+ </div>
128
+
129
+ <div
130
+ v-if="menuPosition === 'left'"
131
+ class="left-collapse-control"
132
+ @click="toggleLeftMenuCollapse"
133
+ >
134
+ <el-icon>
135
+ <Expand v-if="isLeftMenuCollapsed" />
136
+ <Fold v-else />
137
+ </el-icon>
138
+ <span v-if="!isLeftMenuCollapsed">收起菜单</span>
139
+ </div>
140
+
141
+ <!-- 抽屉内容 -->
142
+ <teleport to="body">
143
+ <transition name="side-drawer-slide">
144
+ <div
145
+ v-if="drawerVisible && menuPosition === 'top' && showSubmenuDrawer"
146
+ class="side-menu-drawer"
147
+ @mouseenter="onDrawerEnter"
148
+ @mouseleave="onDrawerLeave"
149
+ @click.stop
150
+ >
151
+ <sideMenuSonList
152
+ :currentData="currentMenuChildren"
153
+ :currentPath="currentPath"
154
+ @menu-click="onSonMenuClick"
155
+ />
156
+ </div>
157
+ </transition>
158
+ </teleport>
159
+
160
+ <!-- 左侧菜单收起时飞出面板 -->
161
+ <teleport to="body">
162
+ <transition name="left-collapsed-flyout">
163
+ <div
164
+ v-if="menuPosition === 'left' && isLeftMenuCollapsed && collapsedFlyoutMenu"
165
+ class="left-collapsed-flyout"
166
+ :style="{ top: collapsedFlyoutTop + 'px' }"
167
+ @click.stop
168
+ >
169
+ <template
170
+ v-for="child in collapsedFlyoutMenu.children"
171
+ :key="child.menuId || child.url || child.menuName"
172
+ >
173
+ <el-collapse
174
+ v-if="child.children && child.children.length > 0"
175
+ v-model="collapsedPanelNames"
176
+ class="left-collapsed-collapse"
177
+ >
178
+ <el-collapse-item :name="getCollapsedPanelName(child)">
179
+ <template #title>
180
+ <span>{{ child.menuName }}</span>
181
+ </template>
182
+ <div
183
+ v-for="grandchild in child.children"
184
+ :key="grandchild.menuId || grandchild.url || grandchild.menuName"
185
+ class="left-submenu-item with-icon"
186
+ :class="{ 'is-active': isActive(grandchild.url) }"
187
+ @click.stop="handleSubMenuClick(grandchild)"
188
+ >
189
+ <span class="left-page-icon">
190
+ <img
191
+ v-if="!failedMenuIconSet.has(grandchild.menuName)"
192
+ class="left-page-icon-img"
193
+ :src="getMenuIconUrl(grandchild.menuName)"
194
+ :alt="grandchild.menuName"
195
+ @error="handleMenuIconError($event, grandchild.menuName)"
196
+ />
197
+ <span v-else class="left-page-icon-fallback">{{ getMenuShortName(grandchild.menuName) }}</span>
198
+ </span>
199
+ <span>{{ grandchild.menuName }}</span>
200
+ </div>
201
+ </el-collapse-item>
202
+ </el-collapse>
203
+ <div
204
+ v-else
205
+ class="left-submenu-item with-icon"
206
+ :class="{ 'is-active': isActive(child.url) }"
207
+ @click.stop="handleSubMenuClick(child)"
208
+ >
209
+ <span class="left-page-icon">
210
+ <img
211
+ v-if="!failedMenuIconSet.has(child.menuName)"
212
+ class="left-page-icon-img"
213
+ :src="getMenuIconUrl(child.menuName)"
214
+ :alt="child.menuName"
215
+ @error="handleMenuIconError($event, child.menuName)"
216
+ />
217
+ <span v-else class="left-page-icon-fallback">{{ getMenuShortName(child.menuName) }}</span>
218
+ </span>
219
+ <span>{{ child.menuName }}</span>
220
+ </div>
221
+ </template>
222
+ </div>
223
+ </transition>
224
+ </teleport>
225
+ </div>
226
+ </template>
227
+
228
+ <script lang="ts" setup>
229
+ import { computed, ref, onMounted, onUnmounted, nextTick, watch, PropType } from "vue";
230
+ import { ArrowLeft, ArrowRight, ArrowDown, Expand, Fold } from "@element-plus/icons-vue";
231
+ import sideMenuSonList from "./sideMenuSonList.vue";
232
+ import type HomeMenu from "../../api/bms/home/bo/HomeMenu";
233
+
234
+ const props = defineProps({
235
+ menuList: {
236
+ type: Array as PropType<HomeMenu[]>,
237
+ default: () => []
238
+ },
239
+ menuPosition: {
240
+ type: String as PropType<"top" | "left">,
241
+ default: "top"
242
+ },
243
+ showSubmenuDrawer: {
244
+ type: Boolean,
245
+ default: true
246
+ },
247
+ currentPath: {
248
+ type: String,
249
+ default: ''
250
+ },
251
+ navigationBar: {
252
+ type: Boolean,
253
+ default: false
254
+ }
255
+ });
256
+
257
+ const emit = defineEmits<{
258
+ (event: "parent-click", item: HomeMenu, index: number): void;
259
+ (event: "menu-item-click", item: HomeMenu): void;
260
+ (event: "collapse-change", collapsed: boolean): void;
261
+ }>();
262
+
263
+ const sidebarRouters = computed(() => props.menuList);
264
+ const menuPosition = computed(() => props.menuPosition);
265
+ const isLeftMenuCollapsed = computed(
266
+ () => menuPosition.value === "left" && props.navigationBar
267
+ );
268
+
269
+ // 滚动相关
270
+ const scrollWrapperRef = ref<HTMLElement | null>(null);
271
+ const scrollContentRef = ref<HTMLElement | null>(null);
272
+ const sidebarRef = ref<HTMLElement | null>(null);
273
+ const showArrows = ref(false);
274
+ const scrollStep = 200;
275
+
276
+ // 抽屉相关
277
+ const drawerVisible = ref(false);
278
+ const activeIndex = ref<number | null>(null);
279
+ const expandedIndexList = ref<number[]>([]);
280
+ const currentMenuChildren = ref<HomeMenu[]>([]);
281
+ const failedMenuIconSet = ref<Set<string>>(new Set());
282
+ const menuTabIconStateMap = ref<Record<string, "module" | "page" | "text">>({});
283
+ const collapsedFlyoutIndex = ref<number | null>(null);
284
+ const collapsedFlyoutTop = ref(0);
285
+ const collapsedPanelNames = ref<string[]>([]);
286
+
287
+ const collapsedFlyoutMenu = computed(() => {
288
+ if (collapsedFlyoutIndex.value === null) return null;
289
+ return sidebarRouters.value[collapsedFlyoutIndex.value] || null;
290
+ });
291
+
292
+ // 根据当前路由匹配选中的菜单索引
293
+ const selectedIndex = computed(() => {
294
+ const currentPath = props.currentPath;
295
+ for (let i = 0; i < sidebarRouters.value.length; i++) {
296
+ const item = sidebarRouters.value[i];
297
+ if (item.url && item.url === currentPath) return i;
298
+ if (item.children) {
299
+ for (const child of item.children) {
300
+ if (child.url && child.url === currentPath) return i;
301
+ if (child.children) {
302
+ for (const grandchild of child.children) {
303
+ if (grandchild.url && grandchild.url === currentPath) return i;
304
+ }
305
+ }
306
+ }
307
+ }
308
+ }
309
+ return null;
310
+ });
311
+
312
+ function checkOverflow() {
313
+ if (!scrollWrapperRef.value || !scrollContentRef.value) return;
314
+ if (menuPosition.value === "left") { showArrows.value = false; return; }
315
+ showArrows.value = scrollContentRef.value.scrollWidth > scrollWrapperRef.value.clientWidth;
316
+ }
317
+
318
+ function handleWheel(e: WheelEvent) {
319
+ if (!scrollWrapperRef.value || menuPosition.value !== "top") return;
320
+ e.preventDefault();
321
+ scrollWrapperRef.value.scrollLeft += e.deltaY;
322
+ }
323
+
324
+ function scrollLeft() {
325
+ if (!scrollWrapperRef.value) return;
326
+ scrollWrapperRef.value.scrollBy({ left: -scrollStep, behavior: 'smooth' });
327
+ }
328
+
329
+ function scrollRight() {
330
+ if (!scrollWrapperRef.value) return;
331
+ scrollWrapperRef.value.scrollBy({ left: scrollStep, behavior: 'smooth' });
332
+ }
333
+
334
+ let closeTimer: ReturnType<typeof setTimeout> | null = null;
335
+ let openTimer: ReturnType<typeof setTimeout> | null = null;
336
+
337
+ function startCloseTimer() {
338
+ cancelCloseTimer();
339
+ closeTimer = setTimeout(() => { closeDrawer(); }, 150);
340
+ }
341
+ function cancelCloseTimer() { if (closeTimer) { clearTimeout(closeTimer); closeTimer = null; } }
342
+ function cancelOpenTimer() { if (openTimer) { clearTimeout(openTimer); openTimer = null; } }
343
+
344
+ function handleMenuEnter(item: HomeMenu, index: number) {
345
+ if (menuPosition.value === "left" || !props.showSubmenuDrawer) return;
346
+ cancelCloseTimer();
347
+ cancelOpenTimer();
348
+ if (!item.children || item.children.length === 0) { closeDrawer(); return; }
349
+ openTimer = setTimeout(() => {
350
+ activeIndex.value = index;
351
+ currentMenuChildren.value = item.children;
352
+ drawerVisible.value = true;
353
+ }, 500);
354
+ }
355
+
356
+ function onMenuAreaLeave() {
357
+ if (menuPosition.value === "left") return;
358
+ cancelOpenTimer();
359
+ startCloseTimer();
360
+ }
361
+
362
+ function onDrawerEnter() { cancelCloseTimer(); cancelOpenTimer(); }
363
+ function onDrawerLeave() { startCloseTimer(); }
364
+
365
+ function handleMenuClick(item: HomeMenu, index: number, event?: MouseEvent) {
366
+ if (menuPosition.value === "left") {
367
+ closeDrawer();
368
+ if (isLeftMenuCollapsed.value && item.children && item.children.length > 0) {
369
+ openCollapsedFlyout(index, event); return;
370
+ }
371
+ if (item.children && item.children.length > 0) {
372
+ closeCollapsedFlyout(); toggleExpandedIndex(index); return;
373
+ }
374
+ if (item.url) { emit("menu-item-click", item); }
375
+ return;
376
+ }
377
+ if (item.children && item.children.length > 0) {
378
+ closeCollapsedFlyout();
379
+ emit("parent-click", item, index);
380
+ return;
381
+ }
382
+ if (!item.children || item.children.length === 0) {
383
+ if (item.url) { emit("menu-item-click", item); }
384
+ closeDrawer();
385
+ }
386
+ }
387
+
388
+ function handleSubMenuClick(item: HomeMenu) {
389
+ if (!item.url) return;
390
+ emit("menu-item-click", item);
391
+ closeCollapsedFlyout();
392
+ }
393
+
394
+ function onSonMenuClick(menu: HomeMenu) {
395
+ emit("menu-item-click", menu);
396
+ closeDrawer();
397
+ }
398
+
399
+ function isActive(url?: string) { return !!url && props.currentPath === url; }
400
+ function isMenuExpanded(index: number) { return expandedIndexList.value.includes(index); }
401
+ function toggleExpandedIndex(index: number) {
402
+ if (isMenuExpanded(index)) { expandedIndexList.value = expandedIndexList.value.filter((i) => i !== index); return; }
403
+ expandedIndexList.value = [...expandedIndexList.value, index];
404
+ }
405
+ function setMenuExpanded(index: number) {
406
+ if (!isMenuExpanded(index)) expandedIndexList.value = [...expandedIndexList.value, index];
407
+ }
408
+
409
+ function shouldShowMenuArrow(item: HomeMenu) {
410
+ if (isLeftMenuCollapsed.value || !item.children || item.children.length === 0) return false;
411
+ return menuPosition.value === "left" || props.showSubmenuDrawer;
412
+ }
413
+
414
+ function getMenuShortName(menuName?: string) { return (menuName || "-").slice(0, 1); }
415
+ function getMenuIconUrl(menuName?: string) { return `./menu-icon/${menuName || ""}.png`; }
416
+
417
+ function getMenuIconDisplay(item: HomeMenu) {
418
+ const fallbackName = item.menuName;
419
+ if (!item.children || item.children.length === 0) {
420
+ return { imageName: failedMenuIconSet.value.has(item.menuName) ? "" : item.menuName, fallbackName };
421
+ }
422
+ const iconKey = getMenuTabIconKey(item);
423
+ const iconState = menuTabIconStateMap.value[iconKey] || "module";
424
+ if (iconState === "module") return { imageName: item.menuName, fallbackName };
425
+ if (iconState === "page") {
426
+ const firstPageMenuName = findFirstPageMenuName(item.children);
427
+ return { imageName: firstPageMenuName && !failedMenuIconSet.value.has(firstPageMenuName) ? firstPageMenuName : "", fallbackName };
428
+ }
429
+ return { imageName: "", fallbackName };
430
+ }
431
+
432
+ function getMenuTabIconKey(item: HomeMenu) { return String(item.menuId || item.url || item.menuName); }
433
+
434
+ function findFirstPageMenuName(menuList?: HomeMenu[]): string {
435
+ if (!menuList || menuList.length === 0) return "";
436
+ for (const item of menuList) {
437
+ if (item.url) return item.menuName;
438
+ const childMenuName = findFirstPageMenuName(item.children);
439
+ if (childMenuName) return childMenuName;
440
+ }
441
+ return "";
442
+ }
443
+
444
+ function handleMenuIconError(event: Event, iconName?: string) {
445
+ const img = event.target as HTMLImageElement;
446
+ if (img.dataset.ext !== "jpg") { img.dataset.ext = "jpg"; img.src = `./menu-icon/${iconName || ""}.jpg`; return; }
447
+ const nextSet = new Set(failedMenuIconSet.value); nextSet.add(iconName || ""); failedMenuIconSet.value = nextSet;
448
+ }
449
+
450
+ function handleMenuTabIconError(event: Event, item: HomeMenu) {
451
+ const currentIcon = getMenuIconDisplay(item).imageName;
452
+ const img = event.target as HTMLImageElement;
453
+ if (img.dataset.ext !== "jpg") { img.dataset.ext = "jpg"; img.src = `./menu-icon/${currentIcon || ""}.jpg`; return; }
454
+ if (!item.children || item.children.length === 0) { const nextSet = new Set(failedMenuIconSet.value); nextSet.add(item.menuName); failedMenuIconSet.value = nextSet; return; }
455
+ const iconKey = getMenuTabIconKey(item);
456
+ const firstPageMenuName = findFirstPageMenuName(item.children);
457
+ const nextStateMap = { ...menuTabIconStateMap.value };
458
+ if ((nextStateMap[iconKey] || "module") === "module" && firstPageMenuName && firstPageMenuName !== item.menuName && !failedMenuIconSet.value.has(firstPageMenuName)) {
459
+ nextStateMap[iconKey] = "page";
460
+ } else {
461
+ const nextSet = new Set(failedMenuIconSet.value); if (currentIcon) nextSet.add(currentIcon); failedMenuIconSet.value = nextSet; nextStateMap[iconKey] = "text";
462
+ }
463
+ menuTabIconStateMap.value = nextStateMap;
464
+ }
465
+
466
+ function openCollapsedFlyout(index: number, event?: MouseEvent) {
467
+ event?.stopPropagation();
468
+ if (collapsedFlyoutIndex.value === index) { closeCollapsedFlyout(); return; }
469
+ const target = event?.currentTarget as HTMLElement | undefined;
470
+ const rect = target?.getBoundingClientRect();
471
+ collapsedFlyoutTop.value = rect ? Math.min(Math.max(rect.top - 4, 58), Math.max(window.innerHeight - 360, 58)) : 58;
472
+ collapsedFlyoutIndex.value = index;
473
+ collapsedPanelNames.value = getDefaultCollapsedPanels(sidebarRouters.value[index]);
474
+ }
475
+
476
+ function closeCollapsedFlyout() { collapsedFlyoutIndex.value = null; collapsedPanelNames.value = []; }
477
+ function getCollapsedPanelName(item?: HomeMenu) { return String(item?.menuId || item?.url || item?.menuName || "collapsed-menu"); }
478
+
479
+ function getDefaultCollapsedPanels(item?: HomeMenu) {
480
+ if (!item?.children) return [];
481
+ const activeChild = item.children.find((child) => child.children?.length && hasActiveRoute(child));
482
+ if (activeChild) return [getCollapsedPanelName(activeChild)];
483
+ const firstGroup = item.children.find((child) => child.children && child.children.length > 0);
484
+ return firstGroup ? [getCollapsedPanelName(firstGroup)] : [];
485
+ }
486
+
487
+ function hasActiveRoute(item: HomeMenu): boolean {
488
+ if (isActive(item.url)) return true;
489
+ return !!item.children?.some((child) => hasActiveRoute(child));
490
+ }
491
+
492
+ function toggleLeftMenuCollapse() {
493
+ emit("collapse-change", !props.navigationBar);
494
+ }
495
+
496
+ function closeDrawer() { drawerVisible.value = false; activeIndex.value = null; }
497
+
498
+ function handleKeydown(e: KeyboardEvent) {
499
+ if (e.key === 'Escape' && drawerVisible.value) closeDrawer();
500
+ if (e.key === 'Escape' && menuPosition.value === "left") { expandedIndexList.value = []; closeCollapsedFlyout(); }
501
+ }
502
+
503
+ function handleDocumentClick() { closeCollapsedFlyout(); }
504
+
505
+ let resizeObserver: ResizeObserver | null = null;
506
+
507
+ onMounted(() => {
508
+ nextTick(() => { checkOverflow(); });
509
+ if (scrollContentRef.value) {
510
+ resizeObserver = new ResizeObserver(() => { checkOverflow(); });
511
+ resizeObserver.observe(scrollContentRef.value);
512
+ }
513
+ document.addEventListener('keydown', handleKeydown);
514
+ document.addEventListener('click', handleDocumentClick);
515
+ });
516
+
517
+ onUnmounted(() => {
518
+ cancelCloseTimer(); cancelOpenTimer();
519
+ if (resizeObserver) { resizeObserver.disconnect(); resizeObserver = null; }
520
+ document.removeEventListener('keydown', handleKeydown);
521
+ document.removeEventListener('click', handleDocumentClick);
522
+ });
523
+
524
+ watch(() => props.menuList, () => { closeCollapsedFlyout(); nextTick(() => { checkOverflow(); }); }, { deep: true });
525
+
526
+ watch(
527
+ [selectedIndex, menuPosition],
528
+ () => {
529
+ if (menuPosition.value === "left" && selectedIndex.value !== null) setMenuExpanded(selectedIndex.value);
530
+ if (menuPosition.value === "top") { expandedIndexList.value = []; closeCollapsedFlyout(); }
531
+ },
532
+ { immediate: true }
533
+ );
534
+ </script>
535
+
536
+ <style lang="scss" scoped>
537
+ @use './sideMenu.scss' as *;
538
+ </style>
539
+
540
+ <style lang="scss">
541
+ @use './sideMenu-global.scss' as *;
542
+ </style>