Commit ff2b12b4094de8d3cfd954b04a603a01557ce118

Authored by vben
1 parent 056fc131

refactor(menu): added component. Solve the menu stuck problem

Showing 43 changed files with 1793 additions and 213 deletions
CHANGELOG.zh_CN.md
1 1 ## Wip
2 2  
  3 +### ✨ Refactor
  4 +
  5 +- 新增 `SimpleMenu`组件替代左侧菜单组件(顶部菜单没有替换,功能尽量做到简单不卡)。解决菜单卡顿问题。
  6 +
3 7 ### 🐛 Bug Fixes
4 8  
5 9 - 修复 `TableAction`图标问题
... ...
mock/sys/user.ts
... ... @@ -39,7 +39,7 @@ export default [
39 39 // mock user login
40 40 {
41 41 url: '/api/login',
42   - timeout: 1000,
  42 + timeout: 200,
43 43 method: 'post',
44 44 response: ({ body }) => {
45 45 const { username, password } = body;
... ... @@ -62,7 +62,6 @@ export default [
62 62 },
63 63 {
64 64 url: '/api/getUserInfoById',
65   - timeout: 200,
66 65 method: 'get',
67 66 response: ({ query }) => {
68 67 const { userId } = query;
... ...
package.json
... ... @@ -19,7 +19,7 @@
19 19 },
20 20 "dependencies": {
21 21 "@iconify/iconify": "^2.0.0-rc.6",
22   - "@vueuse/core": "^4.0.5",
  22 + "@vueuse/core": "^4.0.8",
23 23 "ant-design-vue": "^2.0.0-rc.8",
24 24 "apexcharts": "^3.23.1",
25 25 "axios": "^0.21.1",
... ... @@ -45,12 +45,12 @@
45 45 "devDependencies": {
46 46 "@commitlint/cli": "^11.0.0",
47 47 "@commitlint/config-conventional": "^11.0.0",
48   - "@iconify/json": "^1.1.286",
  48 + "@iconify/json": "^1.1.287",
49 49 "@ls-lint/ls-lint": "^1.9.2",
50 50 "@purge-icons/generated": "^0.5.1",
51 51 "@types/echarts": "^4.9.3",
52 52 "@types/fs-extra": "^9.0.6",
53   - "@types/http-proxy": "^1.17.4",
  53 + "@types/http-proxy": "^1.17.5",
54 54 "@types/koa-static": "^4.0.1",
55 55 "@types/lodash-es": "^4.17.4",
56 56 "@types/mockjs": "^1.0.3",
... ... @@ -63,24 +63,24 @@
63 63 "@typescript-eslint/eslint-plugin": "^4.13.0",
64 64 "@typescript-eslint/parser": "^4.13.0",
65 65 "@vitejs/plugin-legacy": "^1.2.1",
66   - "@vitejs/plugin-vue": "^1.0.5",
  66 + "@vitejs/plugin-vue": "^1.0.6",
67 67 "@vitejs/plugin-vue-jsx": "^1.0.2",
68 68 "@vue/compiler-sfc": "^3.0.5",
69 69 "@vuedx/typecheck": "^0.5.0",
70 70 "@vuedx/typescript-plugin-vue": "^0.5.0",
71 71 "autoprefixer": "^10.2.1",
72   - "commitizen": "^4.2.2",
  72 + "commitizen": "^4.2.3",
73 73 "conventional-changelog-cli": "^2.1.1",
74 74 "conventional-changelog-custom-config": "^0.3.1",
75 75 "cross-env": "^7.0.3",
76 76 "dotenv": "^8.2.0",
77   - "eslint": "^7.17.0",
  77 + "eslint": "^7.18.0",
78 78 "eslint-config-prettier": "^7.1.0",
79 79 "eslint-plugin-prettier": "^3.3.1",
80 80 "eslint-plugin-vue": "^7.4.1",
81 81 "esno": "^0.4.0",
82 82 "fs-extra": "^9.0.1",
83   - "husky": "^4.3.7",
  83 + "husky": "^4.3.8",
84 84 "koa-static": "^5.0.0",
85 85 "less": "^4.1.0",
86 86 "lint-staged": "^10.5.3",
... ... @@ -96,11 +96,11 @@
96 96 "stylelint-order": "^4.1.0",
97 97 "ts-node": "^9.1.0",
98 98 "typescript": "^4.1.3",
99   - "vite": "^2.0.0-beta.27",
  99 + "vite": "^2.0.0-beta.30",
100 100 "vite-plugin-html": "^2.0.0-beta.5",
101 101 "vite-plugin-mock": "^2.0.0-beta.3",
102 102 "vite-plugin-purge-icons": "^0.5.1",
103   - "vite-plugin-pwa": "^0.3.6",
  103 + "vite-plugin-pwa": "^0.3.8",
104 104 "vue-eslint-parser": "^7.3.0",
105 105 "yargs": "^16.2.0"
106 106 },
... ...
src/components/Application/src/search/AppSearchModal.vue
... ... @@ -2,54 +2,52 @@
2 2 <Teleport to="body">
3 3 <transition name="zoom-fade" mode="out-in">
4 4 <div :class="getClass" @click.stop v-if="visible">
5   - <ClickOutSide @clickOutside="handleClose">
6   - <div :class="`${prefixCls}-content`">
7   - <div :class="`${prefixCls}-input__wrapper`">
8   - <a-input
9   - :class="`${prefixCls}-input`"
10   - :placeholder="t('common.searchText')"
11   - allow-clear
12   - @change="handleSearch"
13   - >
14   - <template #prefix>
15   - <SearchOutlined />
16   - </template>
17   - </a-input>
18   - <span :class="`${prefixCls}-cancel`" @click="handleClose">{{
19   - t('common.cancelText')
20   - }}</span>
21   - </div>
  5 + <div :class="`${prefixCls}-content`" v-click-outside="handleClose">
  6 + <div :class="`${prefixCls}-input__wrapper`">
  7 + <a-input
  8 + :class="`${prefixCls}-input`"
  9 + :placeholder="t('common.searchText')"
  10 + allow-clear
  11 + @change="handleSearch"
  12 + >
  13 + <template #prefix>
  14 + <SearchOutlined />
  15 + </template>
  16 + </a-input>
  17 + <span :class="`${prefixCls}-cancel`" @click="handleClose">{{
  18 + t('common.cancelText')
  19 + }}</span>
  20 + </div>
22 21  
23   - <div :class="`${prefixCls}-not-data`" v-show="getIsNotData">
24   - {{ t('component.app.searchNotData') }}
25   - </div>
26   - <ul :class="`${prefixCls}-list`" v-show="!getIsNotData" ref="scrollWrap">
27   - <li
28   - :ref="setRefs(index)"
29   - v-for="(item, index) in searchResult"
30   - :key="item.path"
31   - :data-index="index"
32   - @mouseenter="handleMouseenter"
33   - @click="handleEnter"
34   - :class="[
35   - `${prefixCls}-list__item`,
36   - {
37   - [`${prefixCls}-list__item--active`]: activeIndex === index,
38   - },
39   - ]"
40   - >
41   - <div :class="`${prefixCls}-list__item-icon`">
42   - <g-icon :icon="item.icon || 'mdi:form-select'" :size="20" />
43   - </div>
44   - <div :class="`${prefixCls}-list__item-text`">{{ item.name }}</div>
45   - <div :class="`${prefixCls}-list__item-enter`">
46   - <g-icon icon="ant-design:enter-outlined" :size="20" />
47   - </div>
48   - </li>
49   - </ul>
50   - <AppSearchFooter />
  22 + <div :class="`${prefixCls}-not-data`" v-show="getIsNotData">
  23 + {{ t('component.app.searchNotData') }}
51 24 </div>
52   - </ClickOutSide>
  25 + <ul :class="`${prefixCls}-list`" v-show="!getIsNotData" ref="scrollWrap">
  26 + <li
  27 + :ref="setRefs(index)"
  28 + v-for="(item, index) in searchResult"
  29 + :key="item.path"
  30 + :data-index="index"
  31 + @mouseenter="handleMouseenter"
  32 + @click="handleEnter"
  33 + :class="[
  34 + `${prefixCls}-list__item`,
  35 + {
  36 + [`${prefixCls}-list__item--active`]: activeIndex === index,
  37 + },
  38 + ]"
  39 + >
  40 + <div :class="`${prefixCls}-list__item-icon`">
  41 + <g-icon :icon="item.icon || 'mdi:form-select'" :size="20" />
  42 + </div>
  43 + <div :class="`${prefixCls}-list__item-text`">{{ item.name }}</div>
  44 + <div :class="`${prefixCls}-list__item-enter`">
  45 + <g-icon icon="ant-design:enter-outlined" :size="20" />
  46 + </div>
  47 + </li>
  48 + </ul>
  49 + <AppSearchFooter />
  50 + </div>
53 51 </div>
54 52 </transition>
55 53 </Teleport>
... ... @@ -63,17 +61,20 @@
63 61 import { SearchOutlined } from '@ant-design/icons-vue';
64 62 import AppSearchFooter from './AppSearchFooter.vue';
65 63 import { useI18n } from '/@/hooks/web/useI18n';
66   - import { ClickOutSide } from '/@/components/ClickOutSide';
67 64 import { useAppInject } from '/@/hooks/web/useAppInject';
  65 + import clickOutside from '/@/directives/clickOutside';
68 66  
69 67 export default defineComponent({
70 68 name: 'AppSearchModal',
71   - components: { SearchOutlined, ClickOutSide, AppSearchFooter },
  69 + components: { SearchOutlined, AppSearchFooter },
72 70 emits: ['close'],
73 71  
74 72 props: {
75 73 visible: Boolean,
76 74 },
  75 + directives: {
  76 + clickOutside,
  77 + },
77 78 setup(_, { emit }) {
78 79 const scrollWrap = ref<ElRef>(null);
79 80 const { prefixCls } = useDesign('app-search-modal');
... ...
src/components/MenuPlus/index.ts deleted 100644 → 0
1   -export { default as Menu } from './src/index.vue';
src/components/MenuPlus/src/index.vue deleted 100644 → 0
1   -<template>
2   - <ul :class="getClass" :style="getStyle">
3   - <slot></slot>
4   - </ul>
5   -</template>
6   -
7   -<script lang="ts">
8   - import { defineComponent, ref, computed, CSSProperties, unref } from 'vue';
9   - import { useDesign } from '/@/hooks/web/useDesign';
10   - import { propTypes } from '/@/utils/propTypes';
11   - export default defineComponent({
12   - props: {
13   - mode: propTypes.oneOf(['horizontal', 'vertical']).def('vertical'),
14   - theme: propTypes.oneOf(['light', 'dark', 'primary']).def('light'),
15   - activeName: propTypes.oneOfType([propTypes.string, propTypes.number]),
16   - openNames: propTypes.array.def([]),
17   - accordion: propTypes.bool,
18   - width: propTypes.string.def('210px'),
19   - },
20   - setup(props) {
21   - const currentActiveName = ref(props.activeName);
22   - const openedNames = ref<string[]>();
23   -
24   - const { prefixCls } = useDesign('menu');
25   -
26   - const getClass = computed(() => {
27   - const { theme, mode } = props;
28   - let curTheme = theme;
29   - if (mode === 'vertical' && theme === 'primary') {
30   - curTheme = 'light';
31   - }
32   - return [
33   - prefixCls,
34   - `${prefixCls}-${curTheme}`,
35   - {
36   - [`${prefixCls}-${mode}`]: mode,
37   - },
38   - ];
39   - });
40   -
41   - const getStyle = computed(
42   - (): CSSProperties => {
43   - const { mode, width } = props;
44   - if (mode === 'vertical') {
45   - return {
46   - width: width,
47   - };
48   - }
49   - return {};
50   - }
51   - );
52   -
53   - function updateActiveName() {
54   - if (unref(currentActiveName) === undefined) {
55   - currentActiveName.value = -1;
56   - }
57   - }
58   -
59   - function updateOpened() {}
60   -
61   - return { getClass, getStyle };
62   - },
63   - });
64   -</script>
src/components/SimpleMenu/index.ts 0 → 100644
  1 +export { default as SimpleMenu } from './src/SimpleMenu.vue';
... ...
src/components/SimpleMenu/src/SimpleMenu.vue 0 → 100644
  1 +<template>
  2 + <Menu
  3 + v-bind="getBindValues"
  4 + @select="handleSelect"
  5 + :activeName="activeName"
  6 + :openNames="openNames"
  7 + :class="prefixCls"
  8 + :activeSubMenuNames="activeSubMenuNames"
  9 + >
  10 + <template v-for="item in items" :key="item.path">
  11 + <SimpleSubMenu
  12 + :item="item"
  13 + :parent="true"
  14 + :collapsedShowTitle="collapsedShowTitle"
  15 + :collapse="collapse"
  16 + />
  17 + </template>
  18 + </Menu>
  19 +</template>
  20 +<script lang="ts">
  21 + import type { PropType } from 'vue';
  22 + import type { MenuState } from './types';
  23 + import type { Menu as MenuType } from '/@/router/types';
  24 +
  25 + import { defineComponent, computed, ref, unref, reactive, toRefs, watch } from 'vue';
  26 + import { useDesign } from '/@/hooks/web/useDesign';
  27 +
  28 + import Menu from './components/Menu.vue';
  29 + import SimpleSubMenu from './SimpleSubMenu.vue';
  30 + import { listenerLastChangeTab } from '/@/logics/mitt/tabChange';
  31 + import { propTypes } from '/@/utils/propTypes';
  32 + import { REDIRECT_NAME } from '/@/router/constant';
  33 + import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router';
  34 + import { isFunction } from '/@/utils/is';
  35 +
  36 + import { useOpenKeys } from './useOpenKeys';
  37 + export default defineComponent({
  38 + name: 'SimpleMenu',
  39 + inheritAttrs: false,
  40 + components: {
  41 + Menu,
  42 + SimpleSubMenu,
  43 + },
  44 + props: {
  45 + items: {
  46 + type: Array as PropType<MenuType[]>,
  47 + default: () => [],
  48 + },
  49 + collapse: propTypes.bool,
  50 + mixSider: propTypes.bool,
  51 + theme: propTypes.string,
  52 + accordion: propTypes.bool.def(true),
  53 + collapsedShowTitle: propTypes.bool,
  54 + beforeClickFn: {
  55 + type: Function as PropType<(key: string) => Promise<boolean>>,
  56 + },
  57 + },
  58 + setup(props, { attrs, emit }) {
  59 + const currentActiveMenu = ref('');
  60 + const isClickGo = ref(false);
  61 +
  62 + const menuState = reactive<MenuState>({
  63 + activeName: '',
  64 + openNames: [],
  65 + activeSubMenuNames: [],
  66 + });
  67 +
  68 + const { currentRoute } = useRouter();
  69 + const { prefixCls } = useDesign('simple-menu');
  70 + const { items, accordion, mixSider } = toRefs(props);
  71 + const { setOpenKeys } = useOpenKeys(menuState, items, accordion, mixSider);
  72 +
  73 + const getBindValues = computed(() => ({ ...attrs, ...props }));
  74 +
  75 + watch(
  76 + () => props.collapse,
  77 + (collapse) => {
  78 + if (collapse) {
  79 + menuState.openNames = [];
  80 + } else {
  81 + setOpenKeys(currentRoute.value.path);
  82 + }
  83 + },
  84 + { immediate: true }
  85 + );
  86 +
  87 + listenerLastChangeTab((route) => {
  88 + if (route.name === REDIRECT_NAME) return;
  89 +
  90 + currentActiveMenu.value = route.meta?.currentActiveMenu;
  91 + handleMenuChange(route);
  92 +
  93 + if (unref(currentActiveMenu)) {
  94 + menuState.activeName = unref(currentActiveMenu);
  95 + setOpenKeys(unref(currentActiveMenu));
  96 + }
  97 + });
  98 +
  99 + async function handleMenuChange(route?: RouteLocationNormalizedLoaded) {
  100 + if (unref(isClickGo)) {
  101 + isClickGo.value = false;
  102 + return;
  103 + }
  104 + const path = (route || unref(currentRoute)).path;
  105 + menuState.activeName = path;
  106 +
  107 + setOpenKeys(path);
  108 + // if (unref(currentActiveMenu)) return;
  109 + }
  110 +
  111 + async function handleSelect(key: string) {
  112 + const { beforeClickFn } = props;
  113 + if (beforeClickFn && isFunction(beforeClickFn)) {
  114 + const flag = await beforeClickFn(key);
  115 + if (!flag) return;
  116 + }
  117 + emit('menuClick', key);
  118 +
  119 + isClickGo.value = true;
  120 + setOpenKeys(key);
  121 + menuState.activeName = key;
  122 + }
  123 +
  124 + return {
  125 + prefixCls,
  126 + getBindValues,
  127 + handleSelect,
  128 + ...toRefs(menuState),
  129 + };
  130 + },
  131 + });
  132 +</script>
  133 +<style lang="less">
  134 + @import './index.less';
  135 +</style>
... ...
src/components/SimpleMenu/src/SimpleMenuTag.vue 0 → 100644
  1 +<template>
  2 + <span :class="getTagClass" v-if="getShowTag">{{ getContent }}</span>
  3 +</template>
  4 +<script lang="ts">
  5 + import type { Menu } from '/@/router/types';
  6 + import type { PropType } from 'vue';
  7 +
  8 + import { defineComponent, computed } from 'vue';
  9 +
  10 + import { useDesign } from '/@/hooks/web/useDesign';
  11 +
  12 + export default defineComponent({
  13 + name: 'SimpleMenuTag',
  14 + props: {
  15 + item: {
  16 + type: Object as PropType<Menu>,
  17 + default: {},
  18 + },
  19 + collapseParent: {
  20 + type: Boolean as PropType<boolean>,
  21 + default: false,
  22 + },
  23 + },
  24 + setup(props) {
  25 + const { prefixCls } = useDesign('simple-menu');
  26 +
  27 + const getShowTag = computed(() => {
  28 + const { item } = props;
  29 +
  30 + if (!item) return false;
  31 +
  32 + const { tag } = item;
  33 + if (!tag) return false;
  34 +
  35 + const { dot, content } = tag;
  36 + if (!dot && !content) return false;
  37 + return true;
  38 + });
  39 +
  40 + const getContent = computed(() => {
  41 + if (!getShowTag.value) return '';
  42 + const { item, collapseParent } = props;
  43 + const { tag } = item;
  44 + const { dot, content } = tag!;
  45 + return dot || collapseParent ? '' : content;
  46 + });
  47 +
  48 + const getTagClass = computed(() => {
  49 + const { item, collapseParent } = props;
  50 + const { tag = {} } = item || {};
  51 + const { dot, type = 'error' } = tag;
  52 + const tagCls = `${prefixCls}-tag`;
  53 + return [
  54 + tagCls,
  55 +
  56 + [`${tagCls}--${type}`],
  57 + {
  58 + [`${tagCls}--collapse`]: collapseParent,
  59 + [`${tagCls}--dot`]: dot,
  60 + },
  61 + ];
  62 + });
  63 + return {
  64 + getTagClass,
  65 + getShowTag,
  66 + getContent,
  67 + };
  68 + },
  69 + });
  70 +</script>
... ...
src/components/SimpleMenu/src/SimpleSubMenu.vue 0 → 100644
  1 +<template>
  2 + <MenuItem
  3 + :name="item.path"
  4 + v-if="!menuHasChildren(item) && getShowMenu"
  5 + v-bind="$props"
  6 + :class="getLevelClass"
  7 + >
  8 + <Icon v-if="getIcon" :icon="getIcon" :size="16" />
  9 + <div v-if="collapsedShowTitle && getIsCollapseParent" class="mt-1 collapse-title">
  10 + {{ getI18nName }}
  11 + </div>
  12 + <template #title>
  13 + <span :class="['ml-2']">
  14 + {{ getI18nName }}
  15 + </span>
  16 + <SimpleMenuTag :item="item" :collapseParent="getIsCollapseParent" />
  17 + </template>
  18 + </MenuItem>
  19 + <SubMenu
  20 + :name="item.path"
  21 + v-if="menuHasChildren(item) && getShowMenu"
  22 + :class="[getLevelClass, theme]"
  23 + :collapsedShowTitle="collapsedShowTitle"
  24 + >
  25 + <template #title>
  26 + <Icon v-if="getIcon" :icon="getIcon" :size="16" />
  27 +
  28 + <div v-if="collapsedShowTitle && getIsCollapseParent" class="mt-2 collapse-title">
  29 + {{ getI18nName }}
  30 + </div>
  31 +
  32 + <span v-show="getShowSubTitle" :class="['ml-2', `${prefixCls}-sub-title`]">
  33 + {{ getI18nName }}
  34 + </span>
  35 + <SimpleMenuTag :item="item" :collapseParent="!!collapse && !!parent" />
  36 + </template>
  37 + <template v-for="childrenItem in item.children || []" :key="childrenItem.path">
  38 + <SimpleSubMenu v-bind="$props" :item="childrenItem" :parent="false" />
  39 + </template>
  40 + </SubMenu>
  41 +</template>
  42 +<script lang="ts">
  43 + import type { PropType } from 'vue';
  44 + import type { Menu } from '/@/router/types';
  45 +
  46 + import { defineComponent, computed } from 'vue';
  47 + import { useDesign } from '/@/hooks/web/useDesign';
  48 + import Icon from '/@/components/Icon/index';
  49 +
  50 + import MenuItem from './components/MenuItem.vue';
  51 + import SubMenu from './components/SubMenuItem.vue';
  52 + import { propTypes } from '/@/utils/propTypes';
  53 + import { useI18n } from '/@/hooks/web/useI18n';
  54 + import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
  55 + const { t } = useI18n();
  56 +
  57 + export default defineComponent({
  58 + name: 'SimpleSubMenu',
  59 + components: {
  60 + SubMenu,
  61 + MenuItem,
  62 + SimpleMenuTag: createAsyncComponent(() => import('./SimpleMenuTag.vue')),
  63 + Icon,
  64 + },
  65 + props: {
  66 + item: {
  67 + type: Object as PropType<Menu>,
  68 + default: {},
  69 + },
  70 + parent: propTypes.bool,
  71 + collapsedShowTitle: propTypes.bool,
  72 + collapse: propTypes.bool,
  73 + theme: propTypes.oneOf(['dark', 'light']),
  74 + },
  75 + setup(props) {
  76 + const { prefixCls } = useDesign('simple-menu');
  77 +
  78 + const getShowMenu = computed(() => {
  79 + return !props.item.meta?.hideMenu;
  80 + });
  81 +
  82 + const getIcon = computed(() => props.item?.icon);
  83 + const getI18nName = computed(() => t(props.item?.name));
  84 + const getShowSubTitle = computed(() => !props.collapse || !props.parent);
  85 + const getIsCollapseParent = computed(() => !!props.collapse && !!props.parent);
  86 + const getLevelClass = computed(() => {
  87 + return [
  88 + {
  89 + [`${prefixCls}__parent`]: props.parent,
  90 + [`${prefixCls}__children`]: !props.parent,
  91 + },
  92 + ];
  93 + });
  94 +
  95 + function menuHasChildren(menuTreeItem: Menu): boolean {
  96 + return (
  97 + Reflect.has(menuTreeItem, 'children') &&
  98 + !!menuTreeItem.children &&
  99 + menuTreeItem.children.length > 0
  100 + );
  101 + }
  102 +
  103 + return {
  104 + prefixCls,
  105 + menuHasChildren,
  106 + getShowMenu,
  107 + getIcon,
  108 + getI18nName,
  109 + getShowSubTitle,
  110 + getLevelClass,
  111 + getIsCollapseParent,
  112 + };
  113 + },
  114 + });
  115 +</script>
... ...
src/components/SimpleMenu/src/components/Menu.vue 0 → 100644
  1 +<template>
  2 + <ul :class="getClass">
  3 + <slot></slot>
  4 + </ul>
  5 +</template>
  6 +
  7 +<script lang="ts">
  8 + import type { PropType } from 'vue';
  9 + import type { SubMenuProvider } from './types';
  10 + import {
  11 + defineComponent,
  12 + ref,
  13 + computed,
  14 + onMounted,
  15 + watchEffect,
  16 + watch,
  17 + nextTick,
  18 + getCurrentInstance,
  19 + provide,
  20 + } from 'vue';
  21 + import { useDesign } from '/@/hooks/web/useDesign';
  22 + import { propTypes } from '/@/utils/propTypes';
  23 + import { createSimpleRootMenuContext } from './useSimpleMenuContext';
  24 + import Mitt from '/@/utils/mitt';
  25 + import { isString } from '/@/utils/is';
  26 + export default defineComponent({
  27 + name: 'Menu',
  28 + props: {
  29 + theme: propTypes.oneOf(['light', 'dark']).def('light'),
  30 + activeName: propTypes.oneOfType([propTypes.string, propTypes.number]),
  31 + openNames: {
  32 + type: Array as PropType<string[]>,
  33 + default: [],
  34 + },
  35 + accordion: propTypes.bool.def(true),
  36 + width: propTypes.string.def('100%'),
  37 + collapsedWidth: propTypes.string.def('48px'),
  38 + indentSize: propTypes.number.def(16),
  39 + collapse: propTypes.bool.def(true),
  40 + activeSubMenuNames: {
  41 + type: Array as PropType<(string | number)[]>,
  42 + default: [],
  43 + },
  44 + },
  45 + emits: ['select', 'open-change'],
  46 + setup(props, { emit }) {
  47 + const rootMenuEmitter = new Mitt();
  48 + const instance = getCurrentInstance();
  49 +
  50 + const currentActiveName = ref<string | number>('');
  51 + const openedNames = ref<string[]>([]);
  52 +
  53 + const { prefixCls } = useDesign('menu');
  54 +
  55 + const isRemoveAllPopup = ref(false);
  56 +
  57 + createSimpleRootMenuContext({
  58 + rootMenuEmitter: rootMenuEmitter,
  59 + activeName: currentActiveName,
  60 + });
  61 +
  62 + const getClass = computed(() => {
  63 + const { theme } = props;
  64 + return [
  65 + prefixCls,
  66 + `${prefixCls}-${theme}`,
  67 + `${prefixCls}-vertical`,
  68 + {
  69 + [`${prefixCls}-collapse`]: props.collapse,
  70 + },
  71 + ];
  72 + });
  73 +
  74 + watchEffect(() => {
  75 + openedNames.value = props.openNames;
  76 + });
  77 +
  78 + watchEffect(() => {
  79 + if (props.activeName) {
  80 + currentActiveName.value = props.activeName;
  81 + }
  82 + });
  83 +
  84 + watch(
  85 + () => props.openNames,
  86 + () => {
  87 + nextTick(() => {
  88 + updateOpened();
  89 + });
  90 + }
  91 + );
  92 +
  93 + function updateOpened() {
  94 + rootMenuEmitter.emit('on-update-opened', openedNames.value);
  95 + }
  96 +
  97 + function addSubMenu(name: string) {
  98 + if (openedNames.value.includes(name)) return;
  99 + openedNames.value.push(name);
  100 + updateOpened();
  101 + }
  102 +
  103 + function removeSubMenu(name: string) {
  104 + openedNames.value = openedNames.value.filter((item) => item !== name);
  105 + updateOpened();
  106 + }
  107 +
  108 + function removeAll() {
  109 + openedNames.value = [];
  110 + updateOpened();
  111 + }
  112 +
  113 + function sliceIndex(index: number) {
  114 + if (index === -1) return;
  115 + openedNames.value = openedNames.value.slice(0, index + 1);
  116 + updateOpened();
  117 + }
  118 +
  119 + provide<SubMenuProvider>(`subMenu:${instance?.uid}`, {
  120 + addSubMenu,
  121 + removeSubMenu,
  122 + getOpenNames: () => openedNames.value,
  123 + removeAll,
  124 + isRemoveAllPopup,
  125 + sliceIndex,
  126 + level: 0,
  127 + props,
  128 + });
  129 +
  130 + onMounted(() => {
  131 + openedNames.value = !props.collapse ? [...props.openNames] : [];
  132 + updateOpened();
  133 + rootMenuEmitter.on('on-menu-item-select', (name: string) => {
  134 + currentActiveName.value = name;
  135 +
  136 + nextTick(() => {
  137 + props.collapse && removeAll();
  138 + });
  139 + emit('select', name);
  140 + });
  141 + });
  142 +
  143 + return { getClass, openedNames };
  144 + },
  145 + });
  146 +</script>
  147 +<style lang="less">
  148 + @import './menu.less';
  149 +</style>
... ...
src/components/SimpleMenu/src/components/MenuCollapseTransition.vue 0 → 100644
  1 +<template>
  2 + <transition mode="out-in" v-on="on">
  3 + <slot></slot>
  4 + </transition>
  5 +</template>
  6 +<script lang="ts">
  7 + import { defineComponent } from 'vue';
  8 + import { addClass, removeClass } from '/@/utils/domUtils';
  9 +
  10 + export default defineComponent({
  11 + name: 'MenuCollapseTransition',
  12 + setup() {
  13 + return {
  14 + on: {
  15 + beforeEnter(el: any) {
  16 + addClass(el, 'collapse-transition');
  17 + if (!el.dataset) el.dataset = {};
  18 +
  19 + el.dataset.oldPaddingTop = el.style.paddingTop;
  20 + el.dataset.oldPaddingBottom = el.style.paddingBottom;
  21 +
  22 + el.style.height = '0';
  23 + el.style.paddingTop = 0;
  24 + el.style.paddingBottom = 0;
  25 + },
  26 +
  27 + enter(el: any) {
  28 + el.dataset.oldOverflow = el.style.overflow;
  29 + if (el.scrollHeight !== 0) {
  30 + el.style.height = el.scrollHeight + 'px';
  31 + el.style.paddingTop = el.dataset.oldPaddingTop;
  32 + el.style.paddingBottom = el.dataset.oldPaddingBottom;
  33 + } else {
  34 + el.style.height = '';
  35 + el.style.paddingTop = el.dataset.oldPaddingTop;
  36 + el.style.paddingBottom = el.dataset.oldPaddingBottom;
  37 + }
  38 +
  39 + el.style.overflow = 'hidden';
  40 + },
  41 +
  42 + afterEnter(el: any) {
  43 + removeClass(el, 'collapse-transition');
  44 + el.style.height = '';
  45 + el.style.overflow = el.dataset.oldOverflow;
  46 + },
  47 +
  48 + beforeLeave(el: any) {
  49 + if (!el.dataset) el.dataset = {};
  50 + el.dataset.oldPaddingTop = el.style.paddingTop;
  51 + el.dataset.oldPaddingBottom = el.style.paddingBottom;
  52 + el.dataset.oldOverflow = el.style.overflow;
  53 +
  54 + el.style.height = el.scrollHeight + 'px';
  55 + el.style.overflow = 'hidden';
  56 + },
  57 +
  58 + leave(el: any) {
  59 + if (el.scrollHeight !== 0) {
  60 + addClass(el, 'collapse-transition');
  61 + el.style.height = 0;
  62 + el.style.paddingTop = 0;
  63 + el.style.paddingBottom = 0;
  64 + }
  65 + },
  66 +
  67 + afterLeave(el: any) {
  68 + removeClass(el, 'collapse-transition');
  69 + el.style.height = '';
  70 + el.style.overflow = el.dataset.oldOverflow;
  71 + el.style.paddingTop = el.dataset.oldPaddingTop;
  72 + el.style.paddingBottom = el.dataset.oldPaddingBottom;
  73 + },
  74 + },
  75 + };
  76 + },
  77 + });
  78 +</script>
... ...
src/components/SimpleMenu/src/components/MenuItem.vue 0 → 100644
  1 +<template>
  2 + <li :class="getClass" @click.stop="handleClickItem" :style="getCollapse ? {} : getItemStyle">
  3 + <Tooltip placement="right" v-if="showTooptip">
  4 + <template #title>
  5 + <slot name="title"></slot>
  6 + </template>
  7 + <div :class="`${prefixCls}-tooltip`">
  8 + <slot />
  9 + </div>
  10 + </Tooltip>
  11 +
  12 + <template v-else>
  13 + <slot></slot>
  14 + <slot name="title"></slot>
  15 + </template>
  16 + </li>
  17 +</template>
  18 +
  19 +<script lang="ts">
  20 + import { PropType } from 'vue';
  21 + import { defineComponent, ref, computed, unref, getCurrentInstance, watch } from 'vue';
  22 + import { useDesign } from '/@/hooks/web/useDesign';
  23 + import { propTypes } from '/@/utils/propTypes';
  24 + import { useMenuItem } from './useMenu';
  25 + import { Tooltip } from 'ant-design-vue';
  26 + import { useSimpleRootMenuContext } from './useSimpleMenuContext';
  27 + export default defineComponent({
  28 + name: 'MenuItem',
  29 + components: { Tooltip },
  30 + props: {
  31 + name: {
  32 + type: [String, Number] as PropType<string | number>,
  33 + required: true,
  34 + },
  35 + disabled: propTypes.bool,
  36 + },
  37 + setup(props, { slots }) {
  38 + const instance = getCurrentInstance();
  39 +
  40 + const active = ref(false);
  41 +
  42 + const { getItemStyle, getParentList, getParentMenu, getParentRootMenu } = useMenuItem(
  43 + instance
  44 + );
  45 +
  46 + const { prefixCls } = useDesign('menu');
  47 +
  48 + const { rootMenuEmitter, activeName } = useSimpleRootMenuContext();
  49 +
  50 + const getClass = computed(() => {
  51 + return [
  52 + `${prefixCls}-item`,
  53 + {
  54 + [`${prefixCls}-item-active`]: unref(active),
  55 + [`${prefixCls}-item-selected`]: unref(active),
  56 + [`${prefixCls}-item-disabled`]: !!props.disabled,
  57 + },
  58 + ];
  59 + });
  60 +
  61 + const getCollapse = computed(() => unref(getParentRootMenu)?.props.collapse);
  62 +
  63 + const showTooptip = computed(() => {
  64 + return unref(getParentMenu)?.type.name === 'Menu' && unref(getCollapse) && slots.title;
  65 + });
  66 +
  67 + function handleClickItem() {
  68 + const { disabled } = props;
  69 + if (disabled) return;
  70 +
  71 + rootMenuEmitter.emit('on-menu-item-select', props.name);
  72 + if (unref(getCollapse)) return;
  73 + const { uidList } = getParentList();
  74 + rootMenuEmitter.emit('on-update-opened', {
  75 + opend: false,
  76 + parent: instance?.parent,
  77 + uidList: uidList,
  78 + });
  79 + }
  80 + watch(
  81 + () => activeName.value,
  82 + (name: string) => {
  83 + if (name === props.name) {
  84 + const { list, uidList } = getParentList();
  85 + active.value = true;
  86 + list.forEach((item) => {
  87 + if (item.proxy) {
  88 + (item.proxy as any).active = true;
  89 + }
  90 + });
  91 +
  92 + rootMenuEmitter.emit('on-update-active-name:submenu', uidList);
  93 + } else {
  94 + active.value = false;
  95 + }
  96 + },
  97 + { immediate: true }
  98 + );
  99 +
  100 + return { getClass, prefixCls, getItemStyle, getCollapse, handleClickItem, showTooptip };
  101 + },
  102 + });
  103 +</script>
... ...
src/components/SimpleMenu/src/components/SubMenuItem.vue 0 → 100644
  1 +<template>
  2 + <li :class="getClass">
  3 + <template v-if="!getCollapse">
  4 + <div :class="`${prefixCls}-submenu-title`" @click.stop="handleClick" :style="getItemStyle">
  5 + <slot name="title"></slot>
  6 + <Icon
  7 + icon="eva:arrow-ios-downward-outline"
  8 + :size="14"
  9 + :class="`${prefixCls}-submenu-title-icon`"
  10 + />
  11 + </div>
  12 + <MenuCollapseTransition>
  13 + <ul :class="prefixCls" v-show="opened">
  14 + <slot></slot>
  15 + </ul>
  16 + </MenuCollapseTransition>
  17 + </template>
  18 +
  19 + <Popover
  20 + placement="right"
  21 + :overlayClassName="`${prefixCls}-menu-popover`"
  22 + v-else
  23 + :visible="getIsOpend"
  24 + @visibleChange="handleVisibleChange"
  25 + :overlayStyle="getOverlayStyle"
  26 + :align="{ offset: [0, 0] }"
  27 + >
  28 + <div :class="getSubClass" v-bind="getEvents(false)">
  29 + <div
  30 + :class="[
  31 + {
  32 + [`${prefixCls}-submenu-popup`]: !getParentSubMenu,
  33 + [`${prefixCls}-submenu-collapsed-show-tit`]: collapsedShowTitle,
  34 + },
  35 + ]"
  36 + >
  37 + <slot name="title"></slot>
  38 + </div>
  39 + <Icon
  40 + v-if="getParentSubMenu"
  41 + icon="eva:arrow-ios-downward-outline"
  42 + :size="14"
  43 + :class="`${prefixCls}-submenu-title-icon`"
  44 + />
  45 + </div>
  46 + <template #content v-show="opened">
  47 + <div v-bind="getEvents(true)">
  48 + <ul :class="[prefixCls, `${prefixCls}-${getTheme}`, `${prefixCls}-popup`]">
  49 + <slot></slot>
  50 + </ul>
  51 + </div>
  52 + </template>
  53 + </Popover>
  54 + </li>
  55 +</template>
  56 +
  57 +<script lang="ts">
  58 + import type { CSSProperties, PropType } from 'vue';
  59 + import type { SubMenuProvider } from './types';
  60 + import {
  61 + defineComponent,
  62 + computed,
  63 + unref,
  64 + getCurrentInstance,
  65 + toRefs,
  66 + reactive,
  67 + provide,
  68 + onBeforeMount,
  69 + inject,
  70 + } from 'vue';
  71 + import { useDesign } from '/@/hooks/web/useDesign';
  72 + import { propTypes } from '/@/utils/propTypes';
  73 + import { useMenuItem } from './useMenu';
  74 + import { useSimpleRootMenuContext } from './useSimpleMenuContext';
  75 + import MenuCollapseTransition from './MenuCollapseTransition.vue';
  76 + import Icon from '/@/components/Icon';
  77 + import { Popover } from 'ant-design-vue';
  78 + import { isBoolean, isObject } from '/@/utils/is';
  79 + import Mitt from '/@/utils/mitt';
  80 +
  81 + const DELAY = 200;
  82 + export default defineComponent({
  83 + name: 'SubMenu',
  84 + components: {
  85 + Icon,
  86 + MenuCollapseTransition,
  87 + Popover,
  88 + },
  89 + props: {
  90 + name: {
  91 + type: [String, Number] as PropType<string | number>,
  92 + required: true,
  93 + },
  94 + disabled: propTypes.bool,
  95 + collapsedShowTitle: propTypes.bool,
  96 + },
  97 + setup(props) {
  98 + const instance = getCurrentInstance();
  99 +
  100 + const state = reactive({
  101 + active: false,
  102 + opened: false,
  103 + });
  104 +
  105 + const data = reactive({
  106 + timeout: null as TimeoutHandle | null,
  107 + mouseInChild: false,
  108 + isChild: false,
  109 + });
  110 +
  111 + const { getParentSubMenu, getItemStyle, getParentMenu, getParentList } = useMenuItem(
  112 + instance
  113 + );
  114 +
  115 + const { prefixCls } = useDesign('menu');
  116 +
  117 + const subMenuEmitter = new Mitt();
  118 +
  119 + const { rootMenuEmitter } = useSimpleRootMenuContext();
  120 +
  121 + const {
  122 + addSubMenu: parentAddSubmenu,
  123 + removeSubMenu: parentRemoveSubmenu,
  124 + removeAll: parentRemoveAll,
  125 + getOpenNames: parentGetOpenNames,
  126 + isRemoveAllPopup,
  127 + sliceIndex,
  128 + level,
  129 + props: rootProps,
  130 + handleMouseleave: parentHandleMouseleave,
  131 + } = inject<SubMenuProvider>(`subMenu:${getParentMenu.value?.uid}`)!;
  132 +
  133 + const getClass = computed(() => {
  134 + return [
  135 + `${prefixCls}-submenu`,
  136 + {
  137 + [`${prefixCls}-item-active`]: state.active,
  138 + [`${prefixCls}-opened`]: state.opened,
  139 + [`${prefixCls}-submenu-disabled`]: props.disabled,
  140 + [`${prefixCls}-submenu-has-parent-submenu`]: unref(getParentSubMenu),
  141 + [`${prefixCls}-child-item-active`]: state.active,
  142 + },
  143 + ];
  144 + });
  145 +
  146 + const getAccordion = computed(() => rootProps.accordion);
  147 + const getCollapse = computed(() => rootProps.collapse);
  148 + const getTheme = computed(() => rootProps.theme);
  149 +
  150 + const getOverlayStyle = computed(
  151 + (): CSSProperties => {
  152 + return {
  153 + minWidth: '200px',
  154 + };
  155 + }
  156 + );
  157 +
  158 + const getIsOpend = computed(() => {
  159 + const name = props.name;
  160 + if (unref(getCollapse)) {
  161 + return parentGetOpenNames().includes(name);
  162 + }
  163 + return state.opened;
  164 + });
  165 +
  166 + const getSubClass = computed(() => {
  167 + const isActive = rootProps.activeSubMenuNames.includes(props.name);
  168 + return [
  169 + `${prefixCls}-submenu-title`,
  170 + {
  171 + [`${prefixCls}-submenu-active`]: isActive,
  172 + [`${prefixCls}-submenu-active-border`]: isActive && level === 0,
  173 + [`${prefixCls}-submenu-collapse`]: unref(getCollapse) && level === 0,
  174 + },
  175 + ];
  176 + });
  177 +
  178 + function getEvents(deep: boolean) {
  179 + if (!unref(getCollapse)) {
  180 + return {};
  181 + }
  182 + return {
  183 + onMouseenter: handleMouseenter,
  184 + onMouseleave: () => handleMouseleave(deep),
  185 + };
  186 + }
  187 +
  188 + function handleClick() {
  189 + const { disabled } = props;
  190 + if (disabled || unref(getCollapse)) return;
  191 + const opened = state.opened;
  192 + if (unref(getAccordion)) {
  193 + const { uidList } = getParentList();
  194 + rootMenuEmitter.emit('on-update-opened', {
  195 + opend: false,
  196 + parent: instance?.parent,
  197 + uidList: uidList,
  198 + });
  199 + }
  200 + state.opened = !opened;
  201 + }
  202 +
  203 + function handleMouseenter() {
  204 + const disabled = props.disabled;
  205 + if (disabled) return;
  206 +
  207 + subMenuEmitter.emit('submenu:mouse-enter-child');
  208 +
  209 + const index = parentGetOpenNames().findIndex((item) => item === props.name);
  210 +
  211 + sliceIndex(index);
  212 +
  213 + const isRoot = level === 0 && parentGetOpenNames().length === 2;
  214 + if (isRoot) {
  215 + parentRemoveAll();
  216 + }
  217 + data.isChild = parentGetOpenNames().includes(props.name);
  218 + clearTimeout(data.timeout!);
  219 + data.timeout = setTimeout(() => {
  220 + parentAddSubmenu(props.name);
  221 + }, DELAY);
  222 + }
  223 +
  224 + function handleMouseleave(deepDispatch = false) {
  225 + const parentName = getParentMenu.value?.props.name;
  226 + if (!parentName) {
  227 + isRemoveAllPopup.value = true;
  228 + }
  229 +
  230 + if (parentGetOpenNames().slice(-1)[0] === props.name) {
  231 + data.isChild = false;
  232 + }
  233 +
  234 + subMenuEmitter.emit('submenu:mouse-leave-child');
  235 + if (data.timeout) {
  236 + clearTimeout(data.timeout!);
  237 + data.timeout = setTimeout(() => {
  238 + if (isRemoveAllPopup.value) {
  239 + parentRemoveAll();
  240 + } else if (!data.mouseInChild) {
  241 + parentRemoveSubmenu(props.name);
  242 + }
  243 + }, DELAY);
  244 + }
  245 + if (deepDispatch) {
  246 + if (getParentSubMenu.value) {
  247 + parentHandleMouseleave?.(true);
  248 + }
  249 + }
  250 + }
  251 +
  252 + onBeforeMount(() => {
  253 + subMenuEmitter.on('submenu:mouse-enter-child', () => {
  254 + data.mouseInChild = true;
  255 + isRemoveAllPopup.value = false;
  256 + clearTimeout(data.timeout!);
  257 + });
  258 + subMenuEmitter.on('submenu:mouse-leave-child', () => {
  259 + if (data.isChild) return;
  260 + data.mouseInChild = false;
  261 + clearTimeout(data.timeout!);
  262 + });
  263 +
  264 + rootMenuEmitter.on(
  265 + 'on-update-opened',
  266 + (data: boolean | (string | number)[] | Recordable) => {
  267 + if (unref(getCollapse)) return;
  268 + if (isBoolean(data)) {
  269 + state.opened = data;
  270 + return;
  271 + }
  272 +
  273 + if (isObject(data)) {
  274 + const { opend, parent, uidList } = data as Recordable;
  275 + if (parent === instance?.parent) {
  276 + state.opened = opend;
  277 + } else if (!uidList.includes(instance?.uid)) {
  278 + state.opened = false;
  279 + }
  280 + return;
  281 + }
  282 +
  283 + if (props.name && Array.isArray(data)) {
  284 + state.opened = (data as (string | number)[]).includes(props.name);
  285 + }
  286 + }
  287 + );
  288 +
  289 + rootMenuEmitter.on('on-update-active-name:submenu', (data: number[]) => {
  290 + state.active = data.includes(instance?.uid!);
  291 + });
  292 + });
  293 +
  294 + function handleVisibleChange(visible: boolean) {
  295 + state.opened = visible;
  296 + }
  297 +
  298 + // provide
  299 + provide<SubMenuProvider>(`subMenu:${instance?.uid}`, {
  300 + addSubMenu: parentAddSubmenu,
  301 + removeSubMenu: parentRemoveSubmenu,
  302 + getOpenNames: parentGetOpenNames,
  303 + removeAll: parentRemoveAll,
  304 + isRemoveAllPopup,
  305 + sliceIndex,
  306 + level: level + 1,
  307 + handleMouseleave,
  308 + props: rootProps,
  309 + });
  310 +
  311 + return {
  312 + getClass,
  313 + prefixCls,
  314 + getCollapse,
  315 + getItemStyle,
  316 + handleClick,
  317 + handleVisibleChange,
  318 + getParentSubMenu,
  319 + getOverlayStyle,
  320 + getTheme,
  321 + getIsOpend,
  322 + getEvents,
  323 + getSubClass,
  324 + ...toRefs(state),
  325 + ...toRefs(data),
  326 + };
  327 + },
  328 + });
  329 +</script>
... ...
src/components/SimpleMenu/src/components/menu.less 0 → 100644
  1 +@menu-prefix-cls: ~'@{namespace}-menu';
  2 +@menu-popup-prefix-cls: ~'@{namespace}-menu-popup';
  3 +@submenu-popup-prefix-cls: ~'@{namespace}-menu-submenu-popup';
  4 +
  5 +// @menu-dark: #191a23;
  6 +// @menu-dark-active-bg: #101117;
  7 +@transition-time: 0.2s;
  8 +@menu-dark-subsidiary-color: rgba(255, 255, 255, 0.7);
  9 +
  10 +.light-border {
  11 + &::after {
  12 + position: absolute;
  13 + top: 0;
  14 + right: 0;
  15 + bottom: 0;
  16 + display: block;
  17 + width: 2px;
  18 + background: @primary-color;
  19 + content: '';
  20 + }
  21 +}
  22 +
  23 +.@{menu-prefix-cls}-menu-popover {
  24 + .ant-popover-arrow {
  25 + display: none;
  26 + }
  27 +
  28 + .ant-popover-inner-content {
  29 + padding: 0;
  30 + }
  31 +
  32 + .@{menu-prefix-cls} {
  33 + &-opened > * > &-submenu-title-icon {
  34 + transform: translateY(-50%) rotate(90deg) !important;
  35 + }
  36 +
  37 + &-item,
  38 + &-submenu-title {
  39 + position: relative;
  40 + z-index: 1;
  41 + padding: 12px 20px;
  42 + color: @menu-dark-subsidiary-color;
  43 + cursor: pointer;
  44 + transition: all @transition-time @ease-in-out;
  45 +
  46 + // &:hover {
  47 + // color: @primary-color;
  48 + // }
  49 +
  50 + &-icon {
  51 + position: absolute;
  52 + top: 50%;
  53 + right: 18px;
  54 + transform: translateY(-50%) rotate(-90deg);
  55 + transition: transform @transition-time @ease-in-out;
  56 + }
  57 + }
  58 +
  59 + &-dark {
  60 + .@{menu-prefix-cls}-item,
  61 + .@{menu-prefix-cls}-submenu-title {
  62 + color: @menu-dark-subsidiary-color;
  63 + // background: @menu-dark-active-bg;
  64 +
  65 + &:hover {
  66 + color: #fff;
  67 + }
  68 +
  69 + &-selected {
  70 + color: #fff;
  71 + background: @primary-color !important;
  72 + }
  73 + }
  74 + }
  75 +
  76 + &-light {
  77 + .@{menu-prefix-cls}-item,
  78 + .@{menu-prefix-cls}-submenu-title {
  79 + color: @text-color-base;
  80 +
  81 + &:hover {
  82 + color: @primary-color;
  83 + }
  84 +
  85 + &-selected {
  86 + z-index: 2;
  87 + color: @primary-color;
  88 + background: fade(@primary-color, 8);
  89 +
  90 + .light-border();
  91 + }
  92 + }
  93 + }
  94 + }
  95 +}
  96 +
  97 +.content();
  98 +.content() {
  99 + .@{menu-prefix-cls} {
  100 + position: relative;
  101 + display: block;
  102 + width: 100%;
  103 + padding: 0;
  104 + margin: 0;
  105 + font-size: @font-size-base;
  106 + color: @text-color-base;
  107 + list-style: none;
  108 + outline: none;
  109 +
  110 + .collapse-transition {
  111 + transition: @transition-time height ease-in-out, @transition-time padding-top ease-in-out,
  112 + @transition-time padding-bottom ease-in-out;
  113 + }
  114 +
  115 + &-light {
  116 + background: #fff;
  117 +
  118 + .@{menu-prefix-cls}-submenu-active {
  119 + color: @primary-color !important;
  120 + // background: fade(@primary-color, 8);
  121 +
  122 + &-border {
  123 + .light-border();
  124 + }
  125 + }
  126 + }
  127 +
  128 + &-dark {
  129 + // background: @menu-dark;
  130 +
  131 + .@{menu-prefix-cls}-submenu-active {
  132 + color: #fff !important;
  133 + }
  134 + }
  135 +
  136 + &-item {
  137 + position: relative;
  138 + z-index: 1;
  139 + display: flex;
  140 + font-size: @font-size-base;
  141 + color: inherit;
  142 + list-style: none;
  143 + cursor: pointer;
  144 + outline: none;
  145 + align-items: center;
  146 + // transition: all @transition-time @ease-in-out;
  147 +
  148 + &:hover,
  149 + &:active {
  150 + color: inherit;
  151 + }
  152 + }
  153 +
  154 + &-item > i {
  155 + margin-right: 6px;
  156 + }
  157 +
  158 + &-submenu-title > i,
  159 + &-submenu-title span > i {
  160 + margin-right: 8px;
  161 + }
  162 +
  163 + // vertical
  164 + &-vertical &-item,
  165 + &-vertical &-submenu-title {
  166 + position: relative;
  167 + z-index: 1;
  168 + padding: 12px 24px;
  169 + cursor: pointer;
  170 + // transition: all @transition-time @ease-in-out;
  171 +
  172 + &:hover {
  173 + color: @primary-color;
  174 + }
  175 +
  176 + .@{menu-prefix-cls}-tooltip {
  177 + width: calc(100% - 0px);
  178 + padding: 12px 0;
  179 + text-align: center;
  180 + }
  181 + .@{menu-prefix-cls}-submenu-popup {
  182 + padding: 12px 0;
  183 + }
  184 + }
  185 +
  186 + &-vertical &-submenu-collapse {
  187 + .@{submenu-popup-prefix-cls} {
  188 + display: flex;
  189 + justify-content: center;
  190 + align-items: center;
  191 + }
  192 + .@{menu-prefix-cls}-submenu-collapsed-show-tit {
  193 + flex-direction: column;
  194 + }
  195 + }
  196 +
  197 + &-vertical&-collapse &-item,
  198 + &-vertical&-collapse &-submenu-title {
  199 + padding: 0 0;
  200 + }
  201 +
  202 + &-vertical &-submenu-title-icon {
  203 + position: absolute;
  204 + top: 50%;
  205 + right: 18px;
  206 + transform: translateY(-50%);
  207 + }
  208 +
  209 + &-submenu-title-icon {
  210 + transition: transform @transition-time @ease-in-out;
  211 + }
  212 +
  213 + &-vertical &-opened > * > &-submenu-title-icon {
  214 + transform: translateY(-50%) rotate(180deg);
  215 + }
  216 +
  217 + &-vertical &-submenu {
  218 + &-nested {
  219 + padding-left: 20px;
  220 + }
  221 + .@{menu-prefix-cls}-item {
  222 + padding-left: 43px;
  223 + }
  224 + }
  225 +
  226 + &-light&-vertical &-item {
  227 + &-active:not(.@{menu-prefix-cls}-submenu) {
  228 + z-index: 2;
  229 + color: @primary-color;
  230 + background: fade(@primary-color, 8);
  231 +
  232 + .light-border();
  233 + }
  234 + &-active.@{menu-prefix-cls}-submenu {
  235 + color: @primary-color;
  236 + }
  237 + }
  238 +
  239 + &-light&-vertical&-collapse {
  240 + > li.@{menu-prefix-cls}-item-active,
  241 + .@{menu-prefix-cls}-submenu-active {
  242 + position: relative;
  243 + background: fade(@primary-color, 3);
  244 +
  245 + &::after {
  246 + display: none;
  247 + }
  248 +
  249 + &::before {
  250 + position: absolute;
  251 + top: 0;
  252 + left: 0;
  253 + width: 3px;
  254 + height: 100%;
  255 + background: @primary-color;
  256 + content: '';
  257 + }
  258 + }
  259 + }
  260 +
  261 + &-dark&-vertical &-item,
  262 + &-dark&-vertical &-submenu-title {
  263 + color: @menu-dark-subsidiary-color;
  264 + &-active:not(.@{menu-prefix-cls}-submenu) {
  265 + color: #fff !important;
  266 + background: @primary-color !important;
  267 + }
  268 +
  269 + &:hover {
  270 + color: #fff;
  271 + // background: @menu-dark;
  272 + }
  273 +
  274 + // &-active:not(.@{menu-prefix-cls}-submenu) {
  275 + // color: @primary-color;
  276 + // }
  277 + }
  278 +
  279 + &-dark&-vertical&-collapse {
  280 + > li.@{menu-prefix-cls}-item-active,
  281 + .@{menu-prefix-cls}-submenu-active {
  282 + position: relative;
  283 + color: #fff !important;
  284 + background-color: @sider-dark-darken-bg-color !important;
  285 +
  286 + &::before {
  287 + position: absolute;
  288 + top: 0;
  289 + left: 0;
  290 + width: 3px;
  291 + height: 100%;
  292 + background: @primary-color;
  293 + content: '';
  294 + }
  295 +
  296 + .@{menu-prefix-cls}-submenu-collapse {
  297 + background-color: transparent;
  298 + }
  299 + }
  300 + }
  301 +
  302 + &-dark&-vertical &-submenu &-item {
  303 + // &:hover {
  304 + // color: #fff;
  305 + // background: transparent;
  306 + // }
  307 +
  308 + &-active,
  309 + &-active:hover {
  310 + color: #fff;
  311 + border-right: none;
  312 + }
  313 + }
  314 +
  315 + &-dark&-vertical &-child-item-active > &-submenu-title {
  316 + color: #fff;
  317 + }
  318 +
  319 + &-dark&-vertical &-opened {
  320 + // background: @menu-dark-active-bg;
  321 + // .@{menu-prefix-cls}-submenu-title {
  322 + // background: @menu-dark;
  323 + // }
  324 +
  325 + .@{menu-prefix-cls}-submenu-has-parent-submenu {
  326 + .@{menu-prefix-cls}-submenu-title {
  327 + background: transparent;
  328 + }
  329 + }
  330 + }
  331 + }
  332 +}
... ...
src/components/SimpleMenu/src/components/types.ts 0 → 100644
  1 +import { Ref } from 'vue';
  2 +
  3 +export interface Props {
  4 + theme: string;
  5 + activeName?: string | number | undefined;
  6 + openNames: string[];
  7 + accordion: boolean;
  8 + width: string;
  9 + collapsedWidth: string;
  10 + indentSize: number;
  11 + collapse: boolean;
  12 + activeSubMenuNames: (string | number)[];
  13 +}
  14 +
  15 +export interface SubMenuProvider {
  16 + addSubMenu: (name: string | number, update?: boolean) => void;
  17 + removeSubMenu: (name: string | number, update?: boolean) => void;
  18 + removeAll: () => void;
  19 + sliceIndex: (index: number) => void;
  20 + isRemoveAllPopup: Ref<boolean>;
  21 + getOpenNames: () => (string | number)[];
  22 + handleMouseleave?: Fn;
  23 + level: number;
  24 + props: Props;
  25 +}
... ...
src/components/SimpleMenu/src/components/useMenu.ts 0 → 100644
  1 +import { computed, ComponentInternalInstance, unref } from 'vue';
  2 +import type { CSSProperties } from 'vue';
  3 +
  4 +export function useMenuItem(instance: ComponentInternalInstance | null) {
  5 + const getParentMenu = computed(() => {
  6 + return findParentMenu(['Menu', 'SubMenu']);
  7 + });
  8 +
  9 + const getParentRootMenu = computed(() => {
  10 + return findParentMenu(['Menu']);
  11 + });
  12 +
  13 + const getParentSubMenu = computed(() => {
  14 + return findParentMenu(['SubMenu']);
  15 + });
  16 +
  17 + const getItemStyle = computed(
  18 + (): CSSProperties => {
  19 + let parent = instance?.parent;
  20 + if (!parent) return {};
  21 + const indentSize = (unref(getParentRootMenu)?.props.indentSize as number) ?? 20;
  22 + let padding = indentSize;
  23 +
  24 + if (unref(getParentRootMenu)?.props.collapse) {
  25 + padding = indentSize;
  26 + } else {
  27 + while (parent && parent.type.name !== 'Menu') {
  28 + if (parent.type.name === 'SubMenu') {
  29 + padding += indentSize;
  30 + }
  31 + parent = parent.parent;
  32 + }
  33 + }
  34 + return { paddingLeft: padding + 'px' };
  35 + }
  36 + );
  37 +
  38 + function findParentMenu(name: string[]) {
  39 + let parent = instance?.parent;
  40 + if (!parent) return null;
  41 + while (parent && name.indexOf(parent.type.name!) === -1) {
  42 + parent = parent.parent;
  43 + }
  44 + return parent;
  45 + }
  46 +
  47 + function getParentList() {
  48 + let parent = instance;
  49 + if (!parent)
  50 + return {
  51 + uidList: [],
  52 + list: [],
  53 + };
  54 + const ret = [];
  55 + while (parent && parent.type.name !== 'Menu') {
  56 + if (parent.type.name === 'SubMenu') {
  57 + ret.push(parent);
  58 + }
  59 + parent = parent.parent;
  60 + }
  61 + return {
  62 + uidList: ret.map((item) => item.uid),
  63 + list: ret,
  64 + };
  65 + }
  66 +
  67 + function getParentInstance(instance: ComponentInternalInstance, name = 'SubMenu') {
  68 + let parent = instance.parent;
  69 + while (parent) {
  70 + if (parent.type.name !== name) {
  71 + return parent;
  72 + }
  73 + parent = parent.parent;
  74 + }
  75 + return parent;
  76 + }
  77 +
  78 + return {
  79 + getParentMenu,
  80 + getParentInstance,
  81 + getParentRootMenu,
  82 + getParentList,
  83 + getParentSubMenu,
  84 + getItemStyle,
  85 + };
  86 +}
... ...
src/components/SimpleMenu/src/components/useSimpleMenuContext.ts 0 → 100644
  1 +import type { InjectionKey, Ref } from 'vue';
  2 +import { createContext, useContext } from '/@/hooks/core/useContext';
  3 +import Mitt from '/@/utils/mitt';
  4 +
  5 +export interface SimpleRootMenuContextProps {
  6 + rootMenuEmitter: Mitt;
  7 + activeName: Ref<string | number>;
  8 +}
  9 +
  10 +const key: InjectionKey<SimpleRootMenuContextProps> = Symbol();
  11 +
  12 +export function createSimpleRootMenuContext(context: SimpleRootMenuContextProps) {
  13 + return createContext<SimpleRootMenuContextProps>(context, key, { readonly: false, native: true });
  14 +}
  15 +
  16 +export function useSimpleRootMenuContext() {
  17 + return useContext<SimpleRootMenuContextProps>(key);
  18 +}
... ...
src/components/SimpleMenu/src/index.less 0 → 100644
  1 +@simple-prefix-cls: ~'@{namespace}-simple-menu';
  2 +@prefix-cls: ~'@{namespace}-menu';
  3 +
  4 +.@{prefix-cls} {
  5 + &-dark&-vertical .@{simple-prefix-cls}__parent {
  6 + background-color: @sider-dark-bg-color;
  7 + > .@{prefix-cls}-submenu-title {
  8 + background-color: @sider-dark-bg-color;
  9 + }
  10 + }
  11 +
  12 + &-dark&-vertical .@{simple-prefix-cls}__children,
  13 + &-dark&-popup .@{simple-prefix-cls}__children {
  14 + background-color: @sider-dark-lighten-1-bg-color;
  15 + > .@{prefix-cls}-submenu-title {
  16 + background-color: @sider-dark-lighten-1-bg-color;
  17 + }
  18 + }
  19 +
  20 + .collapse-title {
  21 + font-size: 12px;
  22 + }
  23 +}
  24 +
  25 +.@{simple-prefix-cls} {
  26 + &-tag {
  27 + position: absolute;
  28 + top: calc(50% - 10px);
  29 + right: 30px;
  30 + display: inline-block;
  31 + padding: 2px 3px;
  32 + margin-right: 4px;
  33 + font-size: 10px;
  34 + line-height: 14px;
  35 + color: #fff;
  36 + border-radius: 2px;
  37 +
  38 + &--collapse {
  39 + top: 6px !important;
  40 + right: 2px;
  41 + }
  42 +
  43 + &--dot {
  44 + top: calc(50% - 4px);
  45 + width: 6px;
  46 + height: 6px;
  47 + padding: 0;
  48 + border-radius: 50%;
  49 + }
  50 +
  51 + &--primary {
  52 + background: @primary-color;
  53 + }
  54 +
  55 + &--error {
  56 + background: @error-color;
  57 + }
  58 +
  59 + &--success {
  60 + background: @success-color;
  61 + }
  62 +
  63 + &--warn {
  64 + background: @warning-color;
  65 + }
  66 + }
  67 +}
... ...
src/components/SimpleMenu/src/types.ts 0 → 100644
  1 +export interface MenuState {
  2 + activeName: string;
  3 + openNames: string[];
  4 + activeSubMenuNames: string[];
  5 +}
... ...
src/components/SimpleMenu/src/useOpenKeys.ts 0 → 100644
  1 +import type { Menu as MenuType } from '/@/router/types';
  2 +import type { MenuState } from './types';
  3 +
  4 +import { Ref, toRaw } from 'vue';
  5 +
  6 +import { unref } from 'vue';
  7 +import { es6Unique } from '/@/utils';
  8 +import { getAllParentPath } from '/@/router/helper/menuHelper';
  9 +import { useTimeoutFn } from '/@/hooks/core/useTimeout';
  10 +
  11 +export function useOpenKeys(
  12 + menuState: MenuState,
  13 + menus: Ref<MenuType[]>,
  14 + accordion: Ref<boolean>,
  15 + mixSider: Ref<boolean>
  16 + // mode: Ref<MenuModeEnum>,
  17 +) {
  18 + async function setOpenKeys(path: string) {
  19 + // if (mode.value === MenuModeEnum.HORIZONTAL) {
  20 + // return;
  21 + // }
  22 + const native = !mixSider.value;
  23 + useTimeoutFn(
  24 + () => {
  25 + const menuList = toRaw(menus.value);
  26 + if (menuList?.length === 0) {
  27 + menuState.activeSubMenuNames = [];
  28 + menuState.openNames = [];
  29 + return;
  30 + }
  31 + const keys = getAllParentPath(menuList, path);
  32 + if (!unref(accordion)) {
  33 + menuState.openNames = es6Unique([...menuState.openNames, ...keys]);
  34 + } else {
  35 + menuState.openNames = keys;
  36 + }
  37 + menuState.activeSubMenuNames = menuState.openNames;
  38 + },
  39 + 16,
  40 + native
  41 + );
  42 + }
  43 +
  44 + return { setOpenKeys };
  45 +}
... ...
src/components/Table/src/components/editable/EditableCell.vue
... ... @@ -31,7 +31,7 @@
31 31 import type { CSSProperties, PropType } from 'vue';
32 32 import type { BasicColumn } from '../../types/table';
33 33  
34   - import { defineComponent, ref, unref, nextTick, computed, watchEffect, toRaw } from 'vue';
  34 + import { defineComponent, ref, unref, nextTick, computed, watchEffect } from 'vue';
35 35 import { FormOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons-vue';
36 36  
37 37 import { useDesign } from '/@/hooks/web/useDesign';
... ...
src/components/Table/src/hooks/useProvinceTable.ts deleted 100644 → 0
1   -import type { Ref } from 'vue';
2   -import type { TableActionType } from '../types/table';
3   -
4   -import { provide, inject } from 'vue';
5   -
6   -const key = Symbol('table');
7   -
8   -type Instance = TableActionType & { wrapRef: Ref<Nullable<HTMLElement>> };
9   -
10   -export function provideTable(instance: Instance) {
11   - provide(key, instance);
12   -}
13   -
14   -export function injectTable(): Instance {
15   - return inject(key) as Instance;
16   -}
src/design/ant/index.less
... ... @@ -22,6 +22,12 @@
22 22 background: rgba(0, 0, 0, 0.3);
23 23 }
24 24  
  25 +.ant-popover {
  26 + &-content {
  27 + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  28 + }
  29 +}
  30 +
25 31 // =================================
26 32 // ==============descriptions=======
27 33 // =================================
... ...
src/design/color.less
... ... @@ -9,7 +9,6 @@
9 9 --sider-dark-darken-bg-color: #273352;
10 10 --sider-dark-lighten-1-bg-color: #273352;
11 11 --sider-dark-lighten-2-bg-color: #273352;
12   - --sider-dark-lighten-3-bg-color: #273352;
13 12 }
14 13  
15 14 @white: #fff;
... ... @@ -88,7 +87,6 @@
88 87 @sider-dark-darken-bg-color: var(--sider-dark-darken-bg-color);
89 88 @sider-dark-lighten-1-bg-color: var(--sider-dark-lighten-1-bg-color);
90 89 @sider-dark-lighten-2-bg-color: var(--sider-dark-lighten-2-bg-color);
91   -@sider-dark-lighten-3-bg-color: var(--sider-dark-lighten-3-bg-color);
92 90  
93 91 // trigger
94 92 @trigger-dark-hover-bg-color: rgba(255, 255, 255, 0.2);
... ...
src/hooks/setting/useMenuSetting.ts
... ... @@ -78,9 +78,12 @@ const getIsMixMode = computed(() =&gt; {
78 78 });
79 79  
80 80 const getRealWidth = computed(() => {
81   - return unref(getCollapsed) && !unref(getMixSideFixed)
82   - ? unref(getMiniWidthNumber)
83   - : unref(getMenuWidth);
  81 + if (unref(getIsMixSidebar)) {
  82 + return unref(getCollapsed) && !unref(getMixSideFixed)
  83 + ? unref(getMiniWidthNumber)
  84 + : unref(getMenuWidth);
  85 + }
  86 + return unref(getCollapsed) ? unref(getMiniWidthNumber) : unref(getMenuWidth);
84 87 });
85 88  
86 89 const getMiniWidthNumber = computed(() => {
... ...
src/layouts/default/header/index.vue
... ... @@ -142,7 +142,7 @@
142 142 });
143 143  
144 144 const getLogoWidth = computed(() => {
145   - if (!unref(getIsMixMode)) {
  145 + if (!unref(getIsMixMode) || unref(getIsMobile)) {
146 146 return {};
147 147 }
148 148 const width = unref(getMenuWidth) < 180 ? 180 : unref(getMenuWidth);
... ...
src/layouts/default/menu/index.tsx
... ... @@ -4,6 +4,7 @@ import type { PropType, CSSProperties } from &#39;vue&#39;;
4 4  
5 5 import { computed, defineComponent, unref, toRef } from 'vue';
6 6 import { BasicMenu } from '/@/components/Menu';
  7 +import { SimpleMenu } from '/@/components/SimpleMenu';
7 8 import { AppLogo } from '/@/components/Application';
8 9  
9 10 import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
... ... @@ -126,7 +127,18 @@ export default defineComponent({
126 127 }
127 128  
128 129 function renderMenu() {
129   - return (
  130 + const menus = unref(menusRef);
  131 + if (!menus || !menus.length) return null;
  132 + return !props.isHorizontal ? (
  133 + <SimpleMenu
  134 + items={menus}
  135 + theme={unref(getComputedMenuTheme)}
  136 + accordion={unref(getAccordion)}
  137 + collapse={unref(getCollapsed)}
  138 + collapsedShowTitle={unref(getCollapsedShowTitle)}
  139 + onMenuClick={handleMenuClick}
  140 + />
  141 + ) : (
130 142 <BasicMenu
131 143 beforeClickFn={beforeMenuClickFn}
132 144 isHorizontal={props.isHorizontal}
... ... @@ -135,7 +147,7 @@ export default defineComponent({
135 147 showLogo={unref(getIsShowLogo)}
136 148 mode={unref(getComputedMenuMode)}
137 149 theme={unref(getComputedMenuTheme)}
138   - items={unref(menusRef)}
  150 + items={menus}
139 151 accordion={unref(getAccordion)}
140 152 onMenuClick={handleMenuClick}
141 153 />
... ...
src/layouts/default/menu/useLayoutMenu.ts
... ... @@ -40,7 +40,12 @@ export function useSplitMenu(splitType: Ref&lt;MenuSplitTyeEnum&gt;) {
40 40 async ([path]: [string, MenuSplitTyeEnum]) => {
41 41 if (unref(splitNotLeft) || unref(getIsMobile)) return;
42 42  
43   - const parentPath = await getCurrentParentPath(path);
  43 + const { meta } = unref(currentRoute);
  44 + const currentActiveMenu = meta.currentActiveMenu;
  45 + let parentPath = await getCurrentParentPath(path);
  46 + if (!parentPath) {
  47 + parentPath = await getCurrentParentPath(currentActiveMenu);
  48 + }
44 49 parentPath && throttleHandleSplitLeftMenu(parentPath);
45 50 },
46 51 {
... ... @@ -67,11 +72,15 @@ export function useSplitMenu(splitType: Ref&lt;MenuSplitTyeEnum&gt;) {
67 72  
68 73 // Handle left menu split
69 74 async function handleSplitLeftMenu(parentPath: string) {
  75 + console.log('======================');
  76 + console.log(unref(getSplitLeft));
  77 + console.log('======================');
70 78 if (unref(getSplitLeft) || unref(getIsMobile)) return;
71 79  
72 80 // spilt mode left
73 81 const children = await getChildrenMenus(parentPath);
74   - if (!children) {
  82 +
  83 + if (!children || !children.length) {
75 84 setMenuSetting({ hidden: true });
76 85 menusRef.value = [];
77 86 return;
... ...
src/layouts/default/sider/MixSider.vue
... ... @@ -61,9 +61,7 @@
61 61 />
62 62 </div>
63 63 <ScrollContainer :class="`${prefixCls}-menu-list__content`">
64   - <BasicMenu
65   - :isHorizontal="false"
66   - mode="inline"
  64 + <SimpleMenu
67 65 :items="chilrenMenus"
68 66 :theme="getMenuTheme"
69 67 mixSider
... ... @@ -85,7 +83,7 @@
85 83  
86 84 import { defineComponent, onMounted, ref, computed, unref } from 'vue';
87 85  
88   - import { BasicMenu, MenuTag } from '/@/components/Menu';
  86 + import { MenuTag } from '/@/components/Menu';
89 87 import { ScrollContainer } from '/@/components/Container';
90 88 import Icon from '/@/components/Icon';
91 89 import { AppLogo } from '/@/components/Application';
... ... @@ -103,13 +101,14 @@
103 101 import clickOutside from '/@/directives/clickOutside';
104 102 import { getShallowMenus, getChildrenMenus, getCurrentParentPath } from '/@/router/menus';
105 103 import { listenerLastChangeTab } from '/@/logics/mitt/tabChange';
  104 + import { SimpleMenu } from '/@/components/SimpleMenu';
106 105  
107 106 export default defineComponent({
108 107 name: 'LayoutMixSider',
109 108 components: {
110 109 ScrollContainer,
111 110 AppLogo,
112   - BasicMenu,
  111 + SimpleMenu,
113 112 MenuTag,
114 113 Icon,
115 114 Trigger,
... ... @@ -335,6 +334,7 @@
335 334 <style lang="less">
336 335 @prefix-cls: ~'@{namespace}-layout-mix-sider';
337 336 @tag-prefix-cls: ~'@{namespace}-basic-menu-item-tag';
  337 + @menu-prefix-cls: ~'@{namespace}-menu';
338 338 @width: 80px;
339 339 .@{prefix-cls} {
340 340 position: fixed;
... ... @@ -351,6 +351,10 @@
351 351 right: 2px;
352 352 }
353 353  
  354 + .@{menu-prefix-cls} {
  355 + width: 100% !important;
  356 + }
  357 +
354 358 &-dom {
355 359 height: 100%;
356 360 overflow: hidden;
... ... @@ -392,6 +396,10 @@
392 396 }
393 397 }
394 398 .@{prefix-cls}-menu-list {
  399 + &__content {
  400 + box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.1);
  401 + }
  402 +
395 403 &__title {
396 404 .pushpin {
397 405 color: rgba(0, 0, 0, 0.35);
... ... @@ -578,10 +586,10 @@
578 586  
579 587 &-drag-bar {
580 588 position: absolute;
581   - top: 0;
582   - right: -3px;
583   - width: 3px;
584   - height: 100%;
  589 + top: 50px;
  590 + right: -1px;
  591 + width: 1px;
  592 + height: calc(100% - 50px);
585 593 cursor: ew-resize;
586 594 background: #f8f8f9;
587 595 border-top: none;
... ...
src/locales/lang/en/component/drawer.ts 0 → 100644
  1 +export default {
  2 + loadingText: 'Loading...',
  3 + cancelText: 'Close',
  4 + okText: 'Confirm',
  5 +};
... ...
src/locales/lang/en/component/menu.ts 0 → 100644
  1 +export default {
  2 + search: 'Menu search',
  3 +};
... ...
src/locales/lang/en/component/modal.ts 0 → 100644
  1 +export default {
  2 + cancelText: 'Close',
  3 + okText: 'Confirm',
  4 +};
... ...
src/locales/lang/zh_CN/component/drawer.ts 0 → 100644
  1 +export default {
  2 + loadingText: '加载中...',
  3 + cancelText: '关闭',
  4 + okText: '确认',
  5 +};
... ...
src/locales/lang/zh_CN/component/menu.ts 0 → 100644
  1 +export default {
  2 + search: '菜单搜索',
  3 +};
... ...
src/locales/lang/zh_CN/component/modal.ts 0 → 100644
  1 +export default {
  2 + cancelText: '关闭',
  3 + okText: '确认',
  4 +};
... ...
src/logics/theme/index.ts
... ... @@ -71,7 +71,7 @@ export function updateSidebarBgColor(color: string) {
71 71  
72 72 setCssVar(SIDER_DARK_BG_COLOR, color);
73 73 setCssVar(SIDER_DARK_DARKEN_BG_COLOR, darken(color, 6));
74   - setCssVar(SIDER_LIGHTEN_1_BG_COLOR, lighten(color, 4));
  74 + setCssVar(SIDER_LIGHTEN_1_BG_COLOR, lighten(color, 5));
75 75 setCssVar(SIDER_LIGHTEN_2_BG_COLOR, lighten(color, 8));
76 76  
77 77 // only #ffffff is light
... ...
src/router/guard/index.ts
... ... @@ -8,6 +8,7 @@ import { createMessageGuard } from &#39;./messageGuard&#39;;
8 8 import { createScrollGuard } from './scrollGuard';
9 9 import { createHttpGuard } from './httpGuard';
10 10 import { createPageGuard } from './pageGuard';
  11 +import { createStateGuard } from './stateGuard';
11 12  
12 13 export function createGuard(router: Router) {
13 14 createPageGuard(router);
... ... @@ -18,4 +19,5 @@ export function createGuard(router: Router) {
18 19 createTitleGuard(router);
19 20 createProgressGuard(router);
20 21 createPermissionGuard(router);
  22 + createStateGuard(router);
21 23 }
... ...
src/router/guard/stateGuard.ts
... ... @@ -3,7 +3,7 @@ import { appStore } from &#39;/@/store/modules/app&#39;;
3 3 import { PageEnum } from '/@/enums/pageEnum';
4 4 import { removeTabChangeListener } from '/@/logics/mitt/tabChange';
5 5  
6   -export function createHttpGuard(router: Router) {
  6 +export function createStateGuard(router: Router) {
7 7 router.afterEach((to) => {
8 8 // Just enter the login page and clear the authentication information
9 9 if (to.path === PageEnum.BASE_LOGIN) {
... ...
src/router/menus/index.ts
... ... @@ -54,7 +54,9 @@ export const getMenus = async (): Promise&lt;Menu[]&gt; =&gt; {
54 54 // 获取当前路径的顶级路径
55 55 export async function getCurrentParentPath(currentPath: string) {
56 56 const menus = await getAsyncMenus();
  57 +
57 58 const allParentPath = await getAllParentPath(menus, currentPath);
  59 +
58 60 return allParentPath?.[0];
59 61 }
60 62  
... ...
src/utils/mitt.ts
... ... @@ -28,7 +28,7 @@ export default class Mitt {
28 28 * @param {Function} handler Function to call in response to given event
29 29 */
30 30 on(type: string | Symbol, handler: Fn) {
31   - const handlers = this.cache.get(type);
  31 + const handlers = this.cache?.get(type);
32 32 const added = handlers && handlers.push(handler);
33 33 if (!added) {
34 34 this.cache.set(type, [handler]);
... ... @@ -57,7 +57,7 @@ export default class Mitt {
57 57 * @param {string|symbol} type The event type to invoke
58 58 * @param {*} [evt] Any value (object is recommended and powerful), passed to each handler
59 59 */
60   - emit(type: string | Symbol, evt: any) {
  60 + emit(type: string | Symbol, evt?: any) {
61 61 for (const handler of (this.cache.get(type) || []).slice()) handler(evt);
62 62 for (const handler of (this.cache.get('*') || []).slice()) handler(type, evt);
63 63 }
... ...
vite.config.ts
... ... @@ -89,6 +89,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig =&gt; {
89 89 optimizeDeps: {
90 90 include: [
91 91 '@ant-design/icons-vue',
  92 + 'echarts/map/js/china',
92 93 'ant-design-vue/es/locale/zh_CN',
93 94 'moment/dist/locale/zh-cn',
94 95 'ant-design-vue/es/locale/en_US',
... ...
yarn.lock
... ... @@ -1123,10 +1123,10 @@
1123 1123 resolved "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.2.1.tgz#29a5a86bcfaa41555c8483a287294e520cc28cd6"
1124 1124 integrity sha512-WmvsSfVKQx62vLbHXJvdh4PDjSK9YU6VW9ppXTlbjgDKCYtpy2sMWbK4i9OBdxY6RRwMMVctZhWo6Y5jfMRyTg==
1125 1125  
1126   -"@eslint/eslintrc@^0.2.2":
1127   - version "0.2.2"
1128   - resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz#d01fc791e2fc33e88a29d6f3dc7e93d0cd784b76"
1129   - integrity sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==
  1126 +"@eslint/eslintrc@^0.3.0":
  1127 + version "0.3.0"
  1128 + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318"
  1129 + integrity sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==
1130 1130 dependencies:
1131 1131 ajv "^6.12.4"
1132 1132 debug "^4.1.1"
... ... @@ -1135,7 +1135,7 @@
1135 1135 ignore "^4.0.6"
1136 1136 import-fresh "^3.2.1"
1137 1137 js-yaml "^3.13.1"
1138   - lodash "^4.17.19"
  1138 + lodash "^4.17.20"
1139 1139 minimatch "^3.0.4"
1140 1140 strip-json-comments "^3.1.1"
1141 1141  
... ... @@ -1184,10 +1184,10 @@
1184 1184 dependencies:
1185 1185 cross-fetch "^3.0.6"
1186 1186  
1187   -"@iconify/json@^1.1.285":
1188   - version "1.1.285"
1189   - resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.285.tgz#2f1665c9f3ce4cd9eb2e0c980c0ac8955ce520fc"
1190   - integrity sha512-ABoWg/GibeN3hzTvvzd9oSmSo3V8Hyb3f0LMMUD195xlrd8083nBzFFhA12EfEMnxNsouj6ZtvlgIDnYWEXRow==
  1187 +"@iconify/json@^1.1.287":
  1188 + version "1.1.287"
  1189 + resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.287.tgz#31fe253ce97fb2bf673a60c2467810a3f48a00c3"
  1190 + integrity sha512-wvmQDpHqzbYZv2mDsdp1eXUN+ff53FjElT19uVxFRPOkY2kaIhs7dMPS/ZeDD38TE2eH1arTzZ2KhtB+Mxe8VQ==
1191 1191  
1192 1192 "@intlify/core-base@9.0.0-beta.16":
1193 1193 version "9.0.0-beta.16"
... ... @@ -1494,10 +1494,10 @@
1494 1494 resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz#682477dbbbd07cd032731cb3b0e7eaee3d026b69"
1495 1495 integrity sha512-2aoSC4UUbHDj2uCsCxcG/vRMXey/m17bC7UwitVm5hn22nI8O8Y9iDpA76Orc+DWkQ4zZrOKEshCqR/jSuXAHA==
1496 1496  
1497   -"@types/http-proxy@^1.17.4":
1498   - version "1.17.4"
1499   - resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.4.tgz#e7c92e3dbe3e13aa799440ff42e6d3a17a9d045b"
1500   - integrity sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q==
  1497 +"@types/http-proxy@^1.17.5":
  1498 + version "1.17.5"
  1499 + resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.5.tgz#c203c5e6e9dc6820d27a40eb1e511c70a220423d"
  1500 + integrity sha512-GNkDE7bTv6Sf8JbV2GksknKOsk7OznNYHSdrtvPJXO0qJ9odZig6IZKUi5RFGi6d1bf6dgIAe4uXi3DBc7069Q==
1501 1501 dependencies:
1502 1502 "@types/node" "*"
1503 1503  
... ... @@ -1751,10 +1751,10 @@
1751 1751 "@typescript-eslint/types" "4.13.0"
1752 1752 eslint-visitor-keys "^2.0.0"
1753 1753  
1754   -"@vitejs/plugin-legacy@^1.2.0":
1755   - version "1.2.0"
1756   - resolved "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.2.0.tgz#e6a2f7802f1a81c712f72656300fcdf7541eeab0"
1757   - integrity sha512-eoJi1M7Or16bkRjXFtdG39c8ElvbgxUxlXFo8GO2VmgOGO42r6Ku5MJD4ZkweIM7XGunyFvmEwTYgpUVC4PiPg==
  1754 +"@vitejs/plugin-legacy@^1.2.1":
  1755 + version "1.2.1"
  1756 + resolved "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.2.1.tgz#783a72c56ce987f00caf334acc33195a0bbf6f24"
  1757 + integrity sha512-bVOYH7WxffDSvfFfCGk/UYCzKw59n18fHGOV3VXRSQmeaBmbxuq0CRdAS3EtPvp74DjgA4GiZ+BsrQ0LyF0/yA==
1758 1758 dependencies:
1759 1759 "@babel/standalone" "^7.12.12"
1760 1760 core-js "^3.8.2"
... ... @@ -1772,10 +1772,10 @@
1772 1772 "@vue/babel-plugin-jsx" "^1.0.1"
1773 1773 hash-sum "^2.0.0"
1774 1774  
1775   -"@vitejs/plugin-vue@^1.0.5":
1776   - version "1.0.5"
1777   - resolved "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.0.5.tgz#2639178e975bebc505e9be1c88d25faf9bc4dd06"
1778   - integrity sha512-Fq/Z1rTs7j3QhvmIjeIHqInw2YneXa8Td3z7cYQhyAZXF/WmGMegbapeBqGAoAcGSOfWpOO7Tr0c/T+Qke0O6Q==
  1775 +"@vitejs/plugin-vue@^1.0.6":
  1776 + version "1.0.6"
  1777 + resolved "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.0.6.tgz#698afa5a77a6dcd22cf7757801f46a6f01cdbb53"
  1778 + integrity sha512-cWJewtxnVVpjlhq6DoZ7VP7sF1jTZYVg66ehslZ0tJANWk1uRiCXdqD8yQ4npZ4XewDICQzK+c+9i3Xsubx59w==
1779 1779  
1780 1780 "@vue/babel-helper-vue-transform-on@^1.0.0":
1781 1781 version "1.0.0"
... ... @@ -2023,18 +2023,18 @@
2023 2023 vscode-languageserver-textdocument "^1.0.1"
2024 2024 vscode-uri "^2.1.2"
2025 2025  
2026   -"@vueuse/core@^4.0.5":
2027   - version "4.0.5"
2028   - resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.5.tgz#97bd5f24a28401598436629312eafe66ed0e1bed"
2029   - integrity sha512-Kfy5ys9o1XIY6NwX9O7iad4/FbHrcDuP/LtsgIFvl7XDQtbYArHu5ZSOQyBwqE32TdAqnFi5sYd4vjSvVvpD4A==
  2026 +"@vueuse/core@^4.0.8":
  2027 + version "4.0.8"
  2028 + resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.8.tgz#d5690154c147ae787bf5d67bf8fe3046dff96d85"
  2029 + integrity sha512-wD0JJUXpRgRBPCnGsAqcVk9Zz545zOmIjGv/1Mlco3rVmal7LEZ3rJh8SnBelxuyVNvRwifkK1gtbT24jY6V8Q==
2030 2030 dependencies:
2031   - "@vueuse/shared" "4.0.5"
  2031 + "@vueuse/shared" "4.0.8"
2032 2032 vue-demi latest
2033 2033  
2034   -"@vueuse/shared@4.0.5":
2035   - version "4.0.5"
2036   - resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.5.tgz#0610210da9a01843cdb3fa88c177b29b62738efc"
2037   - integrity sha512-PUSlwoSaerwHA1PPjBGnerXPIvAcVGoxcpjNdbHW44lPqoWskWl2CxG+l2Iz+Zf2iapCatp3ovXnMd16RRvQ1Q==
  2034 +"@vueuse/shared@4.0.8":
  2035 + version "4.0.8"
  2036 + resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.8.tgz#ba6c350b5f0ef12e2a603d956cc6d2809ff5be4f"
  2037 + integrity sha512-euAfdZeFHGAyCBoy7izgufC/kTt+yEjuVjeCmfuDQNAj7QsdzEpRlyblD+EGifHbyGFx8F3Ql6/bQzdTdwRFHA==
2038 2038 dependencies:
2039 2039 vue-demi latest
2040 2040  
... ... @@ -2800,7 +2800,7 @@ commander@~2.17.1:
2800 2800 resolved "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
2801 2801 integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
2802 2802  
2803   -commitizen@^4.0.3, commitizen@^4.2.2:
  2803 +commitizen@^4.0.3:
2804 2804 version "4.2.2"
2805 2805 resolved "https://registry.npmjs.org/commitizen/-/commitizen-4.2.2.tgz#1a93dd07208521ea1ebbf832593542dac714cc79"
2806 2806 integrity sha512-uz+E6lGsDBDI2mYA4QfOxFeqdWUYwR1ky11YmLgg2BnEEP3YbeejpT4lxzGjkYqumnXr062qTOGavR9NtX/iwQ==
... ... @@ -2820,6 +2820,26 @@ commitizen@^4.0.3, commitizen@^4.2.2:
2820 2820 strip-bom "4.0.0"
2821 2821 strip-json-comments "3.0.1"
2822 2822  
  2823 +commitizen@^4.2.3:
  2824 + version "4.2.3"
  2825 + resolved "https://registry.npmjs.org/commitizen/-/commitizen-4.2.3.tgz#088d0ef72500240d331b11e02e288223667c1475"
  2826 + integrity sha512-pYlYEng7XMV2TW4xtjDKBGqeJ0Teq2zyRSx2S3Ml1XAplHSlJZK8vm1KdGclpMEZuGafbS5TeHXIVnHk8RWIzQ==
  2827 + dependencies:
  2828 + cachedir "2.2.0"
  2829 + cz-conventional-changelog "3.2.0"
  2830 + dedent "0.7.0"
  2831 + detect-indent "6.0.0"
  2832 + find-node-modules "2.0.0"
  2833 + find-root "1.1.0"
  2834 + fs-extra "8.1.0"
  2835 + glob "7.1.4"
  2836 + inquirer "6.5.2"
  2837 + is-utf8 "^0.2.1"
  2838 + lodash "^4.17.20"
  2839 + minimist "1.2.5"
  2840 + strip-bom "4.0.0"
  2841 + strip-json-comments "3.0.1"
  2842 +
2823 2843 common-tags@^1.8.0:
2824 2844 version "1.8.0"
2825 2845 resolved "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
... ... @@ -3159,6 +3179,20 @@ currently-unhandled@^0.4.1:
3159 3179 dependencies:
3160 3180 array-find-index "^1.0.1"
3161 3181  
  3182 +cz-conventional-changelog@3.2.0:
  3183 + version "3.2.0"
  3184 + resolved "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-3.2.0.tgz#6aef1f892d64113343d7e455529089ac9f20e477"
  3185 + integrity sha512-yAYxeGpVi27hqIilG1nh4A9Bnx4J3Ov+eXy4koL3drrR+IO9GaWPsKjik20ht608Asqi8TQPf0mczhEeyAtMzg==
  3186 + dependencies:
  3187 + chalk "^2.4.1"
  3188 + commitizen "^4.0.3"
  3189 + conventional-commit-types "^3.0.0"
  3190 + lodash.map "^4.5.1"
  3191 + longest "^2.0.1"
  3192 + word-wrap "^1.0.3"
  3193 + optionalDependencies:
  3194 + "@commitlint/load" ">6.1.1"
  3195 +
3162 3196 cz-conventional-changelog@3.3.0:
3163 3197 version "3.3.0"
3164 3198 resolved "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-3.3.0.tgz#9246947c90404149b3fe2cf7ee91acad3b7d22d2"
... ... @@ -3571,13 +3605,13 @@ eslint-visitor-keys@^2.0.0:
3571 3605 resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
3572 3606 integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
3573 3607  
3574   -eslint@^7.17.0:
3575   - version "7.17.0"
3576   - resolved "https://registry.npmjs.org/eslint/-/eslint-7.17.0.tgz#4ccda5bf12572ad3bf760e6f195886f50569adb0"
3577   - integrity sha512-zJk08MiBgwuGoxes5sSQhOtibZ75pz0J35XTRlZOk9xMffhpA9BTbQZxoXZzOl5zMbleShbGwtw+1kGferfFwQ==
  3608 +eslint@^7.18.0:
  3609 + version "7.18.0"
  3610 + resolved "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz#7fdcd2f3715a41fe6295a16234bd69aed2c75e67"
  3611 + integrity sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==
3578 3612 dependencies:
3579 3613 "@babel/code-frame" "^7.0.0"
3580   - "@eslint/eslintrc" "^0.2.2"
  3614 + "@eslint/eslintrc" "^0.3.0"
3581 3615 ajv "^6.10.0"
3582 3616 chalk "^4.0.0"
3583 3617 cross-spawn "^7.0.2"
... ... @@ -3601,7 +3635,7 @@ eslint@^7.17.0:
3601 3635 js-yaml "^3.13.1"
3602 3636 json-stable-stringify-without-jsonify "^1.0.1"
3603 3637 levn "^0.4.1"
3604   - lodash "^4.17.19"
  3638 + lodash "^4.17.20"
3605 3639 minimatch "^3.0.4"
3606 3640 natural-compare "^1.4.0"
3607 3641 optionator "^0.9.1"
... ... @@ -4423,10 +4457,10 @@ human-signals@^1.1.1:
4423 4457 resolved "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
4424 4458 integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
4425 4459  
4426   -husky@^4.3.7:
4427   - version "4.3.7"
4428   - resolved "https://registry.npmjs.org/husky/-/husky-4.3.7.tgz#ca47bbe6213c1aa8b16bbd504530d9600de91e88"
4429   - integrity sha512-0fQlcCDq/xypoyYSJvEuzbDPHFf8ZF9IXKJxlrnvxABTSzK1VPT2RKYQKrcgJ+YD39swgoB6sbzywUqFxUiqjw==
  4460 +husky@^4.3.8:
  4461 + version "4.3.8"
  4462 + resolved "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d"
  4463 + integrity sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==
4430 4464 dependencies:
4431 4465 chalk "^4.0.0"
4432 4466 ci-info "^2.0.0"
... ... @@ -7840,20 +7874,20 @@ vite-plugin-purge-icons@^0.5.1:
7840 7874 "@purge-icons/generated" "^0.5.1"
7841 7875 rollup-plugin-purge-icons "^0.5.1"
7842 7876  
7843   -vite-plugin-pwa@^0.3.6:
7844   - version "0.3.6"
7845   - resolved "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.3.6.tgz#a522af3fd5461991907f6829975b437f2c847339"
7846   - integrity sha512-GDgT8jFGHUz2j11I7Z0W+X5mnkaUoMVitJ/UjN/ezjy9HcXrvxaIVnhzMdESJSv+dxy4DD9ymD91cF9Ei6//cQ==
  7877 +vite-plugin-pwa@^0.3.8:
  7878 + version "0.3.8"
  7879 + resolved "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.3.8.tgz#c98a683ddbbe87bd55db79acb6a5c849f2a29879"
  7880 + integrity sha512-W5FBJeS3KjaCG1qu7LMTX9+E0u6qNHFk+hk917s4MnAlQ/XnBs30kgRXVBXtVAPhgvn8rqj2ww+2OYed+MKtIg==
7847 7881 dependencies:
7848 7882 debug "^4.3.2"
7849 7883 fast-glob "^3.2.4"
7850 7884 pretty-bytes "^5.5.0"
7851 7885 workbox-build "^6.0.2"
7852 7886  
7853   -vite@^2.0.0-beta.27:
7854   - version "2.0.0-beta.27"
7855   - resolved "https://registry.npmjs.org/vite/-/vite-2.0.0-beta.27.tgz#a2e4b3a698e67c89fd963ff51ee5283ec564c65c"
7856   - integrity sha512-1fGPjSVE4MmCGVguFy7pPurCLnvHu4fJSzVjejd9GoFqCNie+JKCpe3KGsxIb9B8ot/aDd4ISCB0+fH1/01FUA==
  7887 +vite@^2.0.0-beta.30:
  7888 + version "2.0.0-beta.30"
  7889 + resolved "https://registry.npmjs.org/vite/-/vite-2.0.0-beta.30.tgz#d0c1056d1fb05c489614360f92363eebec41a6b4"
  7890 + integrity sha512-wOeO64J3k4jGjCOkH/6RUcIyT/HOTaDZSiXE75aWYqV9hI7Q6uEeSXbAFtb9bG82RGLEWdsqtCvx5t7gaeqtsw==
7857 7891 dependencies:
7858 7892 esbuild "^0.8.26"
7859 7893 postcss "^8.2.1"
... ...