Vben
2021-04-07 5b8eb4a49a097a47caf491c44df427522ab58daa
提交 | 用户 | age
e6db0d 1 <template>
9edc28 2   <div :class="`${prefixCls}-dom`" :style="getDomStyle"></div>
e6db0d 3   <div
V 4     v-click-outside="handleClickOutside"
0e7c57 5     :style="getWrapStyle"
e6db0d 6     :class="[
V 7       prefixCls,
8       getMenuTheme,
9       {
10         open: openMenu,
0e7c57 11         mini: getCollapsed,
e6db0d 12       },
V 13     ]"
0419a0 14     v-bind="getMenuEvents"
e6db0d 15   >
V 16     <AppLogo :showTitle="false" :class="`${prefixCls}-logo`" />
1e5fcd 17
V 18     <Trigger :class="`${prefixCls}-trigger`" />
19
e6db0d 20     <ScrollContainer>
V 21       <ul :class="`${prefixCls}-module`">
22         <li
23           :class="[
24             `${prefixCls}-module__item `,
25             {
26               [`${prefixCls}-module__item--active`]: item.path === activePath,
27             },
28           ]"
d5d4c4 29           v-bind="getItemEvents(item)"
e6db0d 30           v-for="item in menuModules"
V 31           :key="item.path"
32         >
d5d4c4 33           <SimpleMenuTag :item="item" collapseParent dot />
97180e 34           <Icon
e6db0d 35             :class="`${prefixCls}-module__icon`"
0e7c57 36             :size="getCollapsed ? 16 : 20"
d5d4c4 37             :icon="item.icon || (item.meta && item.meta.icon)"
e6db0d 38           />
9edc28 39           <p :class="`${prefixCls}-module__name`">
V 40             {{ t(item.name) }}
41           </p>
e6db0d 42         </li>
V 43       </ul>
44     </ScrollContainer>
45
46     <div :class="`${prefixCls}-menu-list`" ref="sideRef" :style="getMenuStyle">
47       <div
819bcb 48         v-show="openMenu"
e6db0d 49         :class="[
V 50           `${prefixCls}-menu-list__title`,
51           {
52             show: openMenu,
53           },
54         ]"
55       >
56         <span class="text"> {{ title }}</span>
97180e 57         <Icon
V 58           :size="16"
0e7c57 59           :icon="getMixSideFixed ? 'ri:pushpin-2-fill' : 'ri:pushpin-2-line'"
97180e 60           class="pushpin"
V 61           @click="handleFixedMenu"
62         />
e6db0d 63       </div>
V 64       <ScrollContainer :class="`${prefixCls}-menu-list__content`">
ff2b12 65         <SimpleMenu
e6db0d 66           :items="chilrenMenus"
V 67           :theme="getMenuTheme"
ed213d 68           mixSider
e6db0d 69           @menuClick="handleMenuClick"
V 70         />
71       </ScrollContainer>
72       <div
73         v-show="getShowDragBar && openMenu"
74         :class="`${prefixCls}-drag-bar`"
75         ref="dragBarRef"
76       ></div>
77     </div>
78   </div>
79 </template>
80 <script lang="ts">
81   import type { Menu } from '/@/router/types';
1e5fcd 82   import type { CSSProperties } from 'vue';
V 83   import type { RouteLocationNormalized } from 'vue-router';
84
85   import { defineComponent, onMounted, ref, computed, unref } from 'vue';
86
e6db0d 87   import { ScrollContainer } from '/@/components/Container';
d5d4c4 88   import { SimpleMenuTag } from '/@/components/SimpleMenu';
97180e 89   import Icon from '/@/components/Icon';
e6db0d 90   import { AppLogo } from '/@/components/Application';
1e5fcd 91   import Trigger from '../trigger/HeaderTrigger.vue';
V 92
e6db0d 93   import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
V 94   import { useDragLine } from './useLayoutSider';
97180e 95   import { useGlobSetting } from '/@/hooks/setting';
1e5fcd 96   import { useDesign } from '/@/hooks/web/useDesign';
V 97   import { useI18n } from '/@/hooks/web/useI18n';
98   import { useGo } from '/@/hooks/web/usePage';
97180e 99
0e7c57 100   import { SIDE_BAR_SHOW_TIT_MINI_WIDTH, SIDE_BAR_MINI_WIDTH } from '/@/enums/appEnum';
e6db0d 101
V 102   import clickOutside from '/@/directives/clickOutside';
1e5fcd 103   import { getShallowMenus, getChildrenMenus, getCurrentParentPath } from '/@/router/menus';
6bb19f 104   import { listenerRouteChange } from '/@/logics/mitt/routeChange';
ff2b12 105   import { SimpleMenu } from '/@/components/SimpleMenu';
e6db0d 106
V 107   export default defineComponent({
108     name: 'LayoutMixSider',
109     components: {
110       ScrollContainer,
111       AppLogo,
ff2b12 112       SimpleMenu,
97180e 113       Icon,
1e5fcd 114       Trigger,
d5d4c4 115       SimpleMenuTag,
e6db0d 116     },
V 117     directives: {
118       clickOutside,
119     },
120     setup() {
121       let menuModules = ref<Menu[]>([]);
122       const activePath = ref('');
123       const chilrenMenus = ref<Menu[]>([]);
124       const openMenu = ref(false);
125       const dragBarRef = ref<ElRef>(null);
126       const sideRef = ref<ElRef>(null);
127       const currentRoute = ref<Nullable<RouteLocationNormalized>>(null);
128
129       const { prefixCls } = useDesign('layout-mix-sider');
130       const go = useGo();
131       const { t } = useI18n();
132       const {
133         getMenuWidth,
134         getCanDrag,
135         getCloseMixSidebarOnChange,
136         getMenuTheme,
0419a0 137         getMixSideTrigger,
97180e 138         getRealWidth,
V 139         getMixSideFixed,
140         mixSideHasChildren,
141         setMenuSetting,
0e7c57 142         getIsMixSidebar,
V 143         getCollapsed,
e6db0d 144       } = useMenuSetting();
97180e 145
e6db0d 146       const { title } = useGlobSetting();
V 147
148       useDragLine(sideRef, dragBarRef, true);
149
150       const getMenuStyle = computed(
151         (): CSSProperties => {
152           return {
153             width: unref(openMenu) ? `${unref(getMenuWidth)}px` : 0,
0e7c57 154             left: `${unref(getMixSideWidth)}px`,
e6db0d 155           };
V 156         }
157       );
0419a0 158
97180e 159       const getIsFixed = computed(() => {
9edc28 160         /* eslint-disable-next-line */
97180e 161         mixSideHasChildren.value = unref(chilrenMenus).length > 0;
V 162         const isFixed = unref(getMixSideFixed) && unref(mixSideHasChildren);
163         if (isFixed) {
9edc28 164           /* eslint-disable-next-line */
97180e 165           openMenu.value = true;
V 166         }
167         return isFixed;
168       });
169
0e7c57 170       const getMixSideWidth = computed(() => {
V 171         return unref(getCollapsed) ? SIDE_BAR_MINI_WIDTH : SIDE_BAR_SHOW_TIT_MINI_WIDTH;
172       });
173
97180e 174       const getDomStyle = computed(
V 175         (): CSSProperties => {
176           const fixedWidth = unref(getIsFixed) ? unref(getRealWidth) : 0;
0e7c57 177           const width = `${unref(getMixSideWidth) + fixedWidth}px`;
V 178           return getWrapCommonStyle(width);
179         }
180       );
181
182       const getWrapStyle = computed(
183         (): CSSProperties => {
184           const width = `${unref(getMixSideWidth)}px`;
185           return getWrapCommonStyle(width);
97180e 186         }
V 187       );
188
0419a0 189       const getMenuEvents = computed(() => {
0e7c57 190         return !unref(getMixSideFixed)
V 191           ? {
192               onMouseleave: () => {
193                 closeMenu();
194               },
195             }
196           : {};
0419a0 197       });
e6db0d 198
V 199       const getShowDragBar = computed(() => unref(getCanDrag));
200
201       onMounted(async () => {
202         menuModules.value = await getShallowMenus();
203       });
204
6bb19f 205       listenerRouteChange((route) => {
e6db0d 206         currentRoute.value = route;
97180e 207         setActive(true);
e6db0d 208         if (unref(getCloseMixSidebarOnChange)) {
97180e 209           closeMenu();
e6db0d 210         }
V 211       });
212
0e7c57 213       function getWrapCommonStyle(width: string): CSSProperties {
V 214         return {
215           width,
216           maxWidth: width,
217           minWidth: width,
218           flex: `0 0 ${width}`,
219         };
220       }
221
222       // Process module menu click
0419a0 223       async function hanldeModuleClick(path: string, hover = false) {
e6db0d 224         const children = await getChildrenMenus(path);
V 225
226         if (unref(activePath) === path) {
0419a0 227           if (!hover) {
97180e 228             if (!unref(openMenu)) {
V 229               openMenu.value = true;
230             } else {
231               closeMenu();
232             }
0419a0 233           }
e6db0d 234           if (!unref(openMenu)) {
V 235             setActive();
236           }
237         } else {
238           openMenu.value = true;
239           activePath.value = path;
240         }
241
242         if (!children || children.length === 0) {
243           go(path);
244           chilrenMenus.value = [];
97180e 245           closeMenu();
e6db0d 246           return;
V 247         }
248         chilrenMenus.value = children;
249       }
250
0e7c57 251       // Set the currently active menu and submenu
97180e 252       async function setActive(setChildren = false) {
e6db0d 253         const path = currentRoute.value?.path;
V 254         if (!path) return;
255         const parentPath = await getCurrentParentPath(path);
256         activePath.value = parentPath;
257         // hanldeModuleClick(parentPath);
0e7c57 258         if (unref(getIsMixSidebar)) {
97180e 259           const activeMenu = unref(menuModules).find((item) => item.path === unref(activePath));
V 260           const p = activeMenu?.path;
261           if (p) {
262             const children = await getChildrenMenus(p);
263             if (setChildren) {
264               chilrenMenus.value = children;
0e7c57 265
V 266               if (unref(getMixSideFixed)) {
267                 openMenu.value = children.length > 0;
268               }
97180e 269             }
V 270             if (children.length === 0) {
271               chilrenMenus.value = [];
272             }
273           }
274         }
e6db0d 275       }
V 276
277       function handleMenuClick(path: string) {
278         go(path);
279       }
280
281       function handleClickOutside() {
144ab5 282         setActive(true);
97180e 283         closeMenu();
e6db0d 284       }
V 285
0419a0 286       function getItemEvents(item: Menu) {
V 287         if (unref(getMixSideTrigger) === 'hover') {
288           return {
289             onMouseenter: () => hanldeModuleClick(item.path, true),
290           };
291         }
292         return {
293           onClick: () => hanldeModuleClick(item.path),
294         };
97180e 295       }
V 296
297       function handleFixedMenu() {
298         setMenuSetting({
299           mixSideFixed: !unref(getIsFixed),
300         });
301       }
302
0e7c57 303       // Close menu
97180e 304       function closeMenu() {
V 305         if (!unref(getIsFixed)) {
306           openMenu.value = false;
307         }
0419a0 308       }
V 309
e6db0d 310       return {
V 311         t,
312         prefixCls,
313         menuModules,
314         hanldeModuleClick,
315         activePath,
316         chilrenMenus,
317         getShowDragBar,
318         handleMenuClick,
319         getMenuStyle,
320         handleClickOutside,
321         sideRef,
322         dragBarRef,
323         title,
324         openMenu,
325         getMenuTheme,
0419a0 326         getItemEvents,
V 327         getMenuEvents,
97180e 328         getDomStyle,
V 329         handleFixedMenu,
330         getMixSideFixed,
0e7c57 331         getWrapStyle,
V 332         getCollapsed,
e6db0d 333       };
V 334     },
335   });
336 </script>
337 <style lang="less">
338   @prefix-cls: ~'@{namespace}-layout-mix-sider';
339   @width: 80px;
340   .@{prefix-cls} {
341     position: fixed;
342     top: 0;
343     left: 0;
344     z-index: @layout-mix-sider-fixed-z-index;
345     height: 100%;
346     overflow: hidden;
347     background: @sider-dark-bg-color;
0e7c57 348     transition: all 0.2s ease 0s;
e6db0d 349
V 350     &-dom {
351       height: 100%;
352       overflow: hidden;
353       transition: all 0.2s ease 0s;
354     }
355
356     &-logo {
357       display: flex;
358       height: @header-height;
359       padding-left: 0 !important;
360       justify-content: center;
361
362       img {
363         width: @logo-width;
364         height: @logo-width;
365       }
366     }
367
368     &.light {
369       .@{prefix-cls}-logo {
370         border-bottom: 1px solid rgb(238, 238, 238);
371       }
372
373       &.open {
0e7c57 374         > .scrollbar {
e6db0d 375           border-right: 1px solid rgb(238, 238, 238);
V 376         }
377       }
378
379       .@{prefix-cls}-module {
380         &__item {
381           font-weight: normal;
382           color: rgba(0, 0, 0, 0.65);
383
384           &--active {
385             color: @primary-color;
386             background: unset;
97180e 387           }
V 388         }
389       }
390       .@{prefix-cls}-menu-list {
ff2b12 391         &__content {
V 392           box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.1);
393         }
394
97180e 395         &__title {
V 396           .pushpin {
397             color: rgba(0, 0, 0, 0.35);
398
399             &:hover {
400               color: rgba(0, 0, 0, 0.85);
401             }
e6db0d 402           }
V 403         }
404       }
405     }
5b8eb4 406     @border-color: @sider-dark-lighten-bg-color;
e6db0d 407
V 408     &.dark {
409       &.open {
410         .@{prefix-cls}-logo {
d5d4c4 411           // border-bottom: 1px solid @border-color;
e6db0d 412         }
V 413
0e7c57 414         > .scrollbar {
f69aae 415           border-right: 1px solid @border-color;
e6db0d 416         }
V 417       }
418       .@{prefix-cls}-menu-list {
419         background: @sider-dark-bg-color;
420
421         &__title {
422           color: @white;
423           border-bottom: none;
f69aae 424           border-bottom: 1px solid @border-color;
e6db0d 425         }
V 426       }
427     }
428
5cbfb2 429     > .scrollbar {
1e5fcd 430       height: calc(100% - @header-height - 38px);
0e7c57 431     }
V 432
433     &.mini &-module {
434       &__name {
435         display: none;
436       }
437
438       &__icon {
439         margin-bottom: 0;
440       }
5cbfb2 441     }
V 442
e6db0d 443     &-module {
V 444       position: relative;
445       padding-top: 1px;
446
447       &__item {
448         position: relative;
449         padding: 12px 0;
450         color: rgba(255, 255, 255, 0.65);
451         text-align: center;
452         cursor: pointer;
453         transition: all 0.3s ease;
454
455         &:hover {
456           color: @white;
457         }
458         // &:hover,
459         &--active {
460           font-weight: 700;
461           color: @white;
462           background: @sider-dark-darken-bg-color;
463
464           &::before {
465             position: absolute;
466             top: 0;
467             left: 0;
468             width: 3px;
469             height: 100%;
470             background: @primary-color;
471             content: '';
472           }
473         }
474       }
475
476       &__icon {
477         margin-bottom: 8px;
478         font-size: 24px;
1e5fcd 479         transition: all 0.2s;
e6db0d 480       }
V 481
482       &__name {
483         margin-bottom: 0;
484         font-size: 12px;
1e5fcd 485         transition: all 0.2s;
e6db0d 486       }
1e5fcd 487     }
V 488
489     &-trigger {
490       position: absolute;
491       bottom: 0;
492       left: 0;
493       width: 100%;
494       padding: 6px;
495       padding-left: 12px;
496       font-size: 18px;
497       color: rgba(255, 255, 255, 0.65);
498       cursor: pointer;
499       background: @sider-dark-bg-color;
500     }
501
502     &.light &-trigger {
503       color: rgba(0, 0, 0, 0.65);
504       background: #fff;
e6db0d 505     }
V 506
507     &-menu-list {
508       position: fixed;
509       top: 0;
510       width: 0;
511       width: 200px;
512       height: calc(100%);
513       background: #fff;
1e5fcd 514       transition: all 0.2s;
e6db0d 515
V 516       &__title {
517         display: flex;
518         height: @header-height;
97180e 519         // margin-left: -6px;
e6db0d 520         font-size: 18px;
V 521         color: @primary-color;
522         border-bottom: 1px solid rgb(238, 238, 238);
523         opacity: 0;
524         transition: unset;
525         align-items: center;
97180e 526         justify-content: space-between;
e6db0d 527
V 528         &.show {
97180e 529           min-width: 130px;
e6db0d 530           opacity: 1;
V 531           transition: all 0.5s ease;
532         }
97180e 533
V 534         .pushpin {
535           margin-right: 6px;
536           color: rgba(255, 255, 255, 0.65);
537           cursor: pointer;
538
539           &:hover {
540             color: #fff;
541           }
542         }
e6db0d 543       }
V 544
545       &__content {
546         height: calc(100% - @header-height) !important;
547
548         .scrollbar__wrap {
549           height: 100%;
550           overflow-x: hidden;
551         }
552
553         .scrollbar__bar.is-horizontal {
554           display: none;
555         }
556
557         .ant-menu {
558           height: 100%;
559         }
560
561         .ant-menu-inline,
562         .ant-menu-vertical,
563         .ant-menu-vertical-left {
564           border-right: 1px solid transparent;
565         }
566       }
567     }
568
569     &-drag-bar {
570       position: absolute;
ff2b12 571       top: 50px;
V 572       right: -1px;
573       width: 1px;
574       height: calc(100% - 50px);
e6db0d 575       cursor: ew-resize;
V 576       background: #f8f8f9;
577       border-top: none;
578       border-bottom: none;
579       box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15);
580     }
581   }
582 </style>