Blame view

src/layouts/default/sider/MixSider.vue 14.6 KB
1
<template>
2
  <div :class="`${prefixCls}-dom`" :style="getDomStyle"></div>
3
4
  <div
    v-click-outside="handleClickOutside"
vben authored
5
    :style="getWrapStyle"
6
7
8
9
10
    :class="[
      prefixCls,
      getMenuTheme,
      {
        open: openMenu,
vben authored
11
        mini: getCollapsed,
12
13
      },
    ]"
14
    v-bind="getMenuEvents"
15
16
  >
    <AppLogo :showTitle="false" :class="`${prefixCls}-logo`" />
vben authored
17
18
    <LayoutTrigger :class="`${prefixCls}-trigger`" />
vben authored
19
20
21
22
23
24
25
26
27
28
    <ScrollContainer>
      <ul :class="`${prefixCls}-module`">
        <li
          :class="[
            `${prefixCls}-module__item `,
            {
              [`${prefixCls}-module__item--active`]: item.path === activePath,
            },
          ]"
29
          v-bind="getItemEvents(item)"
30
31
32
          v-for="item in menuModules"
          :key="item.path"
        >
33
          <SimpleMenuTag :item="item" collapseParent dot />
34
          <Icon
35
            :class="`${prefixCls}-module__icon`"
vben authored
36
            :size="getCollapsed ? 16 : 20"
37
            :icon="item.icon || (item.meta && item.meta.icon)"
38
          />
39
40
41
          <p :class="`${prefixCls}-module__name`">
            {{ t(item.name) }}
          </p>
42
43
44
45
46
47
        </li>
      </ul>
    </ScrollContainer>

    <div :class="`${prefixCls}-menu-list`" ref="sideRef" :style="getMenuStyle">
      <div
48
        v-show="openMenu"
49
50
51
52
53
54
55
56
        :class="[
          `${prefixCls}-menu-list__title`,
          {
            show: openMenu,
          },
        ]"
      >
        <span class="text"> {{ title }}</span>
57
58
        <Icon
          :size="16"
vben authored
59
          :icon="getMixSideFixed ? 'ri:pushpin-2-fill' : 'ri:pushpin-2-line'"
60
61
62
          class="pushpin"
          @click="handleFixedMenu"
        />
63
64
      </div>
      <ScrollContainer :class="`${prefixCls}-menu-list__content`">
65
        <SimpleMenu
66
          :items="childrenMenus"
67
          :theme="getMenuTheme"
68
          mixSider
vben authored
69
          @menu-click="handleMenuClick"
70
71
72
73
74
75
76
77
78
79
80
81
        />
      </ScrollContainer>
      <div
        v-show="getShowDragBar && openMenu"
        :class="`${prefixCls}-drag-bar`"
        ref="dragBarRef"
      ></div>
    </div>
  </div>
</template>
<script lang="ts">
  import type { Menu } from '/@/router/types';
vben authored
82
  import type { CSSProperties } from 'vue';
83
  import { computed, defineComponent, onMounted, ref, unref, watch } from 'vue';
vben authored
84
  import type { RouteLocationNormalized } from 'vue-router';
85
  import { ScrollContainer } from '/@/components/Container';
86
  import { SimpleMenu, SimpleMenuTag } from '/@/components/SimpleMenu';
87
  import { Icon } from '/@/components/Icon';
88
89
  import { AppLogo } from '/@/components/Application';
  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
90
  import { usePermissionStore } from '/@/store/modules/permission';
91
  import { useDragLine } from './useLayoutSider';
92
  import { useGlobSetting } from '/@/hooks/setting';
vben authored
93
94
95
  import { useDesign } from '/@/hooks/web/useDesign';
  import { useI18n } from '/@/hooks/web/useI18n';
  import { useGo } from '/@/hooks/web/usePage';
96
  import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum';
97
  import clickOutside from '/@/directives/clickOutside';
98
  import { getChildrenMenus, getCurrentParentPath, getShallowMenus } from '/@/router/menus';
99
  import { listenerRouteChange } from '/@/logics/mitt/routeChange';
100
  import LayoutTrigger from '../trigger/index.vue';
