Blame view

src/layouts/default/sider/MixSider.vue 14.5 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';
vben authored
87
  import Icon from '@/components/Icon/Icon.vue';
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
      const openMenu = ref(false);
vben authored
120
121
122
      const dragBarRef = ref(null);
      const sideRef = ref(null);
      const currentRoute = ref<RouteLocationNormalized | null>(null);
123
124
125
126
127
128
129
130
131

      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
      };
    },
  });
</script>
<style lang="less">
  @prefix-cls: ~'@{namespace}-layout-mix-sider';
  @width: 80px;
  .@{prefix-cls} {
    position: fixed;
vben authored
350
    z-index: @layout-mix-sider-fixed-z-index;
351
352
353
354
    top: 0;
    left: 0;
    height: 100%;
    overflow: hidden;
vben authored
355
    transition: all 0.2s ease 0s;
vben authored
356
    background-color: @sider-dark-bg-color;
357
358
359
360
361
362
363
364
365
    &-dom {
      height: 100%;
      overflow: hidden;
      transition: all 0.2s ease 0s;
    }

    &-logo {
      display: flex;
vben authored
366
      justify-content: center;
367
368
369
370
371
372
373
374
375
376
377
      height: @header-height;
      padding-left: 0 !important;

      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
        }
      }

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

          &--active {
393
            background-color: unset;
vben authored
394
            color: @primary-color;
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

        &__title {
          border-bottom: none;
vben authored
431
          border-bottom: 1px solid @border-color;
vben authored
432
          color: @white;
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
        transition: all 0.3s ease;
vben authored
459
        color: rgb(255 255 255 / 65%);
460
461
462
463
464
465
466
467
        text-align: center;
        cursor: pointer;

        &:hover {
          color: @white;
        }
        // &:hover,
        &--active {
468
          background-color: @sider-dark-darken-bg-color;
vben authored
469
470
          color: @white;
          font-weight: 700;
471
472

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

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

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

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

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

        &.show {
538
          min-width: 130px;
539
          transition: all 0.5s ease;
vben authored
540
          opacity: 1;
541
        }
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
585
      border-top: none;
      border-bottom: none;
vben authored
586
      background-color: #f8f8f9;
vben authored
587
      box-shadow: 0 0 4px 0 rgb(28 36 56 / 15%);
vben authored
588
      cursor: ew-resize;
589
590
591
    }
  }
</style>