yuang-framework-ui-pc 1.1.2 → 1.1.3

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.
@@ -1,1277 +0,0 @@
1
- <!-- 高级布局 -->
2
- <template>
3
- <EleAdminLayout
4
- ref="layoutRef"
5
- :height="height"
6
- :headerMenus="layoutHeaders"
7
- :headerActive="navActive"
8
- :sidebarMenus="layoutSidebars"
9
- :sidebarActive="sideActive"
10
- :sideboxMenus="layoutSideboxs"
11
- :sideboxActive="isBoxSide || (collapse && !mobile) ? sideActive : boxActive"
12
- :tabs="layoutTabs"
13
- :tabActive="tabActive"
14
- :levels="layoutLevels"
15
- :collapse="collapse"
16
- :compact="compact"
17
- :maximized="routeMaximized"
18
- :tabBar="routeTabBar"
19
- :breadcrumb="breadcrumbProps"
20
- :backTop="backTopProps"
21
- :headerMenuProps="navMenuProps"
22
- :sidebarMenuProps="sideMenuProps"
23
- :sideboxMenuProps="boxMenuProps"
24
- :layout="routeLayout"
25
- :sidebarLayout="routeSideType"
26
- :headerStyle="headerStyle"
27
- :sidebarStyle="sidebarStyle"
28
- :tabStyle="tabStyle"
29
- :fixedHeader="fixedHeader"
30
- :fixedSidebar="fixedSidebar"
31
- :fixedBody="fixedBody"
32
- :logoInHeader="logoInHeader"
33
- :fixedHome="fixedHome"
34
- :homePath="homeMenuPath"
35
- :isHome="isHome"
36
- :tabContextMenu="tabContextMenu"
37
- :tabContextMenus="tabContextMenus"
38
- :tabSortable="tabSortable"
39
- :headerTitleSlot="headerTitleSlot"
40
- :headerIconSlot="headerIconSlot"
41
- :sidebarTitleSlot="sidebarTitleSlot"
42
- :sidebarIconSlot="sidebarIconSlot"
43
- :sideboxTitleSlot="sideboxTitleSlot"
44
- :sideboxIconSlot="sideboxIconSlot"
45
- :headerCustomStyle="headerCustomStyle"
46
- :sidebarCustomStyle="sidebarCustomStyle"
47
- :sideboxCustomStyle="sideboxCustomStyle"
48
- :tabsCustomStyle="tabsCustomStyle"
49
- :contentCustomStyle="contentCustomStyle"
50
- :logoStyle="logoStyle"
51
- :logoTitleStyle="logoTitleStyle"
52
- :headerMenusStyle="headerMenusStyle"
53
- :sidebarMenusStyle="sidebarMenusStyle"
54
- :sideboxMenusStyle="sideboxMenusStyle"
55
- :mobile="mobile"
56
- :class="['ele-pro-layout', { 'ele-admin-limited': !fluid }]"
57
- @update:collapse="updateCollapse"
58
- @logoClick="handleLogoClick"
59
- @headMenuOpen="handleHeadMenuOpen"
60
- @headMenuClose="handleHeadMenuClose"
61
- @headMenuItemClick="handleHeadMenuItemClick"
62
- @headMenuItemMouseenter="handleHeadMenuItemMouseenter"
63
- @headMouseenter="handleHeadMouseenter"
64
- @headMouseleave="handleHeadMouseleave"
65
- @boxMenuItemClick="handleBoxMenuItemClick"
66
- @boxMenuItemMouseenter="handleBoxMenuItemMouseenter"
67
- @boxMouseenter="handleBoxMouseEnter"
68
- @boxMouseleave="handleBoxMouseLeave"
69
- @sideMenuOpen="handleSideMenuOpen"
70
- @sideMenuClose="handleSideMenuClose"
71
- @sideMenuItemClick="handleSideMenuItemClick"
72
- @sideMouseenter="handleSideMouseEnter"
73
- @sideMouseleave="handleSideMouseLeave"
74
- @tabClick="handleTabClick"
75
- @tabRemove="handleTabRemove"
76
- @tabContextMenu="handleTabContextMenu"
77
- @tabSortChange="handleTabSortChange"
78
- >
79
- <slot></slot>
80
- <slot v-if="!hideFooter" name="footer"></slot>
81
- <template #body>
82
- <ProIframe
83
- v-if="tabBar && keepAlive"
84
- :keepAlive="keepAlive"
85
- :transitionName="transitionName"
86
- :transitionDelay="transitionDelay"
87
- :tabData="tabData"
88
- :tabActive="tabActive"
89
- />
90
- </template>
91
- <template v-if="$slots.logo" #logo>
92
- <slot name="logo" :collapse="collapse" :sidebar="sidebar"></slot>
93
- </template>
94
- <template v-if="$slots.logoTitle" #logoTitle>
95
- <slot name="logoTitle" :collapse="collapse" :sidebar="sidebar"></slot>
96
- </template>
97
- <template v-if="$slots.breadcrumb" #breadcrumb>
98
- <slot
99
- name="breadcrumb"
100
- :levels="levelData"
101
- :isHome="isHome"
102
- :homePath="homeMenuPath"
103
- :sidebar="sidebar"
104
- ></slot>
105
- </template>
106
- <template v-if="$slots.left" #left>
107
- <slot name="left" :sidebar="sidebar"></slot>
108
- </template>
109
- <template v-if="$slots.center" #center>
110
- <slot name="center" :sidebar="sidebar"></slot>
111
- </template>
112
- <template v-if="$slots.right" #right>
113
- <slot name="right" :sidebar="sidebar"></slot>
114
- </template>
115
- <template
116
- v-for="name in Object.keys($slots).filter(
117
- (k) =>
118
- ![
119
- 'default',
120
- 'logo',
121
- 'logoTitle',
122
- 'breadcrumb',
123
- 'left',
124
- 'center',
125
- 'right',
126
- 'footer',
127
- 'body'
128
- ].includes(k)
129
- )"
130
- #[name]="slotProps"
131
- >
132
- <slot :name="name" v-bind="slotProps || {}"></slot>
133
- </template>
134
- </EleAdminLayout>
135
- </template>
136
-
137
- <script lang="ts">
138
- import {
139
- defineComponent,
140
- ref,
141
- shallowRef,
142
- shallowReactive,
143
- unref,
144
- computed,
145
- watch,
146
- onMounted,
147
- nextTick,
148
- provide,
149
- markRaw
150
- } from 'vue';
151
- import type { RouteLocationNormalizedLoaded } from 'vue-router';
152
- import { useRouter } from 'vue-router';
153
- import { HomeOutlined } from '../icons';
154
- import type {
155
- EleAdminLayoutInstance,
156
- EleBreadcrumbProps,
157
- EleBacktopProps,
158
- EleMenusProps
159
- } from '../ele-app/plus';
160
- import { useTimer, useMediaQuery, useWindowListener } from '../utils/hook';
161
- import { mapTree, isExternalLink, debounce } from '../utils/core';
162
- import ProIframe from './components/pro-iframe.vue';
163
- import {
164
- getRouteMatched,
165
- findMenuByPath,
166
- getMatchedLevels,
167
- findTabByPath,
168
- findTabByKey,
169
- getRouteTab,
170
- getMenuItems,
171
- getActiveChilds
172
- } from './util';
173
- import type {
174
- MenuItem,
175
- TabItem,
176
- LevelItem,
177
- TabItemEventOption,
178
- LayoutState,
179
- ProLayoutProvide
180
- } from './types';
181
- import { proLayoutProps, proLayoutEmits, PRO_LAYOUT_KEY } from './props';
182
- import EleAdminLayout from '../ele-admin-layout/index.vue';
183
- import type { MenuItem as MenuItemProps } from '../ele-menus/types';
184
- import type { BreadcrumbItem } from '../ele-breadcrumb/types';
185
- import type { TabPaneItem, TabEventOption } from '../ele-tabs/types';
186
- import type {
187
- Layout,
188
- SidebarLayout,
189
- TabBar,
190
- Maximized
191
- } from '../ele-admin-layout/types';
192
-
193
- export default defineComponent({
194
- name: 'EleProLayout',
195
- components: { EleAdminLayout, ProIframe },
196
- props: proLayoutProps,
197
- emits: proLayoutEmits,
198
- setup(props, { emit }) {
199
- const { currentRoute, push } = useRouter();
200
- const [startTimer, stopTimer] = useTimer(() => props.menuHoverTimeout);
201
- const state: LayoutState = { navData: [], sideData: [], boxData: [] };
202
- const mobileQuery = '(max-width: 768px)';
203
-
204
- /** 布局组件 */
205
- const layoutRef = ref<EleAdminLayoutInstance>(null);
206
-
207
- /** 菜单数据 */
208
- const menuData = shallowRef<MenuItem[]>([]);
209
-
210
- /** 顶栏菜单数据 */
211
- const navData = shallowRef<MenuItem[]>([]);
212
-
213
- /** 顶栏菜单选中 */
214
- const navActive = ref<string>();
215
-
216
- /** 侧栏菜单数据 */
217
- const sideData = shallowRef<MenuItem[]>([]);
218
-
219
- /** 侧栏菜单选中 */
220
- const sideActive = ref<string>();
221
-
222
- /** 双侧栏一级菜单数据 */
223
- const boxData = shallowRef<MenuItem[]>([]);
224
-
225
- /** 双侧栏一级菜单选中 */
226
- const boxActive = ref<string>();
227
-
228
- /** 页签数据 */
229
- const tabData = shallowRef<TabItem[]>([]);
230
-
231
- /** 页签选中 */
232
- const tabActive = ref<string>();
233
-
234
- /** 面包屑导航数据 */
235
- const levelData = shallowRef<LevelItem[]>([]);
236
-
237
- /** 是否是移动端风格 */
238
- const mobile = ref<boolean>(false);
239
-
240
- /** 主页地址 */
241
- const homeMenuPath = ref<string>();
242
-
243
- /** 当前路由是否是主页 */
244
- const isHome = ref<boolean>(false);
245
-
246
- /** 当前路由是否隐藏顶栏 */
247
- const hideHeader = ref<boolean>(false);
248
-
249
- /** 当前路由是否隐藏侧栏 */
250
- const hideSidebar = ref<boolean>(false);
251
-
252
- /** 当前路由是否隐藏双侧栏一级 */
253
- const hideSidebox = ref<boolean>(false);
254
-
255
- /** 当前路由是否隐藏页签栏 */
256
- const hideTabs = ref<boolean>(false);
257
-
258
- /** 当前路由是否隐藏页脚 */
259
- const hideFooter = ref<boolean>(false);
260
-
261
- /** 计算当前路由导航模式 */
262
- const computedNavigation = (): Layout => {
263
- let nav: Layout = 'default';
264
- if (hideHeader.value) {
265
- nav = 'side';
266
- } else if (hideSidebar.value && hideSidebox.value) {
267
- nav = 'top';
268
- } else if (props.layout === 'top' || props.layout === 'mix') {
269
- nav = props.layout;
270
- }
271
- if (mobile.value && (nav === 'top' || nav === 'mix')) {
272
- nav = 'default';
273
- }
274
- return nav;
275
- };
276
-
277
- /** 计算当前路由侧栏导航模式 */
278
- const computedSideNavigation = (): SidebarLayout => {
279
- if (!hideSidebox.value && hideSidebar.value) {
280
- return 'box';
281
- }
282
- return props.sidebarLayout === 'mix' ? 'mix' : 'default';
283
- };
284
-
285
- /** 当前路由导航模式 */
286
- const navigation = ref<Layout>(computedNavigation());
287
-
288
- /** 当前路由侧栏导航模式 */
289
- const sideNavigation = ref<SidebarLayout>(computedSideNavigation());
290
-
291
- /** 布局顶栏菜单数据 */
292
- const layoutHeaders = computed<MenuItemProps[]>(() => {
293
- const navRoute =
294
- props.navTrigger !== 'click' && props.navTrigger !== 'hover';
295
- return getMenuItems(navData.value, navRoute);
296
- });
297
-
298
- /** 布局侧栏菜单数据 */
299
- const layoutSidebars = computed<MenuItemProps[]>(() => {
300
- const menuRoute =
301
- props.itemTrigger !== 'click' && props.itemTrigger !== 'hover';
302
- return getMenuItems(sideData.value, menuRoute);
303
- });
304
-
305
- /** 布局双侧栏一级菜单数据 */
306
- const layoutSideboxs = computed<MenuItemProps[]>(() => {
307
- const boxRoute =
308
- props.boxTrigger !== 'click' && props.boxTrigger !== 'hover';
309
- return getMenuItems(boxData.value, boxRoute);
310
- });
311
-
312
- /** 布局页签数据 */
313
- const layoutTabs = computed<TabPaneItem[]>(() => {
314
- const data = props.fixedHome
315
- ? tabData.value.filter((t) => !t.home)
316
- : tabData.value;
317
- const onlyOne = !props.fixedHome && data.length === 1;
318
- return data.map((d) => {
319
- return {
320
- name: d.key,
321
- label: d.title,
322
- closable: onlyOne && d.home ? false : d.closable,
323
- meta: d.meta
324
- };
325
- });
326
- });
327
-
328
- /** 布局面包屑导航数据 */
329
- const layoutLevels = computed<BreadcrumbItem[]>(() => {
330
- const data: BreadcrumbItem[] = [];
331
- if (!isHome.value) {
332
- const to = homeMenuPath.value;
333
- const is = { transform: 'scale(1.13)', transformOrigin: '8px -2px' };
334
- data.push({
335
- key: to,
336
- to,
337
- icon: markRaw(HomeOutlined),
338
- iconStyle: is
339
- });
340
- }
341
- levelData.value.forEach((d) => {
342
- data.push({ key: d.path, title: d.title });
343
- });
344
- return data;
345
- });
346
-
347
- /** 当前路由布局风格 */
348
- const routeLayout = computed<Layout>(() => {
349
- if (
350
- navigation.value !== 'top' &&
351
- navigation.value !== 'side' &&
352
- !layoutSidebars.value.length &&
353
- !layoutSideboxs.value.length
354
- ) {
355
- return 'top';
356
- }
357
- return navigation.value;
358
- });
359
-
360
- /** 当前路由侧栏布局风格 */
361
- const routeSideType = computed<SidebarLayout>(() => {
362
- if (sideNavigation.value === 'mix' && !layoutSidebars.value.length) {
363
- return 'box';
364
- }
365
- return sideNavigation.value;
366
- });
367
-
368
- /** 当前路由是否是仅双侧栏一级 */
369
- const isBoxSide = computed<boolean>(() => routeSideType.value === 'box');
370
-
371
- /** 当前路由是否显示页签栏 */
372
- const routeTabBar = computed<TabBar>(() => {
373
- return hideTabs.value ? false : props.tabBar;
374
- });
375
-
376
- /** 当前路由内容区是否最大化 */
377
- const routeMaximized = computed<Maximized | undefined>(() => {
378
- const max = props.maximized;
379
- if (
380
- hideHeader.value &&
381
- hideSidebar.value &&
382
- hideSidebox.value &&
383
- !max
384
- ) {
385
- return true;
386
- }
387
- return max === true && props.expanded ? 'expanded' : max;
388
- });
389
-
390
- /** 是否有侧栏 */
391
- const sidebar = computed<boolean>(() => {
392
- return (
393
- (routeLayout.value !== 'top' && !isBoxSide.value) || mobile.value
394
- );
395
- });
396
-
397
- /** 面包屑导航属性 */
398
- const breadcrumbProps = computed<boolean | EleBreadcrumbProps>(() => {
399
- if (!props.breadcrumb || props.breadcrumbSeparator == null) {
400
- return props.breadcrumb;
401
- }
402
- if (props.breadcrumb === true) {
403
- return { separator: props.breadcrumbSeparator };
404
- }
405
- return { separator: props.breadcrumbSeparator, ...props.breadcrumb };
406
- });
407
-
408
- /** 返回顶部属性 */
409
- const backTopProps = computed<boolean | EleBacktopProps>(() => {
410
- const backTop = props.backTop;
411
- const r = props.backTopRight;
412
- const b = props.backTopBottom;
413
- const vh = props.backTopVisibilityHeight;
414
- const t = props.backTopTarget;
415
- if (!backTop || (vh == null && r == null && b == null && t == null)) {
416
- return backTop;
417
- }
418
- const prop: EleBacktopProps = backTop === true ? {} : { ...backTop };
419
- if (vh != null && prop.visibilityHeight == null) {
420
- prop.visibilityHeight = vh;
421
- }
422
- if (r != null && prop.right == null) {
423
- prop.right = r;
424
- }
425
- if (b != null && prop.bottom == null) {
426
- prop.bottom = b;
427
- }
428
- if (t != null && prop.target == null) {
429
- prop.target = t;
430
- }
431
- return prop;
432
- });
433
-
434
- /** 顶栏菜单属性 */
435
- const navMenuProps = computed<EleMenusProps | undefined>(() => {
436
- const mProps = props.headerMenuProps;
437
- const e = props.ellipsis;
438
- const ep = props.ellipsisProps;
439
- const mt = props.menuTrigger;
440
- const mtet = props.menuTextEllipsisTooltip;
441
- if (e == null && ep == null && mt == null && mtet == null) {
442
- return mProps;
443
- }
444
- const prop: EleMenusProps = mProps == null ? {} : { ...mProps };
445
- if (e != null && prop.ellipsis == null) {
446
- prop.ellipsis = e;
447
- }
448
- if (ep != null && prop.ellipsisProps == null) {
449
- prop.ellipsisProps = ep;
450
- }
451
- if (mt != null && prop.menuTrigger == null) {
452
- prop.menuTrigger = mt;
453
- }
454
- if (mtet != null && prop.textEllipsisTooltip == null) {
455
- prop.textEllipsisTooltip = mtet;
456
- }
457
- return prop;
458
- });
459
-
460
- /** 侧栏菜单属性 */
461
- const sideMenuProps = computed<EleMenusProps | undefined>(() => {
462
- const mProps = props.sidebarMenuProps;
463
- const s = props.sidebarOpeneds;
464
- const u = props.uniqueOpened;
465
- const c = props.colorfulIcon;
466
- const t = props.tooltipEffect;
467
- const mtet = props.menuTextEllipsisTooltip;
468
- if (s == null && u == null && c == null && t == null && mtet == null) {
469
- return mProps;
470
- }
471
- const prop: EleMenusProps = mProps == null ? {} : { ...mProps };
472
- if (s != null && prop.defaultOpeneds == null) {
473
- prop.defaultOpeneds = s;
474
- }
475
- if (u != null && prop.uniqueOpened == null) {
476
- prop.uniqueOpened = u;
477
- }
478
- if (c != null && prop.colorful == null) {
479
- prop.colorful = c;
480
- }
481
- if (t != null && prop.popperEffect == null) {
482
- prop.popperEffect = t;
483
- }
484
- if (mtet != null && prop.textEllipsisTooltip == null) {
485
- prop.textEllipsisTooltip = mtet;
486
- }
487
- return prop;
488
- });
489
-
490
- /** 双侧栏一级菜单属性 */
491
- const boxMenuProps = computed<EleMenusProps | undefined>(() => {
492
- const mProps = props.sideboxMenuProps;
493
- const ci = props.colorfulIcon;
494
- const te = props.tooltipEffect;
495
- const mtet = props.menuTextEllipsisTooltip;
496
- if (ci == null && te == null && mtet == null) {
497
- return mProps;
498
- }
499
- const prop: EleMenusProps = mProps == null ? {} : { ...mProps };
500
- if (ci != null && prop.popupColorful == null) {
501
- prop.popupColorful = ci;
502
- }
503
- if (te != null && prop.popperEffect == null) {
504
- prop.popperEffect = te;
505
- }
506
- if (mtet != null && prop.textEllipsisTooltip == null) {
507
- prop.textEllipsisTooltip = mtet;
508
- }
509
- return prop;
510
- });
511
-
512
- /** 更新当前路由导航模式 */
513
- const updateNavigation = (): boolean | void => {
514
- const value = computedNavigation();
515
- if (navigation.value !== value) {
516
- navigation.value = value;
517
- return true;
518
- }
519
- };
520
-
521
- /** 更新当前路由侧栏导航模式 */
522
- const updateSideNavigation = (): boolean | void => {
523
- const value = computedSideNavigation();
524
- if (sideNavigation.value !== value) {
525
- sideNavigation.value = value;
526
- return true;
527
- }
528
- };
529
-
530
- /** 重置菜单数据及选中 */
531
- const resetMenuState = () => {
532
- if (!state.isHover) {
533
- return;
534
- }
535
- startTimer(() => {
536
- state.isHover = false;
537
- const isMixSide =
538
- sideNavigation.value === 'mix' || sideNavigation.value === 'box';
539
- if (navActive.value !== state.navActive) {
540
- navActive.value = state.navActive;
541
- if (isMixSide) {
542
- boxData.value = state.boxData;
543
- } else {
544
- sideData.value = state.sideData;
545
- }
546
- }
547
- if (isMixSide && boxActive.value !== state.boxActive) {
548
- boxActive.value = state.boxActive;
549
- sideData.value = state.sideData;
550
- }
551
- });
552
- };
553
-
554
- /** 更新侧栏折叠状态 */
555
- const updateCollapse = (collapse: boolean) => {
556
- if (collapse !== props.collapse) {
557
- emit('update:collapse', collapse);
558
- }
559
- };
560
-
561
- /** 更新内容区域全屏状态 */
562
- const updateMaximized = (maximized: boolean) => {
563
- if (maximized !== props.maximized) {
564
- emit('update:maximized', maximized);
565
- }
566
- };
567
-
568
- /** 图标点击事件 */
569
- const handleLogoClick = (e: MouseEvent) => {
570
- emit('logoClick', isHome.value, e);
571
- };
572
-
573
- /** 顶栏子菜单展开事件 */
574
- const handleHeadMenuOpen = (index: string, indexPath: string[]) => {
575
- emit('headMenuOpen', index, indexPath);
576
- };
577
-
578
- /** 顶栏子菜单收起事件 */
579
- const handleHeadMenuClose = (index: string, indexPath: string[]) => {
580
- emit('headMenuClose', index, indexPath);
581
- };
582
-
583
- /** 顶栏子菜单项点击事件 */
584
- const handleHeadMenuItemClick = (item: MenuItemProps, e: MouseEvent) => {
585
- const path = item.index;
586
- const trigger = props.navTrigger;
587
- if (!path || (trigger !== 'click' && trigger !== 'hover')) {
588
- return;
589
- }
590
- if (isExternalLink(path)) {
591
- e.stopPropagation();
592
- if (props.beforeClick && props.beforeClick(item, e) === false) {
593
- return;
594
- }
595
- window.open(path);
596
- return;
597
- }
598
- const childMenus = getActiveChilds(navData.value, path, 'tempChildren');
599
- const isChild = !childMenus.some((d) => !d.meta?.hide);
600
- if (trigger !== 'click' && !isChild) {
601
- e.stopPropagation();
602
- return;
603
- }
604
- if (props.beforeClick && props.beforeClick(item, e) === false) {
605
- return;
606
- }
607
- if (isChild && path !== unref(currentRoute).fullPath) {
608
- push(path);
609
- return;
610
- }
611
- e.stopPropagation();
612
- if (navActive.value !== path) {
613
- navActive.value = path;
614
- const isMixSide =
615
- sideNavigation.value === 'mix' || sideNavigation.value === 'box';
616
- const isCollapse =
617
- sideNavigation.value === 'box' || (props.collapse && !mobile.value);
618
- if (!isMixSide) {
619
- sideData.value = childMenus;
620
- return;
621
- }
622
- boxData.value = childMenus.map((d) => {
623
- return {
624
- ...d,
625
- children: isCollapse ? d.children : void 0,
626
- tempChildren: d.children
627
- };
628
- });
629
- }
630
- };
631
-
632
- /** 顶栏子菜单项鼠标进入事件 */
633
- const handleHeadMenuItemMouseenter = (
634
- item: MenuItemProps,
635
- e: MouseEvent
636
- ) => {
637
- if (navigation.value !== 'mix') {
638
- return;
639
- }
640
- stopTimer();
641
- const path = item.index;
642
- const trigger = props.navTrigger;
643
- if (trigger !== 'hover' || !path) {
644
- return;
645
- }
646
- if (
647
- !isExternalLink(path) &&
648
- props.beforeClick &&
649
- props.beforeClick(item, e) === false
650
- ) {
651
- return;
652
- }
653
- const temp = getActiveChilds(navData.value, path, 'tempChildren');
654
- if (navActive.value !== path) {
655
- state.isHover = true;
656
- navActive.value = temp.some((d) => !d.meta?.hide) ? path : void 0;
657
- const isMixSide =
658
- sideNavigation.value === 'mix' || sideNavigation.value === 'box';
659
- if (!isMixSide) {
660
- sideData.value = temp;
661
- return;
662
- }
663
- boxData.value = temp.map((d) => {
664
- return {
665
- ...d,
666
- children: props.collapse ? d.children : void 0,
667
- tempChildren: d.children
668
- };
669
- });
670
- }
671
- };
672
-
673
- /** 顶栏鼠标进入事件 */
674
- const handleHeadMouseenter = () => {
675
- stopTimer();
676
- };
677
-
678
- /** 顶栏鼠标离开事件 */
679
- const handleHeadMouseleave = () => {
680
- resetMenuState();
681
- };
682
-
683
- /** 双侧栏一级子菜单项点击事件 */
684
- const handleBoxMenuItemClick = (item: MenuItemProps, e: MouseEvent) => {
685
- const path = item.index;
686
- const trigger = props.boxTrigger;
687
- if (!path || (trigger !== 'click' && trigger !== 'hover')) {
688
- return;
689
- }
690
- if (isExternalLink(path)) {
691
- e.stopPropagation();
692
- if (props.beforeClick && props.beforeClick(item, e) === false) {
693
- return;
694
- }
695
- window.open(path);
696
- return;
697
- }
698
- if (props.collapse) {
699
- if (props.beforeClick && props.beforeClick(item, e) === false) {
700
- return;
701
- }
702
- if (path !== unref(currentRoute).fullPath) {
703
- push(path);
704
- }
705
- return;
706
- }
707
- const childMenus = getActiveChilds(boxData.value, path, 'tempChildren');
708
- const isChild = !childMenus.some((d) => !d.meta?.hide);
709
- if (trigger !== 'click' && !isChild) {
710
- e.stopPropagation();
711
- return;
712
- }
713
- if (props.beforeClick && props.beforeClick(item, e) === false) {
714
- return;
715
- }
716
- if (isChild && path !== unref(currentRoute).fullPath) {
717
- push(path);
718
- return;
719
- }
720
- e.stopPropagation();
721
- if (boxActive.value !== path) {
722
- boxActive.value = path;
723
- sideData.value = childMenus;
724
- }
725
- };
726
-
727
- /** 双侧栏一级子菜单项鼠标进入事件 */
728
- const handleBoxMenuItemMouseenter = (
729
- item: MenuItemProps,
730
- e: MouseEvent
731
- ) => {
732
- if (props.collapse) {
733
- return;
734
- }
735
- stopTimer();
736
- const path = item.index;
737
- const trigger = props.boxTrigger;
738
- if (trigger !== 'hover' || !path) {
739
- return;
740
- }
741
- if (
742
- !isExternalLink(path) &&
743
- props.beforeClick &&
744
- props.beforeClick(item, e) === false
745
- ) {
746
- return;
747
- }
748
- const temp = getActiveChilds(boxData.value, path, 'tempChildren');
749
- if (boxActive.value !== path) {
750
- state.isHover = true;
751
- boxActive.value = temp.some((d) => !d.meta?.hide) ? path : void 0;
752
- sideData.value = temp;
753
- }
754
- };
755
-
756
- /** 双侧栏一级鼠标进入事件 */
757
- const handleBoxMouseEnter = () => {
758
- stopTimer();
759
- };
760
-
761
- /** 双侧栏一级鼠标离开事件 */
762
- const handleBoxMouseLeave = () => {
763
- resetMenuState();
764
- };
765
-
766
- /** 侧栏子菜单展开事件 */
767
- const handleSideMenuOpen = (index: string, indexPath: string[]) => {
768
- emit('sideMenuOpen', index, indexPath);
769
- };
770
-
771
- /** 侧栏子菜单收起事件 */
772
- const handleSideMenuClose = (index: string, indexPath: string[]) => {
773
- emit('sideMenuClose', index, indexPath);
774
- };
775
-
776
- /** 侧栏子菜单项点击事件 */
777
- const handleSideMenuItemClick = (item: MenuItemProps, e: MouseEvent) => {
778
- const path = item.index;
779
- const trigger = props.itemTrigger;
780
- if (!path || (trigger !== 'click' && trigger !== 'hover')) {
781
- return;
782
- }
783
- if (props.beforeClick && props.beforeClick(item, e) === false) {
784
- return;
785
- }
786
- if (isExternalLink(path)) {
787
- e.stopPropagation();
788
- window.open(path);
789
- return;
790
- }
791
- sideActive.value = path;
792
- if (path !== unref(currentRoute).fullPath) {
793
- push(path);
794
- }
795
- };
796
-
797
- /** 侧栏鼠标进入事件 */
798
- const handleSideMouseEnter = () => {
799
- stopTimer();
800
- };
801
-
802
- /** 侧栏鼠标离开事件 */
803
- const handleSideMouseLeave = () => {
804
- resetMenuState();
805
- };
806
-
807
- /** 页签点击事件 */
808
- const handleTabClick = (option: TabEventOption) => {
809
- const key = option.name as string;
810
- const item = findTabByKey(key, props.tabs);
811
- const opt: TabItemEventOption = { key, item, active: tabActive.value };
812
- emit('tabClick', opt);
813
- };
814
-
815
- /** 页签移除事件 */
816
- const handleTabRemove = (key: string) => {
817
- const item = findTabByKey(key, props.tabs);
818
- const opt: TabItemEventOption = { key, item, active: tabActive.value };
819
- emit('tabRemove', opt);
820
- };
821
-
822
- /** 页签右键菜单点击事件 */
823
- const handleTabContextMenu = (option: TabEventOption) => {
824
- const opt: TabItemEventOption = {
825
- key: option.name as string,
826
- item: findTabByKey(option.name as string, props.tabs),
827
- active: tabActive.value,
828
- command: option.command
829
- };
830
- emit('tabContextMenu', opt);
831
- };
832
-
833
- /** 页签拖动顺序改变事件 */
834
- const handleTabSortChange = (data: TabPaneItem[]) => {
835
- const result: TabItem[] = data.map((d) => {
836
- return findTabByKey(d.name as string, props.tabs) as TabItem;
837
- });
838
- if (props.fixedHome && props.tabs != null) {
839
- const homeTab = props.tabs.find((t) => t.home);
840
- if (homeTab) {
841
- result.unshift(homeTab);
842
- }
843
- }
844
- emit('tabSortChange', result);
845
- };
846
-
847
- /** 获取内容区域节点 */
848
- const getContentElem = (): HTMLElement | null => {
849
- if (!layoutRef.value) {
850
- return null;
851
- }
852
- return layoutRef.value.getContentEl();
853
- };
854
-
855
- /** 处理路由切换 */
856
- const handleRouteChange = (route: RouteLocationNormalizedLoaded) => {
857
- const { path, meta } = route;
858
- // 获取页脚隐藏状态
859
- hideFooter.value = !!meta.hideFooter;
860
-
861
- // 内容区域滚动到顶部
862
- const contentEl = getContentElem();
863
- if (props.autoScrollTop && contentEl) {
864
- contentEl.scrollTop = 0;
865
- }
866
-
867
- // 刷新路由不做处理
868
- if (props.redirectPath && path.startsWith(props.redirectPath)) {
869
- return;
870
- }
871
-
872
- // 获取各组件隐藏状态
873
- hideSidebar.value = !!meta.hideSidebar;
874
- hideSidebox.value =
875
- props.sidebarLayout === 'mix' ? !!meta.hideSidebox : true;
876
- hideHeader.value = !!meta.hideHeader;
877
- hideTabs.value = !!meta.hideTabs;
878
-
879
- // 更新导航模式
880
- const navigationIsChanged = updateNavigation();
881
- const sideNavigationIsChanged = updateSideNavigation();
882
-
883
- // 获取路由对应的菜单数据
884
- const { active, title, matched, activeOther } = getRouteMatched(
885
- route,
886
- menuData.value
887
- );
888
-
889
- // 获取面包屑导航数据
890
- levelData.value = getMatchedLevels(
891
- matched,
892
- activeOther,
893
- route,
894
- menuData.value,
895
- tabData.value
896
- );
897
-
898
- // 添加页签
899
- const t = getRouteTab(route, tabData.value, homeMenuPath.value, title);
900
- isHome.value = t.home as boolean;
901
- tabActive.value = t.key as string;
902
- emit('tabAdd', t);
903
-
904
- // 更新菜单选中
905
- if (!navigationIsChanged && !sideNavigationIsChanged) {
906
- updateMenuActive(active, matched);
907
- if (
908
- navigation.value === 'mix' ||
909
- sideNavigation.value === 'mix' ||
910
- sideNavigation.value === 'box'
911
- ) {
912
- splitMenuData();
913
- }
914
- }
915
-
916
- // 移动端风格自动收起侧栏
917
- if (mobile.value) {
918
- updateCollapse(true);
919
- }
920
- };
921
-
922
- /** 更新菜单选中 */
923
- const updateMenuActive = (active: string, matched?: MenuItem[]) => {
924
- const [active1, active2] = matched?.length
925
- ? [matched[0].path, (matched[1] ?? matched[0]).path]
926
- : [];
927
- if (navigation.value === 'top') {
928
- // 顶栏导航
929
- navActive.value = active;
930
- boxActive.value = void 0;
931
- } else if (navigation.value === 'mix') {
932
- // 混合导航
933
- navActive.value = active1;
934
- boxActive.value = active2;
935
- } else {
936
- // 侧栏导航
937
- navActive.value = void 0;
938
- boxActive.value = active1;
939
- }
940
- sideActive.value = active;
941
- //
942
- state.navActive = navActive.value;
943
- state.boxActive = boxActive.value;
944
- state.sideActive = sideActive.value;
945
- };
946
-
947
- /** 分割菜单数据 */
948
- const splitMenuData = () => {
949
- const isTopNav = navigation.value === 'top';
950
- const isMixNav = navigation.value === 'mix';
951
- const isMixSide =
952
- sideNavigation.value === 'mix' || sideNavigation.value === 'box';
953
- const isCollapse =
954
- sideNavigation.value === 'box' || (props.collapse && !mobile.value);
955
- if (!menuData.value?.length) {
956
- navData.value = [];
957
- boxData.value = [];
958
- sideData.value = [];
959
- } else if (isTopNav) {
960
- // 顶栏导航
961
- navData.value = menuData.value;
962
- boxData.value = [];
963
- sideData.value = [];
964
- } else if (isMixNav) {
965
- // 混合导航
966
- navData.value = menuData.value.map((d) => {
967
- return { ...d, children: void 0, tempChildren: d.children };
968
- });
969
- const childMenus = getActiveChilds(menuData.value, navActive.value);
970
- if (!childMenus.length) {
971
- boxData.value = [];
972
- sideData.value = [];
973
- } else if (isMixSide) {
974
- // 双侧栏
975
- boxData.value = childMenus.map((d) => {
976
- return {
977
- ...d,
978
- children: isCollapse ? d.children : void 0,
979
- tempChildren: d.children
980
- };
981
- });
982
- sideData.value = getActiveChilds(childMenus, boxActive.value);
983
- } else {
984
- // 单侧栏
985
- boxData.value = [];
986
- sideData.value = childMenus;
987
- }
988
- } else {
989
- // 侧栏导航
990
- navData.value = [];
991
- if (isMixSide) {
992
- // 双侧栏
993
- boxData.value = menuData.value.map((d) => {
994
- return {
995
- ...d,
996
- children: isCollapse ? d.children : void 0,
997
- tempChildren: d.children
998
- };
999
- });
1000
- sideData.value = getActiveChilds(menuData.value, boxActive.value);
1001
- } else {
1002
- // 单侧栏
1003
- boxData.value = [];
1004
- sideData.value = menuData.value;
1005
- }
1006
- }
1007
- //
1008
- state.navData = navData.value;
1009
- state.boxData = boxData.value;
1010
- state.sideData = sideData.value;
1011
- };
1012
-
1013
- /** 国际化处理菜单数据 */
1014
- const updateMenuData = () => {
1015
- let home: MenuItem | undefined;
1016
- menuData.value = mapTree(props.menus, (item) => {
1017
- if (!home && !item.children?.length) {
1018
- home = item;
1019
- }
1020
- const title = routeI18n(item.path, item) || item.meta?.title;
1021
- return { ...item, meta: { ...item.meta, title } };
1022
- });
1023
- splitMenuData();
1024
- homeMenuPath.value = props.homePath || home?.path || '/';
1025
- };
1026
-
1027
- /** 国际化处理页签数据 */
1028
- const updateTabData = () => {
1029
- if (!props.tabs) {
1030
- tabData.value = [];
1031
- return;
1032
- }
1033
- tabData.value = props.tabs.map((item) => {
1034
- const m = findMenuByPath(item.path, menuData.value);
1035
- return {
1036
- ...item,
1037
- title: routeI18n(item.path, m, item) || item.title
1038
- };
1039
- });
1040
- };
1041
-
1042
- /** 国际化处理面包屑导航数据 */
1043
- const updateLevelData = () => {
1044
- levelData.value = levelData.value.map((item) => {
1045
- const t = findTabByPath(item.path, tabData.value);
1046
- const m = findMenuByPath(item.path, menuData.value);
1047
- const title =
1048
- t?.title || m?.meta?.title || routeI18n(item.path, m, t, item);
1049
- return { ...item, title: title || item.title };
1050
- });
1051
- };
1052
-
1053
- /** 获取路由地址对应的国际化名称 */
1054
- const routeI18n = (
1055
- path?: string,
1056
- menu?: MenuItem,
1057
- tab?: TabItem,
1058
- level?: LevelItem
1059
- ) => {
1060
- if (props.i18n && path) {
1061
- return props.i18n({
1062
- locale: props.locale,
1063
- path,
1064
- menu,
1065
- tab,
1066
- level
1067
- });
1068
- }
1069
- };
1070
-
1071
- watch(
1072
- () => props.menus,
1073
- () => {
1074
- updateMenuData();
1075
- },
1076
- { deep: true }
1077
- );
1078
-
1079
- watch(
1080
- () => props.tabs,
1081
- () => {
1082
- updateTabData();
1083
- updateLevelData();
1084
- },
1085
- { deep: true }
1086
- );
1087
-
1088
- watch([() => props.layout, mobile], () => {
1089
- updateNavigation();
1090
- });
1091
-
1092
- watch(
1093
- () => props.sidebarLayout,
1094
- () => {
1095
- updateSideNavigation();
1096
- }
1097
- );
1098
-
1099
- watch([navigation, sideNavigation], () => {
1100
- const route = unref(currentRoute);
1101
- const { active, matched } = getRouteMatched(route, menuData.value);
1102
- updateMenuActive(active, matched);
1103
- splitMenuData();
1104
- });
1105
-
1106
- watch(
1107
- () => props.collapse,
1108
- () => {
1109
- if (sideNavigation.value === 'mix' && !mobile.value) {
1110
- if (props.collapse) {
1111
- boxData.value = boxData.value.map((d) => {
1112
- return { ...d, children: d.tempChildren };
1113
- });
1114
- } else {
1115
- boxData.value = boxData.value.map((d) => {
1116
- return { ...d, children: void 0 };
1117
- });
1118
- }
1119
- state.boxData = boxData.value;
1120
- }
1121
- }
1122
- );
1123
-
1124
- watch(
1125
- () => props.locale,
1126
- () => {
1127
- updateMenuData();
1128
- updateTabData();
1129
- updateLevelData();
1130
- },
1131
- { immediate: true }
1132
- );
1133
-
1134
- watch(
1135
- currentRoute,
1136
- (route) => {
1137
- handleRouteChange(unref(route));
1138
- },
1139
- { immediate: true }
1140
- );
1141
-
1142
- /** 共享布局状态 */
1143
- const layoutProvide = shallowReactive<ProLayoutProvide>({
1144
- keepAlive: props.tabBar && props.keepAlive,
1145
- responsive: props.responsive
1146
- });
1147
- provide<ProLayoutProvide>(PRO_LAYOUT_KEY, layoutProvide);
1148
-
1149
- watch([() => props.tabBar, () => props.keepAlive], () => {
1150
- layoutProvide.keepAlive = props.tabBar && props.keepAlive;
1151
- });
1152
-
1153
- /** 移动端小屏幕媒体查询 */
1154
- const [media, startMedia, stopMedia] = useMediaQuery(mobileQuery, () => {
1155
- const isMobile = props.responsive ? media.matches : false;
1156
- if (mobile.value !== isMobile) {
1157
- mobile.value = isMobile;
1158
- updateCollapse(mobile.value);
1159
- }
1160
- });
1161
-
1162
- watch(
1163
- () => props.responsive,
1164
- () => {
1165
- layoutProvide.responsive = props.responsive;
1166
- if (props.responsive) {
1167
- startMedia();
1168
- } else {
1169
- stopMedia();
1170
- }
1171
- },
1172
- { immediate: true }
1173
- );
1174
-
1175
- /** 返回键退出内容全屏 */
1176
- useWindowListener('keydown', (e: KeyboardEvent) => {
1177
- if (e.keyCode === 27 && props.compressOnEsc && props.maximized) {
1178
- e.stopPropagation();
1179
- updateMaximized(false);
1180
- }
1181
- });
1182
-
1183
- /** 触发内容区域尺寸改变 */
1184
- const handleResize = () => {
1185
- const el = getContentElem();
1186
- if (el) {
1187
- const width = el.clientWidth;
1188
- const height = el.clientHeight;
1189
- if (width !== state.contentWidth || height !== state.contentHeight) {
1190
- state.contentWidth = width;
1191
- state.contentHeight = height;
1192
- emit('bodySizeChange', { width, height, mobile: mobile.value });
1193
- }
1194
- }
1195
- };
1196
-
1197
- useWindowListener(debounce(() => handleResize(), 500));
1198
- const [startBodyResizeTimer] = useTimer(600);
1199
-
1200
- watch([() => props.collapse, () => props.compact], () => {
1201
- startBodyResizeTimer(() => {
1202
- handleResize();
1203
- });
1204
- });
1205
-
1206
- watch(
1207
- [
1208
- routeLayout,
1209
- routeSideType,
1210
- routeTabBar,
1211
- routeMaximized,
1212
- () => props.fluid
1213
- ],
1214
- () => {
1215
- nextTick(() => {
1216
- handleResize();
1217
- });
1218
- }
1219
- );
1220
-
1221
- onMounted(() => {
1222
- handleResize();
1223
- });
1224
-
1225
- return {
1226
- layoutRef,
1227
- navActive,
1228
- sideActive,
1229
- boxActive,
1230
- tabData,
1231
- tabActive,
1232
- levelData,
1233
- mobile,
1234
- homeMenuPath,
1235
- isHome,
1236
- hideFooter,
1237
- layoutHeaders,
1238
- layoutSidebars,
1239
- layoutSideboxs,
1240
- layoutTabs,
1241
- layoutLevels,
1242
- routeLayout,
1243
- routeSideType,
1244
- isBoxSide,
1245
- routeTabBar,
1246
- routeMaximized,
1247
- sidebar,
1248
- breadcrumbProps,
1249
- backTopProps,
1250
- navMenuProps,
1251
- sideMenuProps,
1252
- boxMenuProps,
1253
- updateCollapse,
1254
- handleLogoClick,
1255
- handleHeadMenuOpen,
1256
- handleHeadMenuClose,
1257
- handleHeadMenuItemClick,
1258
- handleHeadMenuItemMouseenter,
1259
- handleHeadMouseenter,
1260
- handleHeadMouseleave,
1261
- handleBoxMenuItemClick,
1262
- handleBoxMenuItemMouseenter,
1263
- handleBoxMouseEnter,
1264
- handleBoxMouseLeave,
1265
- handleSideMenuOpen,
1266
- handleSideMenuClose,
1267
- handleSideMenuItemClick,
1268
- handleSideMouseEnter,
1269
- handleSideMouseLeave,
1270
- handleTabClick,
1271
- handleTabRemove,
1272
- handleTabContextMenu,
1273
- handleTabSortChange
1274
- };
1275
- }
1276
- });
1277
- </script>