101
102
103
104
105
106

  export default defineComponent({
    name: 'LayoutMixSider',
    components: {
      ScrollContainer,
      AppLogo,
107
      SimpleMenu,
108
      Icon,
109
      LayoutTrigger,
110
      SimpleMenuTag,
111
112
113
114
115
116
117
    },
    directives: {
      clickOutside,
    },
    setup() {
      let menuModules = ref<Menu[]>([]);
      const activePath = ref('');
118
      const childrenMenus = ref<Menu[]>([]);
119
120
121
122
123
124
125
126
127
128
129
130
131
      const openMenu = ref(false);
      const dragBarRef = ref<ElRef>(null);
      const sideRef = ref<ElRef>(null);
      const currentRoute = ref<Nullable<RouteLocationNormalized>>(null);

      const { prefixCls } = useDesign('layout-mix-sider');
      const go = useGo();
      const { t } = useI18n();
      const {
        getMenuWidth,
        getCanDrag,
        getCloseMixSidebarOnChange,
        getMenuTheme,
132
        getMixSideTrigger,
133
134
135
136
        getRealWidth,
        getMixSideFixed,
        mixSideHasChildren,
        setMenuSetting,
vben authored
137
138
        getIsMixSidebar,
        getCollapsed,
139
      } = useMenuSetting();
140
141
      const { title } = useGlobSetting();
142
      const permissionStore = usePermissionStore();
143
144
145

      useDragLine(sideRef, dragBarRef, true);
Vben authored
146
147
148
149
150
151
      const getMenuStyle = computed((): CSSProperties => {
        return {
          width: unref(openMenu) ? `${unref(getMenuWidth)}px` : 0,
          left: `${unref(getMixSideWidth)}px`,
        };
      });
152
153
      const getIsFixed = computed(() => {
154
        /* eslint-disable-next-line */
155
        mixSideHasChildren.value = unref(childrenMenus).length > 0;
156
157
        const isFixed = unref(getMixSideFixed) && unref(mixSideHasChildren);
        if (isFixed) {
158
          /* eslint-disable-next-line */
159
160
161
162
163
          openMenu.value = true;
        }
        return isFixed;
      });
vben authored
164
165
166
167
      const getMixSideWidth = computed(() => {
        return unref(getCollapsed) ? SIDE_BAR_MINI_WIDTH : SIDE_BAR_SHOW_TIT_MINI_WIDTH;
      });
Vben authored
168
169
170
171
172
      const getDomStyle = computed((): CSSProperties => {
        const fixedWidth = unref(getIsFixed) ? unref(getRealWidth) : 0;
        const width = `${unref(getMixSideWidth) + fixedWidth}px`;
        return getWrapCommonStyle(width);
      });
vben authored
173
Vben authored
174
175
176
177
      const getWrapStyle = computed((): CSSProperties => {
        const width = `${unref(getMixSideWidth)}px`;
        return getWrapCommonStyle(width);
      });
178
179
      const getMenuEvents = computed(() => {
vben authored
180
181
182
        return !unref(getMixSideFixed)
          ? {
              onMouseleave: () => {
183
                setActive(true);
vben authored
184
185
186
187
                closeMenu();
              },
            }
          : {};
188
189
      });
190
191
192
193
194
195
      const getShowDragBar = computed(() => unref(getCanDrag));

      onMounted(async () => {
        menuModules.value = await getShallowMenus();
      });
196
197
198
199
200
201
202
203
204
205
206
      // Menu changes
      watch(
        [() => permissionStore.getLastBuildMenuTime, () => permissionStore.getBackMenuList],
        async () => {
          menuModules.value = await getShallowMenus();
        },
        {
          immediate: true,
        },
      );
207
      listenerRouteChange((route) => {
208
        currentRoute.value = route;
209
        setActive(true);
210
        if (unref(getCloseMixSidebarOnChange)) {
211
          closeMenu();
212
213
214
        }
      });
vben authored
215
216
217
218
219
220
221
222
223
224
      function getWrapCommonStyle(width: string): CSSProperties {
        return {
          width,
          maxWidth: width,
          minWidth: width,
          flex: `0 0 ${width}`,
        };
      }

      // Process module menu click
225
      async function handleModuleClick(path: string, hover = false) {
226
227
        const children = await getChildrenMenus(path);
        if (unref(activePath) === path) {
228
          if (!hover) {
229
230
231
232
233
            if (!unref(openMenu)) {
              openMenu.value = true;
            } else {
              closeMenu();
            }
234
235
236
237
          } else {
            if (!unref(openMenu)) {
              openMenu.value = true;
            }
238
          }
239
240
241
242
243
244
245
246
247
          if (!unref(openMenu)) {
            setActive();
          }
        } else {
          openMenu.value = true;
          activePath.value = path;
        }

        if (!children || children.length === 0) {
248
249
          if (!hover) go(path);
          childrenMenus.value = [];
250
          closeMenu();
251
252
          return;
        }
253
        childrenMenus.value = children;
254
255
      }
vben authored
256
      // Set the currently active menu and submenu
257
      async function setActive(setChildren = false) {
258
259
        const path = currentRoute.value?.path;
        if (!path) return;
260
        activePath.value = await getCurrentParentPath(path);
261
        // hanldeModuleClick(parentPath);
vben authored
262
        if (unref(getIsMixSidebar)) {
263
264
265
266
267
          const activeMenu = unref(menuModules).find((item) => item.path === unref(activePath));
          const p = activeMenu?.path;
          if (p) {
            const children = await getChildrenMenus(p);
            if (setChildren) {
268
              childrenMenus.value = children;
vben authored
269
270
271
272

              if (unref(getMixSideFixed)) {
                openMenu.value = children.length > 0;
              }
273
274
            }
            if (children.length === 0) {
275
              childrenMenus.value = [];
276
277
278
            }
          }
        }
279
280
281
282
283
284
285
      }

      function handleMenuClick(path: string) {
        go(path);
      }

      function handleClickOutside() {
286
        setActive(true);
287
        closeMenu();
288
289
      }
290
291
292
      function getItemEvents(item: Menu) {
        if (unref(getMixSideTrigger) === 'hover') {
          return {
293
294
295
296
297
            onMouseenter: () => handleModuleClick(item.path, true),
            onClick: async () => {
              const children = await getChildrenMenus(item.path);
              if (item.path && (!children || children.length === 0)) go(item.path);
            },
298
299
300
          };
        }
        return {
301
          onClick: () => handleModuleClick(item.path),
302
303
304
        };
      }
305
306
307
308
309
310
      function handleFixedMenu() {
        setMenuSetting({
          mixSideFixed: !unref(getIsFixed),
        });
      }
vben authored
311
      // Close menu
312
313
314
315
316
317
      function closeMenu() {
        if (!unref(getIsFixed)) {
          openMenu.value = false;
        }
      }
318
319
320
321
      return {
        t,
        prefixCls,
        menuModules,
322
        handleModuleClick: handleModuleClick,
323
        activePath,
324
        childrenMenus: childrenMenus,
325
326
327
328
329
330
331
332
333
        getShowDragBar,
        handleMenuClick,
        getMenuStyle,
        handleClickOutside,
        sideRef,
        dragBarRef,
        title,
        openMenu,
        getMenuTheme,
334
335
        getItemEvents,
        getMenuEvents,
336
337
338
        getDomStyle,
        handleFixedMenu,
        getMixSideFixed,
vben authored
339
340
        getWrapStyle,
        getCollapsed,
341
342
343
344
345
346
347
348
349
350
351
352
353
354
      };
    },
  });
</script>
<style lang="less">
  @prefix-cls: ~'@{namespace}-layout-mix-sider';
  @width: 80px;
  .@{prefix-cls} {
    position: fixed;
    top: 0;
    left: 0;
    z-index: @layout-mix-sider-fixed-z-index;
    height: 100%;
    overflow: hidden;
355
    background-color: @sider-dark-bg-color;
vben authored
356
    transition: all 0.2s ease 0s;
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
    &-dom {
      height: 100%;
      overflow: hidden;
      transition: all 0.2s ease 0s;
    }

    &-logo {
      display: flex;
      height: @header-height;
      padding-left: 0 !important;
      justify-content: center;

      img {
        width: @logo-width;
        height: @logo-width;
      }
    }

    &.light {
      .@{prefix-cls}-logo {
vben authored
378
        border-bottom: 1px solid rgb(238 238 238);
379
380
381
      }

      &.open {
vben authored
382
        > .scrollbar {
vben authored
383
          border-right: 1px solid rgb(238 238 238);
384
385
386
387
388
389
        }
      }

      .@{prefix-cls}-module {
        &__item {
          font-weight: normal;
vben authored
390
          color: rgb(0 0 0 / 65%);
391
392
393

          &--active {
            color: @primary-color;
394
            background-color: unset;
395
396
397
          }
        }
      }
398
      .@{prefix-cls}-menu-list {
399
        &__content {
vben authored
400
          box-shadow: 0 0 4px 0 rgb(0 0 0 / 10%);
401
402
        }
403
404
        &__title {
          .pushpin {
vben authored
405
            color: rgb(0 0 0 / 35%);
406
407

            &:hover {
vben authored
408
              color: rgb(0 0 0 / 85%);
409
410
411
412
            }
          }
        }
      }
413
    }
Vben authored
414
    @border-color: @sider-dark-lighten-bg-color;
415
416
417
418

    &.dark {
      &.open {
        .@{prefix-cls}-logo {
419
          // border-bottom: 1px solid @border-color;
420
421
        }
vben authored
422
        > .scrollbar {
vben authored
423
          border-right: 1px solid @border-color;
424
425
426
        }
      }
      .@{prefix-cls}-menu-list {
427
        background-color: @sider-dark-bg-color;
428
429
430
431

        &__title {
          color: @white;
          border-bottom: none;
vben authored
432
          border-bottom: 1px solid @border-color;
433
434
435
436
        }
      }
    }
437
    > .scrollbar {
vben authored
438
      height: calc(100% - @header-height - 38px);
439
440
    }
vben authored
441
442
443
444
445
446
447
448
449
450
    &.mini &-module {
      &__name {
        display: none;
      }

      &__icon {
        margin-bottom: 0;
      }
    }
451
452
453
454
455
456
457
    &-module {
      position: relative;
      padding-top: 1px;

      &__item {
        position: relative;
        padding: 12px 0;
vben authored
458
        color: rgb(255 255 255 / 65%);
459
460
461
462
463
464
465
466
467
468
469
        text-align: center;
        cursor: pointer;
        transition: all 0.3s ease;

        &:hover {
          color: @white;
        }
        // &:hover,
        &--active {
          font-weight: 700;
          color: @white;
470
          background-color: @sider-dark-darken-bg-color;
471
472
473
474
475
476
477

          &::before {
            position: absolute;
            top: 0;
            left: 0;
            width: 3px;
            height: 100%;
478
            background-color: @primary-color;
479
480
481
482
483
484
485
486
            content: '';
          }
        }
      }

      &__icon {
        margin-bottom: 8px;
        font-size: 24px;
vben authored
487
        transition: all 0.2s;
488
489
490
491
492
      }

      &__name {
        margin-bottom: 0;
        font-size: 12px;
vben authored
493
        transition: all 0.2s;
494
495
496
      }
    }
vben authored
497
498
499
500
501
    &-trigger {
      position: absolute;
      bottom: 0;
      left: 0;
      width: 100%;
502
      font-size: 14px;
vben authored
503
      color: rgb(255 255 255 / 65%);
504
      text-align: center;
vben authored
505
      cursor: pointer;
506
507
508
      background-color: @trigger-dark-bg-color;
      height: 36px;
      line-height: 36px;
vben authored
509
510
511
    }

    &.light &-trigger {
vben authored
512
      color: rgb(0 0 0 / 65%);
513
      background-color: #fff;
514
      border-top: 1px solid #eee;
vben authored
515
516
    }
517
518
519
520
521
    &-menu-list {
      position: fixed;
      top: 0;
      width: 200px;
      height: calc(100%);
522
      background-color: #fff;
vben authored
523
      transition: all 0.2s;
524
525
526
527

      &__title {
        display: flex;
        height: @header-height;
528
        // margin-left: -6px;
529
530
        font-size: 18px;
        color: @primary-color;
vben authored
531
532
        border-bottom: 1px solid rgb(238 238 238);
        opacity: 0%;
533
534
        transition: unset;
        align-items: center;
535
        justify-content: space-between;
536
537

        &.show {
538
          min-width: 130px;
vben authored
539
          opacity: 100%;
540
541
          transition: all 0.5s ease;
        }
542
543
544

        .pushpin {
          margin-right: 6px;
vben authored
545
          color: rgb(255 255 255 / 65%);
546
547
548
549
550
551
          cursor: pointer;

          &:hover {
            color: #fff;
          }
        }
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
      }

      &__content {
        height: calc(100% - @header-height) !important;

        .scrollbar__wrap {
          height: 100%;
          overflow-x: hidden;
        }

        .scrollbar__bar.is-horizontal {
          display: none;
        }

        .ant-menu {
          height: 100%;
        }

        .ant-menu-inline,
        .ant-menu-vertical,
        .ant-menu-vertical-left {
          border-right: 1px solid transparent;
        }
      }
    }

    &-drag-bar {
      position: absolute;
580
581
582
583
      top: 50px;
      right: -1px;
      width: 1px;
      height: calc(100% - 50px);
584
      cursor: ew-resize;
585
      background-color: #f8f8f9;
586
587
      border-top: none;
      border-bottom: none;
vben authored
588
      box-shadow: 0 0 4px 0 rgb(28 36 56 / 15%);
589
590
591
    }
  }
</style>