Bläddra i källkod

feat(layout): add mix sidebar mode

vben 4 år sedan
förälder
incheckning
e6db0d39b9
64 ändrade filer med 1523 tillägg och 1090 borttagningar
  1. 5 0
      CHANGELOG.zh_CN.md
  2. 14 14
      package.json
  3. 0 39
      src/assets/images/layout/menu-mix.svg
  4. 0 39
      src/assets/images/layout/menu-sidebar.svg
  5. 0 39
      src/assets/images/layout/menu-top.svg
  6. 4 15
      src/components/Application/src/AppLogo.vue
  7. 2 0
      src/components/Application/src/search/AppSearchModal.vue
  8. 6 1
      src/components/ClickOutSide/src/index.vue
  9. 1 1
      src/components/Markdown/src/index.vue
  10. 4 0
      src/components/Menu/index.ts
  11. 2 6
      src/components/Menu/src/BasicMenu.vue
  12. 1 0
      src/components/Menu/src/components/MenuItemTag.vue
  13. 47 78
      src/components/Menu/src/index.less
  14. 0 3
      src/components/Menu/src/props.ts
  15. 1 1
      src/components/Tinymce/src/Editor.vue
  16. 2 0
      src/design/var/index.less
  17. 84 0
      src/directives/clickOutside.ts
  18. 2 0
      src/enums/menuEnum.ts
  19. 9 3
      src/hooks/setting/useHeaderSetting.ts
  20. 11 5
      src/hooks/setting/useMenuSetting.ts
  21. 1 1
      src/hooks/web/useApexCharts.ts
  22. 1 1
      src/hooks/web/useI18n.ts
  23. 2 2
      src/layouts/default/header/components/Breadcrumb.vue
  24. 5 1
      src/layouts/default/header/index.vue
  25. 3 2
      src/layouts/default/index.vue
  26. 0 3
      src/layouts/default/menu/index.tsx
  27. 23 12
      src/layouts/default/setting/SettingDrawer.tsx
  28. 104 18
      src/layouts/default/setting/components/TypePicker.vue
  29. 6 6
      src/layouts/default/setting/enum.ts
  30. 2 3
      src/layouts/default/setting/handler.ts
  31. 51 1
      src/layouts/default/sider/LayoutSider.vue
  32. 408 0
      src/layouts/default/sider/MixSider.vue
  33. 0 51
      src/layouts/default/sider/index.less
  34. 5 3
      src/layouts/default/sider/index.vue
  35. 30 16
      src/layouts/default/sider/useLayoutSider.ts
  36. 4 1
      src/layouts/page/useCache.ts
  37. 3 2
      src/locales/lang/en/layout/setting.ts
  38. 6 0
      src/locales/lang/en/routes/demo/comp.ts
  39. 1 1
      src/locales/lang/en/routes/demo/level.ts
  40. 0 7
      src/locales/lang/en/routes/demo/tree.ts
  41. 3 2
      src/locales/lang/zh_CN/layout/setting.ts
  42. 1 1
      src/locales/lang/zh_CN/routes/demo/charts.ts
  43. 5 0
      src/locales/lang/zh_CN/routes/demo/comp.ts
  44. 1 1
      src/locales/lang/zh_CN/routes/demo/level.ts
  45. 0 7
      src/locales/lang/zh_CN/routes/demo/tree.ts
  46. 143 0
      src/router/menus/modules/demo/comp.ts
  47. 0 31
      src/router/menus/modules/demo/editor.ts
  48. 22 0
      src/router/menus/modules/demo/feat.ts
  49. 0 42
      src/router/menus/modules/demo/form.ts
  50. 0 73
      src/router/menus/modules/demo/table.ts
  51. 0 25
      src/router/menus/modules/demo/tree.ts
  52. 285 1
      src/router/routes/modules/demo/comp.ts
  53. 0 54
      src/router/routes/modules/demo/editor.ts
  54. 0 52
      src/router/routes/modules/demo/excel.ts
  55. 46 1
      src/router/routes/modules/demo/feat.ts
  56. 0 74
      src/router/routes/modules/demo/form.ts
  57. 0 140
      src/router/routes/modules/demo/table.ts
  58. 0 43
      src/router/routes/modules/demo/tree.ts
  59. 2 0
      src/settings/colorSetting.ts
  60. 3 3
      src/settings/projectSetting.ts
  61. 3 2
      src/store/modules/tab.ts
  62. 2 1
      src/types/config.d.ts
  63. 2 0
      src/utils/factory/createAsyncComponent.tsx
  64. 155 162
      yarn.lock

+ 5 - 0
CHANGELOG.zh_CN.md

@@ -3,6 +3,11 @@
 ### ✨ Features
 
 - 新增 `v-ripple`水波纹指令
+- 新增左侧菜单混合模式
+
+### ✨ Refactor
+
+- 移除折叠显示菜单名配置
 
 ### 🐛 Bug Fixes
 

+ 14 - 14
package.json

@@ -21,23 +21,23 @@
     "reinstall": "rimraf yarn.lock && rimraf package.lock.json && rimraf node_modules && npm run bootstrap"
   },
   "dependencies": {
-    "@iconify/iconify": "^2.0.0-rc.2",
-    "@vueuse/core": "^4.0.0-rc.9",
+    "@iconify/iconify": "^2.0.0-rc.4",
+    "@vueuse/core": "^4.0.0",
     "ant-design-vue": "^2.0.0-rc.5",
-    "apexcharts": "^3.22.3",
+    "apexcharts": "^3.23.0",
     "axios": "^0.21.0",
     "crypto-es": "^1.2.6",
     "echarts": "^4.9.0",
-    "lodash-es": "^4.17.15",
+    "lodash-es": "^4.17.20",
     "mockjs": "^1.1.0",
     "moment": "^2.29.1",
     "nprogress": "^0.2.0",
     "path-to-regexp": "^6.2.0",
     "qrcode": "^1.4.4",
     "sortablejs": "^1.12.0",
-    "vditor": "^3.7.2",
+    "vditor": "^3.7.3",
     "vue": "^3.0.4",
-    "vue-i18n": "^9.0.0-beta.13",
+    "vue-i18n": "9.0.0-beta.14",
     "vue-router": "^4.0.1",
     "vue-types": "^3.0.1",
     "vuex": "^4.0.0-rc.2",
@@ -48,7 +48,7 @@
   "devDependencies": {
     "@commitlint/cli": "^11.0.0",
     "@commitlint/config-conventional": "^11.0.0",
-    "@iconify/json": "^1.1.272",
+    "@iconify/json": "^1.1.275",
     "@ls-lint/ls-lint": "^1.9.2",
     "@purge-icons/generated": "^0.4.1",
     "@types/echarts": "^4.9.3",
@@ -61,10 +61,10 @@
     "@types/qrcode": "^1.3.5",
     "@types/rollup-plugin-visualizer": "^2.6.0",
     "@types/sortablejs": "^1.10.6",
-    "@types/yargs": "^15.0.11",
+    "@types/yargs": "^15.0.12",
     "@types/zxcvbn": "^4.4.0",
-    "@typescript-eslint/eslint-plugin": "^4.10.0",
-    "@typescript-eslint/parser": "^4.10.0",
+    "@typescript-eslint/eslint-plugin": "^4.11.0",
+    "@typescript-eslint/parser": "^4.11.0",
     "@vue/compiler-sfc": "^3.0.4",
     "@vuedx/typecheck": "^0.2.4-0",
     "@vuedx/typescript-plugin-vue": "^0.2.4-0",
@@ -75,16 +75,16 @@
     "cross-env": "^7.0.3",
     "dot-prop": "^6.0.1",
     "dotenv": "^8.2.0",
-    "eslint": "^7.15.0",
-    "eslint-config-prettier": "^7.0.0",
+    "eslint": "^7.16.0",
+    "eslint-config-prettier": "^7.1.0",
     "eslint-plugin-prettier": "^3.3.0",
-    "eslint-plugin-vue": "^7.2.0",
+    "eslint-plugin-vue": "^7.3.0",
     "esno": "^0.3.0",
     "fs-extra": "^9.0.1",
     "globrex": "^0.1.2",
     "husky": "^4.3.6",
     "koa-static": "^5.0.0",
-    "less": "^3.13.0",
+    "less": "^4.0.0",
     "lint-staged": "^10.5.3",
     "portfinder": "^1.0.28",
     "postcss-import": "^12.0.1",

+ 0 - 39
src/assets/images/layout/menu-mix.svg

@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" 
-    xmlns="http://www.w3.org/2000/svg" 
-    xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs>
-        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
-            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
-            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
-            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
-            <feMerge>
-                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
-                <feMergeNode in="SourceGraphic"></feMergeNode>
-            </feMerge>
-        </filter>
-        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
-        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
-            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
-            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
-            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
-        </filter>
-    </defs>
-    <g id="配置面板" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)">
-            <g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)">
-                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
-                    <mask id="mask-3" fill="white">
-                        <use xlink:href="#path-2"></use>
-                    </mask>
-                    <g id="Rectangle-18">
-                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
-                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
-                    </g>
-                    <rect id="Rectangle-18" fill="#fff" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
-                    <rect id="Rectangle-11" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
-                </g>
-            </g>
-        </g>
-    </g>
-</svg>

+ 0 - 39
src/assets/images/layout/menu-sidebar.svg

@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
-  xmlns="http://www.w3.org/2000/svg"
-  xmlns:xlink="http://www.w3.org/1999/xlink">
-  <defs>
-    <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
-      <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
-      <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
-      <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
-      <feMerge>
-        <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
-        <feMergeNode in="SourceGraphic"></feMergeNode>
-      </feMerge>
-    </filter>
-    <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
-    <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
-      <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
-      <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
-      <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
-    </filter>
-  </defs>
-  <g width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-    <g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)">
-      <g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)">
-        <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
-          <mask id="mask-3" fill="white">
-            <use xlink:href="#path-2"></use>
-          </mask>
-          <g id="Rectangle-18">
-            <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
-            <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
-          </g>
-          <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
-          <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
-        </g>
-      </g>
-    </g>
-  </g>
-</svg>

+ 0 - 39
src/assets/images/layout/menu-top.svg

@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
-  xmlns="http://www.w3.org/2000/svg"
-  xmlns:xlink="http://www.w3.org/1999/xlink">
-
-  <defs>
-    <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
-      <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
-      <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
-      <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
-      <feMerge>
-        <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
-        <feMergeNode in="SourceGraphic"></feMergeNode>
-      </feMerge>
-    </filter>
-    <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
-    <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
-      <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
-      <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
-      <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
-    </filter>
-  </defs>
-  <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-    <g id="setting-copy-2" transform="translate(-1254.000000, -337.000000)">
-      <g id="Group-8" transform="translate(1167.000000, 0.000000)">
-        <g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 338.000000)">
-          <mask id="mask-3" fill="white">
-            <use xlink:href="#path-2"></use>
-          </mask>
-          <g id="Rectangle-18">
-            <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
-            <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
-          </g>
-          <rect id="Rectangle-11" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
-        </g>
-      </g>
-    </g>
-  </g>
-</svg>

+ 4 - 15
src/components/Application/src/AppLogo.vue

@@ -3,14 +3,10 @@
  * @Description: logo component
 -->
 <template>
-  <div
-    class="anticon"
-    :class="[prefixCls, theme, { 'collapsed-show-title': getCollapsedShowTitle }]"
-    @click="handleGoHome"
-  >
+  <div class="anticon" :class="[prefixCls, theme]" @click="handleGoHome">
     <img src="/@/assets/images/logo.png" />
     <div class="ml-2 ellipsis" :class="[`${prefixCls}__title`]" v-show="showTitle">
-      {{ globSetting.title }}
+      {{ title }}
     </div>
   </div>
 </template>
@@ -40,9 +36,7 @@
     setup() {
       const { prefixCls } = useDesign('app-logo');
 
-      const { getCollapsedShowTitle } = useMenuSetting();
-
-      const globSetting = useGlobSetting();
+      const { title } = useGlobSetting();
 
       const go = useGo();
 
@@ -52,8 +46,7 @@
 
       return {
         handleGoHome,
-        globSetting,
-        getCollapsedShowTitle,
+        title,
         prefixCls,
       };
     },
@@ -70,10 +63,6 @@
     cursor: pointer;
     transition: all 0.2s ease;
 
-    &.collapsed-show-title {
-      padding-left: 20px;
-    }
-
     &.light {
       border-bottom: 1px solid @border-color-base;
     }

+ 2 - 0
src/components/Application/src/search/AppSearchModal.vue

@@ -65,10 +65,12 @@
   import { useI18n } from '/@/hooks/web/useI18n';
   import { ClickOutSide } from '/@/components/ClickOutSide';
   import { useAppInject } from '/@/hooks/web/useAppInject';
+
   export default defineComponent({
     name: 'AppSearchModal',
     components: { SearchOutlined, ClickOutSide, AppSearchFooter },
     emits: ['close'],
+
     props: {
       visible: Boolean,
     },

+ 6 - 1
src/components/ClickOutSide/src/index.vue

@@ -3,12 +3,13 @@
 </template>
 <script lang="ts">
   import type { Ref } from 'vue';
-  import { defineComponent, ref } from 'vue';
+  import { defineComponent, ref, onMounted } from 'vue';
 
   import { useClickOutside } from '/@/hooks/web/useClickOutside';
 
   export default defineComponent({
     name: 'ClickOutSide',
+    emits: ['mounted', 'clickOutside'],
     setup(_, { emit }) {
       const wrap = ref<ElRef>(null);
 
@@ -16,6 +17,10 @@
         emit('clickOutside');
       });
 
+      onMounted(() => {
+        emit('mounted');
+      });
+
       return { wrap };
     },
   });

+ 1 - 1
src/components/Markdown/src/index.vue

@@ -57,7 +57,7 @@
       onUnmounted(() => {
         const vditorInstance = unref(vditorRef);
         if (!vditorInstance) return;
-        vditorInstance.destroy();
+        vditorInstance?.destroy?.();
       });
 
       return {

+ 4 - 0
src/components/Menu/index.ts

@@ -6,4 +6,8 @@ export const BasicMenu = createAsyncComponent(() => import('./src/BasicMenu.vue'
   loading: false,
 });
 
+export const MenuTag = createAsyncComponent(() => import('./src/components/MenuItemTag.vue'), {
+  loading: false,
+});
+
 withInstall(BasicMenu);

+ 2 - 6
src/components/Menu/src/BasicMenu.vue

@@ -17,7 +17,7 @@
         :item="item"
         :theme="theme"
         :level="1"
-        :showTitle="showTitle"
+        :showTitle="!getCollapsed"
         :isHorizontal="isHorizontal"
       />
     </template>
@@ -95,16 +95,12 @@
           prefixCls,
           `justify-${align}`,
           {
-            [`${prefixCls}--hide-title`]: !unref(showTitle),
-            [`${prefixCls}--collapsed-show-title`]: props.collapsedShowTitle,
             [`${prefixCls}__second`]: !props.isHorizontal && unref(getSplit),
             [`${prefixCls}__sidebar-hor`]: unref(getIsTopMenu),
           },
         ];
       });
 
-      const showTitle = computed(() => props.collapsedShowTitle && unref(getCollapsed));
-
       const getInlineCollapseOptions = computed(() => {
         const isInline = props.mode === MenuModeEnum.INLINE;
 
@@ -168,7 +164,7 @@
         getMenuClass,
         handleOpenChange,
         getOpenKeys,
-        showTitle,
+        getCollapsed,
         ...toRefs(menuState),
       };
     },

+ 1 - 0
src/components/Menu/src/components/MenuItemTag.vue

@@ -15,6 +15,7 @@
 
       const getShowTag = computed(() => {
         const { item, showTitle, isHorizontal } = props;
+
         if (!item || showTitle || isHorizontal) return false;
 
         const { tag } = item;

+ 47 - 78
src/components/Menu/src/index.less

@@ -48,47 +48,16 @@
     opacity: 1 !important;
   }
 
-  &--hide-title {
-    &.ant-menu-inline-collapsed > .ant-menu-item,
-    &.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item,
-    &.ant-menu-inline-collapsed
-      > .ant-menu-item-group
-      > .ant-menu-item-group-list
-      > .ant-menu-submenu
-      > .ant-menu-submenu-title,
-    &.ant-menu-inline-collapsed .ant-menu-submenu-title {
-      padding-right: 16px !important;
-      padding-left: 16px !important;
-    }
-  }
-
-  &--collapsed-show-title.ant-menu-inline-collapsed {
-    .@{basic-menu-prefix-cls}-item__level1 {
-      padding: 2px 0;
-    }
-
-    & > li[role='menuitem']:not(.ant-menu-submenu),
-    & > li > .ant-menu-submenu-title {
-      display: flex;
-      margin-top: 10px;
-      font-size: 12px;
-      flex-direction: column;
-      align-items: center;
-      line-height: 24px;
-    }
-
-    & > li > .ant-menu-submenu-title {
-      line-height: 24px;
-    }
-    .@{basic-menu-content-prefix-cls}-wrapper {
-      display: flex;
-      justify-content: center;
-      align-items: center;
-      flex-direction: column;
-      .@{basic-menu-content-prefix-cls}--show-title {
-        line-height: 30px;
-      }
-    }
+  &.ant-menu-inline-collapsed > .ant-menu-item,
+  &.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item,
+  &.ant-menu-inline-collapsed
+    > .ant-menu-item-group
+    > .ant-menu-item-group-list
+    > .ant-menu-submenu
+    > .ant-menu-submenu-title,
+  &.ant-menu-inline-collapsed .ant-menu-submenu-title {
+    padding-right: 16px !important;
+    padding-left: 16px !important;
   }
 
   .@{basic-menu-content-prefix-cls}-wrapper {
@@ -252,43 +221,6 @@
     }
   }
 
-  .@{basic-menu-tag-prefix-cls} {
-    position: absolute;
-    top: calc(50% - 8px);
-    right: 30px;
-    display: inline-block;
-    padding: 2px 4px;
-    margin-right: 4px;
-    font-size: 12px;
-    line-height: 14px;
-    color: #fff;
-    border-radius: 2px;
-
-    &--dot {
-      top: calc(50% - 4px);
-      width: 8px;
-      height: 8px;
-      padding: 0;
-      border-radius: 50%;
-    }
-
-    &--primary {
-      background: @primary-color;
-    }
-
-    &--error {
-      background: @error-color;
-    }
-
-    &--success {
-      background: @success-color;
-    }
-
-    &--warn {
-      background: @warning-color;
-    }
-  }
-
   .ant-menu-submenu,
   .ant-menu-submenu-inline {
     transition: unset;
@@ -322,3 +254,40 @@
     }
   }
 }
+
+.@{basic-menu-tag-prefix-cls} {
+  position: absolute;
+  top: calc(50% - 8px);
+  right: 30px;
+  display: inline-block;
+  padding: 2px 4px;
+  margin-right: 4px;
+  font-size: 12px;
+  line-height: 14px;
+  color: #fff;
+  border-radius: 2px;
+
+  &--dot {
+    top: calc(50% - 4px);
+    width: 6px;
+    height: 6px;
+    padding: 0;
+    border-radius: 50%;
+  }
+
+  &--primary {
+    background: @primary-color;
+  }
+
+  &--error {
+    background: @error-color;
+  }
+
+  &--success {
+    background: @success-color;
+  }
+
+  &--warn {
+    background: @warning-color;
+  }
+}

+ 0 - 3
src/components/Menu/src/props.ts

@@ -10,8 +10,6 @@ export const basicProps = {
     default: () => [],
   },
 
-  collapsedShowTitle: propTypes.bool,
-
   // 最好是4 倍数
   inlineIndent: propTypes.number.def(20),
   // 菜单组件的mode属性
@@ -19,7 +17,6 @@ export const basicProps = {
     type: String as PropType<MenuModeEnum>,
     default: MenuModeEnum.INLINE,
   },
-  showLogo: propTypes.bool,
   type: {
     type: String as PropType<MenuTypeEnum>,
     default: MenuTypeEnum.MIX,

+ 1 - 1
src/components/Tinymce/src/Editor.vue

@@ -107,7 +107,7 @@
 
       function destory() {
         if (getTinymce() !== null) {
-          getTinymce().remove(unref(editorRef));
+          getTinymce()?.remove?.(unref(editorRef));
         }
       }
 

+ 2 - 0
src/design/var/index.less

@@ -26,6 +26,8 @@
 
 @layout-sider-fixed-z-index: 510;
 
+@layout-mix-sider-fixed-z-index: 550;
+
 @preview-comp-z-index: 1000;
 
 @page-footer-z-index: 99;

+ 84 - 0
src/directives/clickOutside.ts

@@ -0,0 +1,84 @@
+import { on } from '/@/utils/domUtils';
+import { isServer } from '/@/utils/is';
+import type { ComponentPublicInstance, DirectiveBinding, ObjectDirective } from 'vue';
+
+type DocumentHandler = <T extends MouseEvent>(mouseup: T, mousedown: T) => void;
+
+type FlushList = Map<
+  HTMLElement,
+  {
+    documentHandler: DocumentHandler;
+    bindingFn: (...args: unknown[]) => unknown;
+  }
+>;
+
+const nodeList: FlushList = new Map();
+
+let startClick: MouseEvent;
+
+if (!isServer) {
+  on(document, 'mousedown', (e: MouseEvent) => (startClick = e));
+  on(document, 'mouseup', (e: MouseEvent) => {
+    for (const { documentHandler } of nodeList.values()) {
+      documentHandler(e, startClick);
+    }
+  });
+}
+
+function createDocumentHandler(el: HTMLElement, binding: DirectiveBinding): DocumentHandler {
+  let excludes: HTMLElement[] = [];
+  if (Array.isArray(binding.arg)) {
+    excludes = binding.arg;
+  } else {
+    // due to current implementation on binding type is wrong the type casting is necessary here
+    excludes.push((binding.arg as unknown) as HTMLElement);
+  }
+  return function (mouseup, mousedown) {
+    const popperRef = (binding.instance as ComponentPublicInstance<{
+      popperRef: Nullable<HTMLElement>;
+    }>).popperRef;
+    const mouseUpTarget = mouseup.target as Node;
+    const mouseDownTarget = mousedown.target as Node;
+    const isBound = !binding || !binding.instance;
+    const isTargetExists = !mouseUpTarget || !mouseDownTarget;
+    const isContainedByEl = el.contains(mouseUpTarget) || el.contains(mouseDownTarget);
+    const isSelf = el === mouseUpTarget;
+
+    const isTargetExcluded =
+      (excludes.length && excludes.some((item) => item?.contains(mouseUpTarget))) ||
+      (excludes.length && excludes.includes(mouseDownTarget as HTMLElement));
+    const isContainedByPopper =
+      popperRef && (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget));
+    if (
+      isBound ||
+      isTargetExists ||
+      isContainedByEl ||
+      isSelf ||
+      isTargetExcluded ||
+      isContainedByPopper
+    ) {
+      return;
+    }
+    binding.value();
+  };
+}
+
+const ClickOutside: ObjectDirective = {
+  beforeMount(el, binding) {
+    nodeList.set(el, {
+      documentHandler: createDocumentHandler(el, binding),
+      bindingFn: binding.value,
+    });
+  },
+  updated(el, binding) {
+    nodeList.set(el, {
+      documentHandler: createDocumentHandler(el, binding),
+      bindingFn: binding.value,
+    });
+  },
+  unmounted(el) {
+    nodeList.delete(el);
+  },
+};
+
+export default ClickOutside;

+ 2 - 0
src/enums/menuEnum.ts

@@ -4,6 +4,8 @@
 export enum MenuTypeEnum {
   // left menu
   SIDEBAR = 'sidebar',
+
+  MIX_SIDEBAR = 'mix-sidebar',
   // mixin menu
   MIX = 'mix',
   // top menu

+ 9 - 3
src/hooks/setting/useHeaderSetting.ts

@@ -16,6 +16,7 @@ const {
   getSplit,
   getShowHeaderTrigger,
   getIsSidebarType,
+  getIsMixSidebar,
   getIsTopMenu,
 } = useMenuSetting();
 const { getShowBreadCrumb, getShowLogo } = useRootSetting();
@@ -27,13 +28,18 @@ const getShowFullHeaderRef = computed(() => {
     !unref(getFullContent) &&
     unref(getShowMixHeaderRef) &&
     unref(getShowHeader) &&
-    !unref(getIsTopMenu)
+    !unref(getIsTopMenu) &&
+    !unref(getIsMixSidebar)
   );
 });
 
 const getShowInsetHeaderRef = computed(() => {
   const need = !unref(getFullContent) && unref(getShowHeader);
-  return (need && !unref(getShowMixHeaderRef)) || (need && unref(getIsTopMenu));
+  return (
+    (need && !unref(getShowMixHeaderRef)) ||
+    (need && unref(getIsTopMenu)) ||
+    (need && unref(getIsMixSidebar))
+  );
 });
 
 // Get header configuration
@@ -66,7 +72,7 @@ const getShowBread = computed(() => {
 });
 
 const getShowHeaderLogo = computed(() => {
-  return unref(getShowLogo) && !unref(getIsSidebarType);
+  return unref(getShowLogo) && !unref(getIsSidebarType) && !unref(getIsMixSidebar);
 });
 
 const getShowContent = computed(() => {

+ 11 - 5
src/hooks/setting/useMenuSetting.ts

@@ -37,10 +37,10 @@ const getCanDrag = computed(() => unref(getMenuSetting).canDrag);
 
 const getAccordion = computed(() => unref(getMenuSetting).accordion);
 
-const getCollapsedShowTitle = computed(() => unref(getMenuSetting).collapsedShowTitle);
-
 const getTopMenuAlign = computed(() => unref(getMenuSetting).topMenuAlign);
 
+const getCloseMixSidebarOnChange = computed(() => unref(getMenuSetting).closeMixSidebarOnChange);
+
 const getIsSidebarType = computed(() => unref(getMenuType) === MenuTypeEnum.SIDEBAR);
 
 const getIsTopMenu = computed(() => unref(getMenuType) === MenuTypeEnum.TOP_MENU);
@@ -61,6 +61,10 @@ const getIsHorizontal = computed(() => {
   return unref(getMenuMode) === MenuModeEnum.HORIZONTAL;
 });
 
+const getIsMixSidebar = computed(() => {
+  return unref(getMenuType) === MenuTypeEnum.MIX_SIDEBAR;
+});
+
 const getIsMixMode = computed(() => {
   return unref(getMenuMode) === MenuModeEnum.INLINE && unref(getMenuType) === MenuTypeEnum.MIX;
 });
@@ -70,14 +74,15 @@ const getRealWidth = computed(() => {
 });
 
 const getMiniWidthNumber = computed(() => {
-  const { collapsedShowTitle } = unref(getMenuSetting);
-  return collapsedShowTitle ? SIDE_BAR_SHOW_TIT_MINI_WIDTH : SIDE_BAR_MINI_WIDTH;
+  return SIDE_BAR_MINI_WIDTH;
 });
 
 const getCalcContentWidth = computed(() => {
   const width =
     unref(getIsTopMenu) || !unref(getShowMenu) || (unref(getSplit) && unref(getMenuHidden))
       ? 0
+      : unref(getIsMixSidebar)
+      ? SIDE_BAR_SHOW_TIT_MINI_WIDTH
       : unref(getRealWidth);
 
   return `calc(100% - ${unref(width)}px)`;
@@ -124,7 +129,6 @@ export function useMenuSetting() {
     getMenuTheme,
     getCanDrag,
     getIsHorizontal,
-    getCollapsedShowTitle,
     getIsSidebarType,
     getAccordion,
     getShowTopMenu,
@@ -135,5 +139,7 @@ export function useMenuSetting() {
     getMenuBgColor,
     getShowSidebar,
     getIsMixMode,
+    getIsMixSidebar,
+    getCloseMixSidebarOnChange,
   };
 }

+ 1 - 1
src/hooks/web/useApexCharts.ts

@@ -51,7 +51,7 @@ export function useApexCharts(elRef: Ref<HTMLDivElement>) {
 
   tryOnUnmounted(() => {
     if (!chartInstance) return;
-    chartInstance.destroy();
+    chartInstance?.destroy?.();
     chartInstance = null;
   });
 

+ 1 - 1
src/hooks/web/useI18n.ts

@@ -36,5 +36,5 @@ export function useI18n(namespace?: string) {
 // Mainly to configure the vscode i18nn ally plugin. This function is only used for routing and menus. Please use useI18n for other places
 
 // 为什么要编写此函数?
-// 主要用于配合vscode i18nn ally插件。此功能仅用于路由和菜单。请在其他地方使用useIs18n
+// 主要用于配合vscode i18nn ally插件。此功能仅用于路由和菜单。请在其他地方使用useI18n
 export const t = (key: string) => key;

+ 2 - 2
src/layouts/default/header/components/Breadcrumb.vue

@@ -48,7 +48,7 @@
         if (currentRoute.value.name === REDIRECT_NAME) {
           return;
         }
-        const matched = currentRoute.value.matched;
+        const matched = currentRoute.value?.matched;
         if (!matched || matched.length === 0) return;
 
         let breadcrumbList = filter(toRaw(matched), (item) => {
@@ -102,7 +102,7 @@
         color: @breadcrumb-item-normal-color;
 
         a {
-          color: @text-color-base;
+          color: rgba(0, 0, 0, 0.65);
 
           &:hover {
             color: @primary-color;

+ 5 - 1
src/layouts/default/header/index.vue

@@ -10,7 +10,9 @@
         :style="getLogoWidth"
       />
       <LayoutTrigger
-        v-if="(getShowContent && getShowHeaderTrigger && !getSplit) || getIsMobile"
+        v-if="
+          (getShowContent && getShowHeaderTrigger && !getSplit && !getIsMixSidebar) || getIsMobile
+        "
         :theme="getHeaderTheme"
         :sider="false"
       />
@@ -110,6 +112,7 @@
         getSplit,
         getIsMixMode,
         getMenuWidth,
+        getIsMixSidebar,
       } = useMenuSetting();
       const { getShowLocale } = useLocaleSetting();
       const { getUseErrorHandle } = useRootSetting();
@@ -173,6 +176,7 @@
         getUseLockPage,
         getUseErrorHandle,
         getLogoWidth,
+        getIsMixSidebar,
       };
     },
   });

+ 3 - 2
src/layouts/default/index.vue

@@ -2,7 +2,7 @@
   <Layout :class="prefixCls">
     <LayoutFeatures />
     <LayoutHeader fixed v-if="getShowFullHeaderRef" />
-    <Layout>
+    <Layout class="ant-layout-has-sider">
       <LayoutSideBar v-if="getShowSidebar || getIsMobile" />
       <Layout :class="`${prefixCls}__main`">
         <LayoutMultipleHeader />
@@ -53,13 +53,14 @@
 
       const { getShowFullHeaderRef } = useHeaderSetting();
 
-      const { getShowSidebar } = useMenuSetting();
+      const { getShowSidebar, getIsMixSidebar } = useMenuSetting();
 
       return {
         getShowFullHeaderRef,
         getShowSidebar,
         prefixCls,
         getIsMobile,
+        getIsMixSidebar,
       };
     },
   });

+ 0 - 3
src/layouts/default/menu/index.tsx

@@ -43,7 +43,6 @@ export default defineComponent({
     const {
       getMenuMode,
       getMenuType,
-      getCollapsedShowTitle,
       getMenuTheme,
       getCollapsed,
       getAccordion,
@@ -132,12 +131,10 @@ export default defineComponent({
           isHorizontal={props.isHorizontal}
           type={unref(getMenuType)}
           mode={unref(getComputedMenuMode)}
-          collapsedShowTitle={unref(getCollapsedShowTitle)}
           theme={unref(getComputedMenuTheme)}
           items={unref(menusRef)}
           accordion={unref(getAccordion)}
           onMenuClick={handleMenuClick}
-          showLogo={unref(getIsShowLogo)}
         />
       );
     }

+ 23 - 12
src/layouts/default/setting/SettingDrawer.tsx

@@ -61,7 +61,6 @@ export default defineComponent({
       getShowMenu,
       getMenuType,
       getTrigger,
-      getCollapsedShowTitle,
       getMenuFixed,
       getCollapsed,
       getCanDrag,
@@ -71,6 +70,8 @@ export default defineComponent({
       getMenuBgColor,
       getIsTopMenu,
       getSplit,
+      getIsMixSidebar,
+      getCloseMixSidebarOnChange,
     } = useMenuSetting();
 
     const {
@@ -106,6 +107,13 @@ export default defineComponent({
             def={unref(getSplit)}
             disabled={!unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX}
           />
+
+          <SwitchItem
+            title={t('layout.setting.closeMixSidebarOnChange')}
+            event={HandlerEnum.MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE}
+            def={unref(getCloseMixSidebarOnChange)}
+            disabled={!unref(getIsMixSidebar)}
+          />
         </>
       );
     }
@@ -166,14 +174,9 @@ export default defineComponent({
             title={t('layout.setting.menuCollapse')}
             event={HandlerEnum.MENU_COLLAPSED}
             def={unref(getCollapsed)}
-            disabled={!unref(getShowMenuRef)}
-          />
-          <SwitchItem
-            title={t('layout.setting.collapseMenuDisplayName')}
-            event={HandlerEnum.MENU_COLLAPSED_SHOW_TITLE}
-            def={unref(getCollapsedShowTitle)}
-            disabled={!unref(getShowMenuRef) || !unref(getCollapsed)}
+            disabled={!unref(getShowMenuRef) || unref(getIsMixSidebar)}
           />
+
           <SwitchItem
             title={t('layout.setting.fixedHeader')}
             event={HandlerEnum.HEADER_FIXED}
@@ -184,7 +187,7 @@ export default defineComponent({
             title={t('layout.setting.fixedSideBar')}
             event={HandlerEnum.MENU_FIXED}
             def={unref(getMenuFixed)}
-            disabled={!unref(getShowMenuRef)}
+            disabled={!unref(getShowMenuRef) || unref(getIsMixSidebar)}
           />
           <SelectItem
             title={t('layout.setting.topMenuLayout')}
@@ -192,7 +195,10 @@ export default defineComponent({
             def={unref(getTopMenuAlign)}
             options={topMenuAlignOptions}
             disabled={
-              !unref(getShowHeader) || unref(getSplit) || (!unref(getIsTopMenu) && !unref(getSplit))
+              !unref(getShowHeader) ||
+              unref(getSplit) ||
+              (!unref(getIsTopMenu) && !unref(getSplit)) ||
+              unref(getIsMixSidebar)
             }
           />
           <SelectItem
@@ -200,7 +206,7 @@ export default defineComponent({
             event={HandlerEnum.MENU_TRIGGER}
             def={triggerDef}
             options={triggerOptions}
-            disabled={!unref(getShowMenuRef)}
+            disabled={!unref(getShowMenuRef) || unref(getIsMixSidebar)}
           />
           <SelectItem
             title={t('layout.setting.contentMode')}
@@ -282,7 +288,12 @@ export default defineComponent({
             event={HandlerEnum.HEADER_SHOW}
             def={unref(getShowHeader)}
           />
-          <SwitchItem title="Logo" event={HandlerEnum.SHOW_LOGO} def={unref(getShowLogo)} />
+          <SwitchItem
+            title="Logo"
+            event={HandlerEnum.SHOW_LOGO}
+            def={unref(getShowLogo)}
+            disabled={unref(getIsMixSidebar)}
+          />
           <SwitchItem
             title={t('layout.setting.footer')}
             event={HandlerEnum.SHOW_FOOTER}

+ 104 - 18
src/layouts/default/setting/components/TypePicker.vue

@@ -6,12 +6,13 @@
           @click="handler(item)"
           :class="[
             `${prefixCls}__item`,
+            `${prefixCls}__item--${item.type}`,
             {
               [`${prefixCls}__item--active`]: def === item.type,
             },
           ]"
         >
-          <img :src="item.src" />
+          <div class="mix-sidebar" />
         </div>
       </Tooltip>
     </template>
@@ -58,33 +59,118 @@
 
     &__item {
       position: relative;
-      width: 70px;
-      height: 50px;
-      margin: 0 20px 20px 0;
+      width: 56px;
+      height: 48px;
+      margin-right: 16px;
+      overflow: hidden;
       cursor: pointer;
-      border-radius: 6px;
+      background-color: #f0f2f5;
+      border-radius: 4px;
+      box-shadow: 0 1px 2.5px 0 rgba(0, 0, 0, 0.18);
 
+      &::before,
       &::after {
         position: absolute;
-        top: 50%;
-        left: 50%;
-        width: 0;
-        height: 0;
         content: '';
-        opacity: 0;
-        transition: all 0.3s;
       }
 
+      &--sidebar {
+        &::before {
+          top: 0;
+          left: 0;
+          z-index: 1;
+          width: 33%;
+          height: 100%;
+          background-color: #273352;
+          border-radius: 4px 0 0 4px;
+        }
+
+        &::after {
+          top: 0;
+          left: 0;
+          width: 100%;
+          height: 25%;
+          background-color: #fff;
+        }
+      }
+
+      &--mix {
+        &::before {
+          top: 0;
+          left: 0;
+          width: 33%;
+          height: 100%;
+          background-color: #fff;
+          border-radius: 4px 0 0 4px;
+        }
+
+        &::after {
+          top: 0;
+          left: 0;
+          z-index: 1;
+          width: 100%;
+          height: 25%;
+          background-color: #273352;
+        }
+      }
+
+      &--top-menu {
+        &::after {
+          top: 0;
+          left: 0;
+          width: 100%;
+          height: 25%;
+          background-color: #273352;
+        }
+      }
+
+      &--mix-sidebar {
+        &::before {
+          top: 0;
+          left: 0;
+          z-index: 1;
+          width: 25%;
+          height: 100%;
+          background-color: #273352;
+          border-radius: 4px 0 0 4px;
+        }
+
+        &::after {
+          top: 0;
+          left: 0;
+          width: 100%;
+          height: 25%;
+          background-color: #fff;
+        }
+
+        .mix-sidebar {
+          position: absolute;
+          left: 25%;
+          width: 15%;
+          height: 100%;
+          background-color: #fff;
+        }
+      }
+
+      // &::after {
+      //   position: absolute;
+      //   top: 50%;
+      //   left: 50%;
+      //   width: 0;
+      //   height: 0;
+      //   content: '';
+      //   opacity: 0;
+      //   transition: all 0.3s;
+      // }
+
       &:hover,
       &--active {
+        padding: 12px;
+        border: 2px solid @primary-color;
+
+        &::before,
         &::after {
-          top: -8px;
-          left: -4px;
-          width: 80px;
-          height: 64px;
-          border: 2px solid @primary-color;
-          border-radius: 6px;
-          opacity: 1;
+          border-radius: 0;
         }
       }
     }

+ 6 - 6
src/layouts/default/setting/enum.ts

@@ -1,9 +1,6 @@
 import { ContentEnum, RouterTransitionEnum } from '/@/enums/appEnum';
 import { MenuModeEnum, MenuTypeEnum, TopMenuAlignEnum, TriggerEnum } from '/@/enums/menuEnum';
 
-import mixImg from '/@/assets/images/layout/menu-mix.svg';
-import sidebarImg from '/@/assets/images/layout/menu-sidebar.svg';
-import menuTopImg from '/@/assets/images/layout/menu-top.svg';
 import { useI18n } from '/@/hooks/web/useI18n';
 
 const { t } = useI18n();
@@ -22,6 +19,7 @@ export enum HandlerEnum {
   MENU_THEME,
   MENU_SPLIT,
   MENU_FIXED,
+  MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE,
 
   // header
   HEADER_SHOW,
@@ -116,19 +114,21 @@ export const menuTypeList = [
     title: t('layout.setting.menuTypeSidebar'),
     mode: MenuModeEnum.INLINE,
     type: MenuTypeEnum.SIDEBAR,
-    src: sidebarImg,
   },
   {
     title: t('layout.setting.menuTypeMix'),
     mode: MenuModeEnum.INLINE,
     type: MenuTypeEnum.MIX,
-    src: mixImg,
   },
 
   {
     title: t('layout.setting.menuTypeTopMenu'),
     mode: MenuModeEnum.HORIZONTAL,
     type: MenuTypeEnum.TOP_MENU,
-    src: menuTopImg,
+  },
+  {
+    title: t('layout.setting.menuTypeMixSidebar'),
+    mode: MenuModeEnum.INLINE,
+    type: MenuTypeEnum.MIX_SIDEBAR,
   },
 ];

+ 2 - 3
src/layouts/default/setting/handler.ts

@@ -48,9 +48,6 @@ export function handler(event: HandlerEnum, value: any): DeepPartial<ProjectConf
     case HandlerEnum.MENU_WIDTH:
       return { menuSetting: { menuWidth: value } };
 
-    case HandlerEnum.MENU_COLLAPSED_SHOW_TITLE:
-      return { menuSetting: { collapsedShowTitle: value } };
-
     case HandlerEnum.MENU_SHOW_SIDEBAR:
       return { menuSetting: { show: value } };
 
@@ -60,6 +57,8 @@ export function handler(event: HandlerEnum, value: any): DeepPartial<ProjectConf
 
     case HandlerEnum.MENU_SPLIT:
       return { menuSetting: { split: value } };
+    case HandlerEnum.MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE:
+      return { menuSetting: { closeMixSidebarOnChange: value } };
 
     case HandlerEnum.MENU_FIXED:
       return { menuSetting: { fixed: value } };

+ 51 - 1
src/layouts/default/sider/LayoutSider.vue

@@ -128,5 +128,55 @@
   });
 </script>
 <style lang="less">
-  @import './index.less';
+  @import (reference) '../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-layout-sideBar';
+
+  .@{prefix-cls} {
+    z-index: @layout-sider-fixed-z-index;
+
+    &--fixed {
+      position: fixed;
+      top: 0;
+      left: 0;
+      height: 100%;
+    }
+
+    &--mix {
+      top: @header-height;
+      height: calc(100% - @header-height);
+    }
+
+    &.ant-layout-sider-dark {
+      background: @sider-dark-bg-color;
+
+      .ant-layout-sider-trigger {
+        color: darken(@white, 25%);
+        background: @trigger-dark-bg-color;
+
+        &:hover {
+          color: @white;
+          background: @trigger-dark-hover-bg-color;
+        }
+      }
+    }
+
+    &:not(.ant-layout-sider-dark) {
+      // box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
+
+      .ant-layout-sider-trigger {
+        color: @text-color-base;
+        border-top: 1px solid @border-color-light;
+      }
+    }
+
+    .ant-layout-sider-zero-width-trigger {
+      top: 40%;
+      z-index: 10;
+    }
+
+    & .ant-layout-sider-trigger {
+      height: 36px;
+      line-height: 36px;
+    }
+  }
 </style>

+ 408 - 0
src/layouts/default/sider/MixSider.vue

@@ -0,0 +1,408 @@
+<template>
+  <div :class="`${prefixCls}-dom`" />
+
+  <div
+    v-click-outside="handleClickOutside"
+    :class="[
+      prefixCls,
+      getMenuTheme,
+      {
+        open: openMenu,
+      },
+    ]"
+  >
+    <AppLogo :showTitle="false" :class="`${prefixCls}-logo`" />
+    <ScrollContainer>
+      <ul :class="`${prefixCls}-module`">
+        <li
+          :class="[
+            `${prefixCls}-module__item `,
+            {
+              [`${prefixCls}-module__item--active`]: item.path === activePath,
+            },
+          ]"
+          v-for="item in menuModules"
+          :key="item.path"
+          @click="hanldeModuleClick(item.path)"
+        >
+          <MenuTag :item="item" :showTitle="false" :isHorizontal="false" />
+          <g-icon
+            :class="`${prefixCls}-module__icon`"
+            :size="22"
+            :icon="item.meta && item.meta.icon"
+          />
+          <p :class="`${prefixCls}-module__name`">{{ t(item.name) }}</p>
+        </li>
+      </ul>
+    </ScrollContainer>
+
+    <div :class="`${prefixCls}-menu-list`" ref="sideRef" :style="getMenuStyle">
+      <div
+        :class="[
+          `${prefixCls}-menu-list__title`,
+          {
+            show: openMenu,
+          },
+        ]"
+      >
+        <span class="text"> {{ title }}</span>
+      </div>
+      <ScrollContainer :class="`${prefixCls}-menu-list__content`">
+        <BasicMenu
+          :isHorizontal="false"
+          mode="inline"
+          :items="chilrenMenus"
+          :theme="getMenuTheme"
+          @menuClick="handleMenuClick"
+        />
+      </ScrollContainer>
+      <div
+        v-show="getShowDragBar && openMenu"
+        :class="`${prefixCls}-drag-bar`"
+        ref="dragBarRef"
+      ></div>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, onMounted, ref, computed, CSSProperties, unref } from 'vue';
+  import type { Menu } from '/@/router/types';
+  import type { RouteLocationNormalized } from 'vue-router';
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { getShallowMenus, getChildrenMenus, getCurrentParentPath } from '/@/router/menus';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { ScrollContainer } from '/@/components/Container';
+  import { AppLogo } from '/@/components/Application';
+  import { useGo } from '/@/hooks/web/usePage';
+  import { BasicMenu, MenuTag } from '/@/components/Menu';
+  import { listenerLastChangeTab } from '/@/logics/mitt/tabChange';
+  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+  import { useDragLine } from './useLayoutSider';
+
+  import clickOutside from '/@/directives/clickOutside';
+  import { useGlobSetting } from '/@/hooks/setting';
+
+  export default defineComponent({
+    name: 'LayoutMixSider',
+    components: {
+      ScrollContainer,
+      AppLogo,
+      BasicMenu,
+      MenuTag,
+    },
+    directives: {
+      clickOutside,
+    },
+    setup() {
+      let menuModules = ref<Menu[]>([]);
+      const activePath = ref('');
+      const chilrenMenus = ref<Menu[]>([]);
+      const openMenu = ref(false);
+      const dragBarRef = ref<ElRef>(null);
+      const sideRef = ref<ElRef>(null);
+      const currentRoute = ref<Nullable<RouteLocationNormalized>>(null);
+
+      const { prefixCls } = useDesign('layout-mix-sider');
+      const go = useGo();
+      const { t } = useI18n();
+      const {
+        getMenuWidth,
+        getCanDrag,
+        getCloseMixSidebarOnChange,
+        getMenuTheme,
+        getMixSidebarTheme,
+      } = useMenuSetting();
+      const { title } = useGlobSetting();
+
+      useDragLine(sideRef, dragBarRef, true);
+
+      const getMenuStyle = computed(
+        (): CSSProperties => {
+          return {
+            width: unref(openMenu) ? `${unref(getMenuWidth)}px` : 0,
+          };
+        }
+      );
+
+      const getShowDragBar = computed(() => unref(getCanDrag));
+
+      onMounted(async () => {
+        menuModules.value = await getShallowMenus();
+      });
+
+      listenerLastChangeTab((route) => {
+        currentRoute.value = route;
+        setActive();
+        if (unref(getCloseMixSidebarOnChange)) {
+          openMenu.value = false;
+        }
+      });
+
+      async function hanldeModuleClick(path: string) {
+        const children = await getChildrenMenus(path);
+
+        if (unref(activePath) === path) {
+          openMenu.value = !unref(openMenu);
+          if (!unref(openMenu)) {
+            setActive();
+          }
+        } else {
+          openMenu.value = true;
+          activePath.value = path;
+        }
+
+        if (!children || children.length === 0) {
+          go(path);
+          chilrenMenus.value = [];
+          openMenu.value = false;
+          return;
+        }
+        chilrenMenus.value = children;
+      }
+
+      async function setActive() {
+        const path = currentRoute.value?.path;
+        if (!path) return;
+        const parentPath = await getCurrentParentPath(path);
+        activePath.value = parentPath;
+        // hanldeModuleClick(parentPath);
+      }
+
+      function handleMenuClick(path: string) {
+        go(path);
+      }
+
+      function handleClickOutside() {
+        openMenu.value = false;
+        setActive();
+      }
+
+      return {
+        t,
+        prefixCls,
+        menuModules,
+        hanldeModuleClick,
+        activePath,
+        chilrenMenus,
+        getShowDragBar,
+        handleMenuClick,
+        getMenuStyle,
+        handleClickOutside,
+        sideRef,
+        dragBarRef,
+        title,
+        openMenu,
+        getMenuTheme,
+        getMixSidebarTheme,
+      };
+    },
+  });
+</script>
+<style lang="less">
+  @import (reference) '../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-layout-mix-sider';
+  @tag-prefix-cls: ~'@{namespace}-basic-menu-item-tag';
+  @width: 80px;
+  .@{prefix-cls} {
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: @layout-mix-sider-fixed-z-index;
+    width: @width;
+    height: 100%;
+    max-width: @width;
+    min-width: @width;
+    overflow: hidden;
+    background: @sider-dark-bg-color;
+    transition: all 0.2s ease 0s;
+    flex: 0 0 @width;
+    .@{tag-prefix-cls} {
+      position: absolute;
+      top: 6px;
+      right: 2px;
+    }
+
+    &-dom {
+      width: @width;
+      height: 100%;
+      max-width: @width;
+      min-width: @width;
+      overflow: hidden;
+      transition: all 0.2s ease 0s;
+      flex: 0 0 @width;
+    }
+
+    &-logo {
+      display: flex;
+      height: @header-height;
+      padding-left: 0 !important;
+      justify-content: center;
+
+      img {
+        width: @logo-width;
+        height: @logo-width;
+      }
+    }
+
+    &.light {
+      .@{prefix-cls}-logo {
+        border-bottom: 1px solid rgb(238, 238, 238);
+      }
+
+      &.open {
+        > .scroll-container {
+          border-right: 1px solid rgb(238, 238, 238);
+        }
+      }
+
+      .@{prefix-cls}-module {
+        &__item {
+          font-weight: normal;
+          color: rgba(0, 0, 0, 0.65);
+
+          &--active {
+            color: @primary-color;
+            background: unset;
+          }
+        }
+      }
+    }
+
+    &.dark {
+      &.open {
+        .@{prefix-cls}-logo {
+          border-bottom: 1px solid rgb(114 114 114);
+        }
+
+        > .scroll-container {
+          border-right: 1px solid rgb(114 114 114);
+        }
+      }
+      .@{prefix-cls}-menu-list {
+        background: @sider-dark-bg-color;
+
+        &__title {
+          color: @white;
+          border-bottom: none;
+          border-bottom: 1px solid rgb(114 114 114);
+        }
+      }
+    }
+
+    &-module {
+      position: relative;
+      height: calc(100% - @header-height) !important;
+      padding-top: 1px;
+
+      &__item {
+        position: relative;
+        padding: 12px 0;
+        color: rgba(255, 255, 255, 0.65);
+        text-align: center;
+        cursor: pointer;
+        transition: all 0.3s ease;
+
+        &:hover {
+          color: @white;
+        }
+        // &:hover,
+        &--active {
+          font-weight: 700;
+          color: @white;
+          background: @sider-dark-darken-bg-color;
+
+          &::before {
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 3px;
+            height: 100%;
+            background: @primary-color;
+            content: '';
+          }
+        }
+      }
+
+      &__icon {
+        margin-bottom: 8px;
+        font-size: 24px;
+      }
+
+      &__name {
+        margin-bottom: 0;
+        font-size: 12px;
+      }
+    }
+
+    &-menu-list {
+      position: fixed;
+      top: 0;
+      left: 80px;
+      width: 0;
+      width: 200px;
+      height: calc(100%);
+      background: #fff;
+      transition: width 0.2s;
+      .@{tag-prefix-cls} {
+        position: absolute;
+        top: 10px;
+        right: 30px;
+      }
+
+      &__title {
+        display: flex;
+        height: @header-height;
+        margin-left: -6px;
+        font-size: 18px;
+        color: @primary-color;
+        border-bottom: 1px solid rgb(238, 238, 238);
+        opacity: 0;
+        transition: unset;
+        // justify-content: center;
+        align-items: center;
+        justify-content: start;
+
+        &.show {
+          opacity: 1;
+          transition: all 0.5s ease;
+        }
+      }
+
+      &__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;
+      top: 0;
+      right: -3px;
+      width: 3px;
+      height: 100%;
+      cursor: ew-resize;
+      background: #f8f8f9;
+      border-top: none;
+      border-bottom: none;
+      box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15);
+    }
+  }
+</style>

+ 0 - 51
src/layouts/default/sider/index.less

@@ -1,51 +0,0 @@
-@import (reference) '../../../design/index.less';
-@prefix-cls: ~'@{namespace}-layout-sideBar';
-
-.@{prefix-cls} {
-  z-index: @layout-sider-fixed-z-index;
-
-  &--fixed {
-    position: fixed;
-    top: 0;
-    left: 0;
-    height: 100%;
-  }
-
-  &--mix {
-    top: @header-height;
-    height: calc(100% - @header-height);
-  }
-
-  &.ant-layout-sider-dark {
-    background: @sider-dark-bg-color;
-
-    .ant-layout-sider-trigger {
-      color: darken(@white, 25%);
-      background: @trigger-dark-bg-color;
-
-      &:hover {
-        color: @white;
-        background: @trigger-dark-hover-bg-color;
-      }
-    }
-  }
-
-  &:not(.ant-layout-sider-dark) {
-    // box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
-
-    .ant-layout-sider-trigger {
-      color: @text-color-base;
-      border-top: 1px solid @border-color-light;
-    }
-  }
-
-  .ant-layout-sider-zero-width-trigger {
-    top: 40%;
-    z-index: 10;
-  }
-
-  & .ant-layout-sider-trigger {
-    height: 36px;
-    line-height: 36px;
-  }
-}

+ 5 - 3
src/layouts/default/sider/index.vue

@@ -10,6 +10,7 @@
   >
     <Sider />
   </Drawer>
+  <MixSider v-else-if="getIsMixSidebar" />
   <Sider v-else />
 </template>
 <script lang="ts">
@@ -17,16 +18,17 @@
 
   import Sider from './LayoutSider.vue';
   import { Drawer } from 'ant-design-vue';
+  import MixSider from './MixSider.vue';
   import { useAppInject } from '/@/hooks/web/useAppInject';
   import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
   import { useDesign } from '/@/hooks/web/useDesign';
   export default defineComponent({
     name: 'SiderWrapper',
-    components: { Sider, Drawer },
+    components: { Sider, Drawer, MixSider },
     setup() {
       const { prefixCls } = useDesign('layout-sider-wrapper');
       const { getIsMobile } = useAppInject();
-      const { setMenuSetting, getCollapsed, getMenuWidth } = useMenuSetting();
+      const { setMenuSetting, getCollapsed, getMenuWidth, getIsMixSidebar } = useMenuSetting();
 
       function handleClose() {
         setMenuSetting({
@@ -34,7 +36,7 @@
         });
       }
 
-      return { prefixCls, getIsMobile, getCollapsed, handleClose, getMenuWidth };
+      return { prefixCls, getIsMobile, getCollapsed, handleClose, getMenuWidth, getIsMixSidebar };
     },
   });
 </script>

+ 30 - 16
src/layouts/default/sider/useLayoutSider.ts

@@ -71,21 +71,30 @@ export function useTrigger(getIsMobile: Ref<boolean>) {
  * @param siderRef
  * @param dragBarRef
  */
-export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) {
+export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>, mix = false) {
   const { getMiniWidthNumber, getCollapsed, setMenuSetting } = useMenuSetting();
 
   onMounted(() => {
     nextTick(() => {
-      const [exec] = useDebounce(changeWrapWidth, 20);
+      const [exec] = useDebounce(changeWrapWidth, 80);
       exec();
     });
   });
 
+  function getEl(elRef: Ref<ElRef | ComponentRef>): any {
+    const el = unref(elRef);
+    if (!el) return null;
+    if (Reflect.has(el, '$el')) {
+      return (unref(elRef) as ComponentRef)?.$el;
+    }
+    return unref(elRef);
+  }
+
   function handleMouseMove(ele: HTMLElement, wrap: HTMLElement, clientX: number) {
     document.onmousemove = function (innerE) {
       let iT = (ele as any).left + (innerE.clientX - clientX);
       innerE = innerE || window.event;
-      const maxT = 600;
+      const maxT = 800;
       const minT = unref(getMiniWidthNumber);
       iT < 0 && (iT = 0);
       iT > maxT && (iT = maxT);
@@ -97,31 +106,36 @@ export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) {
 
   // Drag and drop in the menu area-release the mouse
   function removeMouseup(ele: any) {
-    const wrap = unref(siderRef).$el;
+    const wrap = getEl(siderRef);
     document.onmouseup = function () {
       document.onmousemove = null;
       document.onmouseup = null;
+      wrap.style.transition = 'width 0.2s';
       const width = parseInt(wrap.style.width);
-      const miniWidth = unref(getMiniWidthNumber);
 
-      if (!unref(getCollapsed)) {
-        width > miniWidth + 20
-          ? setMenuSetting({ menuWidth: width })
-          : setMenuSetting({ collapsed: true });
+      if (!mix) {
+        const miniWidth = unref(getMiniWidthNumber);
+        if (!unref(getCollapsed)) {
+          width > miniWidth + 20
+            ? setMenuSetting({ menuWidth: width })
+            : setMenuSetting({ collapsed: true });
+        } else {
+          width > miniWidth && setMenuSetting({ collapsed: false, menuWidth: width });
+        }
       } else {
-        width > miniWidth && setMenuSetting({ collapsed: false, menuWidth: width });
+        setMenuSetting({ menuWidth: width });
       }
+
       ele.releaseCapture?.();
     };
   }
 
   function changeWrapWidth() {
-    const ele = unref(dragBarRef)?.$el;
-    if (!ele) {
-      return;
-    }
-    const side = unref(siderRef);
-    const wrap = (side || {}).$el;
+    const ele = getEl(dragBarRef);
+    if (!ele) return;
+    const wrap = getEl(siderRef);
+    if (!wrap) return;
+
     ele.onmousedown = (e: any) => {
       wrap.style.transition = 'unset';
       const clientX = e?.clientX;

+ 4 - 1
src/layouts/page/useCache.ts

@@ -15,7 +15,10 @@ export function useCache(isPage: boolean) {
     if (routeName && ![ParentLayoutName].includes(routeName)) {
       name.value = routeName;
     } else {
-      const matched = currentRoute.value.matched;
+      const matched = currentRoute.value?.matched;
+      if (!matched) {
+        return;
+      }
       const len = matched.length;
       if (len < 2) return;
       name.value = matched[len - 2].name as string;

+ 3 - 2
src/locales/lang/en/layout/setting.ts

@@ -12,7 +12,8 @@ export default {
   menuTriggerTop: 'Top',
   // menu type
   menuTypeSidebar: 'Left menu mode',
-  menuTypeMix: 'Mixed mode',
+  menuTypeMixSidebar: 'Left menu mixed mode',
+  menuTypeMix: 'Top Menu Mix mode',
   menuTypeTopMenu: 'Top menu mode',
 
   on: 'On',
@@ -35,6 +36,7 @@ export default {
   interfaceDisplay: 'Interface display',
   animation: 'Animation',
   splitMenu: 'Split menu',
+  closeMixSidebarOnChange: 'Switch page to close menu',
 
   headerTheme: 'Header theme',
   sidebarTheme: 'Menu theme',
@@ -43,7 +45,6 @@ export default {
   menuSearch: 'Menu search',
   menuAccordion: 'Sidebar accordion',
   menuCollapse: 'Collapse menu',
-  collapseMenuDisplayName: 'Collapse menu display name',
   topMenuLayout: 'Top menu layout',
   menuCollapseButton: 'Menu collapse button',
   contentMode: 'Content area width',

+ 6 - 0
src/locales/lang/en/routes/demo/comp.ts

@@ -9,6 +9,12 @@ export default {
   scrollAction: 'Scroll Function',
   virtualScroll: 'Virtual Scroll',
 
+  tree: 'Tree',
+
+  treeBasic: 'Basic',
+  editTree: 'Right-click',
+  actionTree: 'Function operation',
+
   modal: 'Modal',
   drawer: 'Drawer',
   desc: 'Desc',

+ 1 - 1
src/locales/lang/en/routes/demo/level.ts

@@ -1,3 +1,3 @@
 export default {
-  level: 'Multi menu cache',
+  level: 'MultiMenu',
 };

+ 0 - 7
src/locales/lang/en/routes/demo/tree.ts

@@ -1,7 +0,0 @@
-export default {
-  tree: 'Tree',
-
-  basic: 'Basic',
-  editTree: 'Right-click',
-  actionTree: 'Function operation',
-};

+ 3 - 2
src/locales/lang/zh_CN/layout/setting.ts

@@ -12,7 +12,8 @@ export default {
   menuTriggerTop: '顶部',
   // menu type
   menuTypeSidebar: '左侧菜单模式',
-  menuTypeMix: '混合模式',
+  menuTypeMixSidebar: '左侧菜单混合模式',
+  menuTypeMix: '顶部菜单混合模式',
   menuTypeTopMenu: '顶部菜单模式',
 
   on: '开',
@@ -34,6 +35,7 @@ export default {
   interfaceDisplay: '界面显示',
   animation: '动画',
   splitMenu: '分割菜单',
+  closeMixSidebarOnChange: '切换页面关闭菜单',
 
   headerTheme: '顶栏主题',
   sidebarTheme: '菜单主题',
@@ -42,7 +44,6 @@ export default {
   menuSearch: '菜单搜索',
   menuAccordion: '侧边菜单手风琴模式',
   menuCollapse: '折叠菜单',
-  collapseMenuDisplayName: '折叠菜单显示名称',
   topMenuLayout: '顶部菜单布局',
   menuCollapseButton: '菜单折叠按钮',
   contentMode: '内容区域宽度',

+ 1 - 1
src/locales/lang/zh_CN/routes/demo/charts.ts

@@ -1,5 +1,5 @@
 export default {
-  charts: '图表',
+  charts: '图表',
   map: '地图',
   line: '折线图',
   pie: '饼图',

+ 5 - 0
src/locales/lang/zh_CN/routes/demo/comp.ts

@@ -9,6 +9,11 @@ export default {
   scrollAction: '滚动函数',
   virtualScroll: '虚拟滚动',
 
+  tree: 'Tree',
+  treeBasic: '基础树',
+  editTree: '右键示例',
+  actionTree: '函数操作示例',
+
   modal: '弹窗扩展',
   drawer: '抽屉扩展',
   desc: '详情组件',

+ 1 - 1
src/locales/lang/zh_CN/routes/demo/level.ts

@@ -1,3 +1,3 @@
 export default {
-  level: '多级菜单缓存',
+  level: '多级菜单',
 };

+ 0 - 7
src/locales/lang/zh_CN/routes/demo/tree.ts

@@ -1,7 +0,0 @@
-export default {
-  tree: 'Tree',
-
-  basic: '基础树',
-  editTree: '右键示例',
-  actionTree: '函数操作示例',
-};

+ 143 - 0
src/router/menus/modules/demo/comp.ts

@@ -15,6 +15,107 @@ const menu: MenuModule = {
         name: t('routes.demo.comp.basic'),
       },
       {
+        path: 'form',
+        name: t('routes.demo.form.form'),
+
+        children: [
+          {
+            path: 'basic',
+            name: t('routes.demo.form.basic'),
+          },
+          {
+            path: 'useForm',
+            name: t('routes.demo.form.useForm'),
+          },
+          {
+            path: 'refForm',
+            name: t('routes.demo.form.refForm'),
+          },
+          {
+            path: 'advancedForm',
+            name: t('routes.demo.form.advancedForm'),
+          },
+          {
+            path: 'ruleForm',
+            name: t('routes.demo.form.ruleForm'),
+          },
+          {
+            path: 'dynamicForm',
+            name: t('routes.demo.form.dynamicForm'),
+          },
+          {
+            path: 'customerForm',
+            name: t('routes.demo.form.customerForm'),
+          },
+        ],
+      },
+      {
+        path: 'table',
+        name: t('routes.demo.table.table'),
+        children: [
+          {
+            path: 'basic',
+            name: t('routes.demo.table.basic'),
+          },
+          {
+            path: 'treeTable',
+            name: t('routes.demo.table.treeTable'),
+          },
+          {
+            path: 'fetchTable',
+            name: t('routes.demo.table.fetchTable'),
+          },
+          {
+            path: 'fixedColumn',
+            name: t('routes.demo.table.fixedColumn'),
+          },
+          {
+            path: 'customerCell',
+            name: t('routes.demo.table.customerCell'),
+          },
+          {
+            path: 'formTable',
+            name: t('routes.demo.table.formTable'),
+          },
+          {
+            path: 'useTable',
+            name: t('routes.demo.table.useTable'),
+          },
+          {
+            path: 'refTable',
+            name: t('routes.demo.table.refTable'),
+          },
+          {
+            path: 'multipleHeader',
+            name: t('routes.demo.table.multipleHeader'),
+          },
+          {
+            path: 'mergeHeader',
+            name: t('routes.demo.table.mergeHeader'),
+          },
+          {
+            path: 'expandTable',
+            name: t('routes.demo.table.expandTable'),
+          },
+          {
+            path: 'fixedHeight',
+            name: t('routes.demo.table.fixedHeight'),
+          },
+          {
+            path: 'footerTable',
+            name: t('routes.demo.table.footerTable'),
+          },
+          {
+            path: 'editCellTable',
+            name: t('routes.demo.table.editCellTable'),
+          },
+          {
+            path: 'editRowTable',
+            name: t('routes.demo.table.editRowTable'),
+          },
+        ],
+      },
+      {
         path: 'countTo',
         name: t('routes.demo.comp.countTo'),
       },
@@ -55,6 +156,48 @@ const menu: MenuModule = {
         },
       },
       {
+        path: 'tree',
+        name: t('routes.demo.comp.tree'),
+        children: [
+          {
+            path: 'basic',
+            name: t('routes.demo.comp.treeBasic'),
+          },
+          {
+            path: 'editTree',
+            name: t('routes.demo.comp.editTree'),
+          },
+          {
+            path: 'actionTree',
+            name: t('routes.demo.comp.actionTree'),
+          },
+        ],
+      },
+      {
+        name: t('routes.demo.editor.editor'),
+        path: 'editor',
+        children: [
+          {
+            path: 'markdown',
+            name: t('routes.demo.editor.markdown'),
+          },
+          {
+            path: 'tinymce',
+            name: t('routes.demo.editor.tinymce'),
+            children: [
+              {
+                path: 'index',
+                name: t('routes.demo.editor.tinymceBasic'),
+              },
+              {
+                path: 'editor',
+                name: t('routes.demo.editor.tinymceForm'),
+              },
+            ],
+          },
+        ],
+      },
+      {
         path: 'scroll',
         name: t('routes.demo.comp.scroll'),
         children: [

+ 0 - 31
src/router/menus/modules/demo/editor.ts

@@ -1,31 +0,0 @@
-import type { MenuModule } from '/@/router/types.d';
-import { t } from '/@/hooks/web/useI18n';
-
-const menu: MenuModule = {
-  orderNo: 500,
-  menu: {
-    name: t('routes.demo.editor.editor'),
-    path: '/editor',
-    children: [
-      {
-        path: 'markdown',
-        name: t('routes.demo.editor.markdown'),
-      },
-      {
-        path: 'tinymce',
-        name: t('routes.demo.editor.tinymce'),
-        children: [
-          {
-            path: 'index',
-            name: t('routes.demo.editor.tinymceBasic'),
-          },
-          {
-            path: 'editor',
-            name: t('routes.demo.editor.tinymceForm'),
-          },
-        ],
-      },
-    ],
-  },
-};
-export default menu;

+ 22 - 0
src/router/menus/modules/demo/feat.ts

@@ -63,6 +63,28 @@ const menu: MenuModule = {
         name: t('routes.demo.feat.errorLog'),
       },
       {
+        name: t('routes.demo.excel.excel'),
+        path: 'excel',
+        children: [
+          {
+            path: 'customExport',
+            name: t('routes.demo.excel.customExport'),
+          },
+          {
+            path: 'jsonExport',
+            name: t('routes.demo.excel.jsonExport'),
+          },
+          {
+            path: 'arrayExport',
+            name: t('routes.demo.excel.arrayExport'),
+          },
+          {
+            path: 'importExcel',
+            name: t('routes.demo.excel.importExcel'),
+          },
+        ],
+      },
+      {
         path: 'testTab',
         name: t('routes.demo.feat.tab'),
         children: [

+ 0 - 42
src/router/menus/modules/demo/form.ts

@@ -1,42 +0,0 @@
-import type { MenuModule } from '/@/router/types.d';
-import { t } from '/@/hooks/web/useI18n';
-
-const menu: MenuModule = {
-  orderNo: 40,
-  menu: {
-    path: '/form',
-    name: t('routes.demo.form.form'),
-
-    children: [
-      {
-        path: 'basic',
-        name: t('routes.demo.form.basic'),
-      },
-      {
-        path: 'useForm',
-        name: t('routes.demo.form.useForm'),
-      },
-      {
-        path: 'refForm',
-        name: t('routes.demo.form.refForm'),
-      },
-      {
-        path: 'advancedForm',
-        name: t('routes.demo.form.advancedForm'),
-      },
-      {
-        path: 'ruleForm',
-        name: t('routes.demo.form.ruleForm'),
-      },
-      {
-        path: 'dynamicForm',
-        name: t('routes.demo.form.dynamicForm'),
-      },
-      {
-        path: 'customerForm',
-        name: t('routes.demo.form.customerForm'),
-      },
-    ],
-  },
-};
-export default menu;

+ 0 - 73
src/router/menus/modules/demo/table.ts

@@ -1,73 +0,0 @@
-import type { MenuModule } from '/@/router/types.d';
-import { t } from '/@/hooks/web/useI18n';
-
-const menu: MenuModule = {
-  orderNo: 30,
-  menu: {
-    path: '/table',
-    name: t('routes.demo.table.table'),
-    children: [
-      {
-        path: 'basic',
-        name: t('routes.demo.table.basic'),
-      },
-      {
-        path: 'treeTable',
-        name: t('routes.demo.table.treeTable'),
-      },
-      {
-        path: 'fetchTable',
-        name: t('routes.demo.table.fetchTable'),
-      },
-      {
-        path: 'fixedColumn',
-        name: t('routes.demo.table.fixedColumn'),
-      },
-      {
-        path: 'customerCell',
-        name: t('routes.demo.table.customerCell'),
-      },
-      {
-        path: 'formTable',
-        name: t('routes.demo.table.formTable'),
-      },
-      {
-        path: 'useTable',
-        name: t('routes.demo.table.useTable'),
-      },
-      {
-        path: 'refTable',
-        name: t('routes.demo.table.refTable'),
-      },
-      {
-        path: 'multipleHeader',
-        name: t('routes.demo.table.multipleHeader'),
-      },
-      {
-        path: 'mergeHeader',
-        name: t('routes.demo.table.mergeHeader'),
-      },
-      {
-        path: 'expandTable',
-        name: t('routes.demo.table.expandTable'),
-      },
-      {
-        path: 'fixedHeight',
-        name: t('routes.demo.table.fixedHeight'),
-      },
-      {
-        path: 'footerTable',
-        name: t('routes.demo.table.footerTable'),
-      },
-      {
-        path: 'editCellTable',
-        name: t('routes.demo.table.editCellTable'),
-      },
-      {
-        path: 'editRowTable',
-        name: t('routes.demo.table.editRowTable'),
-      },
-    ],
-  },
-};
-export default menu;

+ 0 - 25
src/router/menus/modules/demo/tree.ts

@@ -1,25 +0,0 @@
-import type { MenuModule } from '/@/router/types.d';
-import { t } from '/@/hooks/web/useI18n';
-
-const menu: MenuModule = {
-  orderNo: 50,
-  menu: {
-    path: '/tree',
-    name: t('routes.demo.tree.tree'),
-    children: [
-      {
-        path: 'basic',
-        name: t('routes.demo.tree.basic'),
-      },
-      {
-        path: 'editTree',
-        name: t('routes.demo.tree.editTree'),
-      },
-      {
-        path: 'actionTree',
-        name: t('routes.demo.tree.actionTree'),
-      },
-    ],
-  },
-};
-export default menu;

+ 285 - 1
src/router/routes/modules/demo/comp.ts

@@ -22,6 +22,208 @@ const comp: AppRouteModule = {
         title: t('routes.demo.comp.basic'),
       },
     },
+
+    {
+      path: 'form',
+      name: 'FormDemo',
+      redirect: '/comp/form/basic',
+      component: getParentLayout('FormDemo'),
+      meta: {
+        // icon: 'mdi:form-select',
+        title: t('routes.demo.form.form'),
+      },
+      children: [
+        {
+          path: 'basic',
+          name: 'FormBasicDemo',
+          component: () => import('/@/views/demo/form/index.vue'),
+          meta: {
+            title: t('routes.demo.form.basic'),
+          },
+        },
+        {
+          path: 'useForm',
+          name: 'UseFormDemo',
+          component: () => import('/@/views/demo/form/UseForm.vue'),
+          meta: {
+            title: t('routes.demo.form.useForm'),
+          },
+        },
+        {
+          path: 'refForm',
+          name: 'RefFormDemo',
+          component: () => import('/@/views/demo/form/RefForm.vue'),
+          meta: {
+            title: t('routes.demo.form.refForm'),
+          },
+        },
+        {
+          path: 'advancedForm',
+          name: 'AdvancedFormDemo',
+          component: () => import('/@/views/demo/form/AdvancedForm.vue'),
+          meta: {
+            title: t('routes.demo.form.advancedForm'),
+          },
+        },
+        {
+          path: 'ruleForm',
+          name: 'RuleFormDemo',
+          component: () => import('/@/views/demo/form/RuleForm.vue'),
+          meta: {
+            title: t('routes.demo.form.ruleForm'),
+          },
+        },
+        {
+          path: 'dynamicForm',
+          name: 'DynamicFormDemo',
+          component: () => import('/@/views/demo/form/DynamicForm.vue'),
+          meta: {
+            title: t('routes.demo.form.dynamicForm'),
+          },
+        },
+        {
+          path: 'customerForm',
+          name: 'CustomerFormDemo',
+          component: () => import('/@/views/demo/form/CustomerForm.vue'),
+          meta: {
+            title: t('routes.demo.form.customerForm'),
+          },
+        },
+      ],
+    },
+    {
+      path: 'table',
+      name: 'TableDemo',
+      redirect: '/comp/table/basic',
+      component: getParentLayout('TableDemo'),
+      meta: {
+        // icon: 'carbon:table-split',
+        title: t('routes.demo.table.table'),
+      },
+
+      children: [
+        {
+          path: 'basic',
+          name: 'TableBasicDemo',
+          component: () => import('/@/views/demo/table/Basic.vue'),
+          meta: {
+            title: t('routes.demo.table.basic'),
+          },
+        },
+        {
+          path: 'treeTable',
+          name: 'TreeTableDemo',
+          component: () => import('/@/views/demo/table/TreeTable.vue'),
+          meta: {
+            title: t('routes.demo.table.treeTable'),
+          },
+        },
+        {
+          path: 'fetchTable',
+          name: 'FetchTableDemo',
+          component: () => import('/@/views/demo/table/FetchTable.vue'),
+          meta: {
+            title: t('routes.demo.table.fetchTable'),
+          },
+        },
+        {
+          path: 'fixedColumn',
+          name: 'FixedColumnDemo',
+          component: () => import('/@/views/demo/table/FixedColumn.vue'),
+          meta: {
+            title: t('routes.demo.table.fixedColumn'),
+          },
+        },
+        {
+          path: 'customerCell',
+          name: 'CustomerCellDemo',
+          component: () => import('/@/views/demo/table/CustomerCell.vue'),
+          meta: {
+            title: t('routes.demo.table.customerCell'),
+          },
+        },
+        {
+          path: 'formTable',
+          name: 'FormTableDemo',
+          component: () => import('/@/views/demo/table/FormTable.vue'),
+          meta: {
+            title: t('routes.demo.table.formTable'),
+          },
+        },
+        {
+          path: 'useTable',
+          name: 'UseTableDemo',
+          component: () => import('/@/views/demo/table/UseTable.vue'),
+          meta: {
+            title: t('routes.demo.table.useTable'),
+          },
+        },
+        {
+          path: 'refTable',
+          name: 'RefTableDemo',
+          component: () => import('/@/views/demo/table/RefTable.vue'),
+          meta: {
+            title: t('routes.demo.table.refTable'),
+          },
+        },
+        {
+          path: 'multipleHeader',
+          name: 'MultipleHeaderDemo',
+          component: () => import('/@/views/demo/table/MultipleHeader.vue'),
+          meta: {
+            title: t('routes.demo.table.multipleHeader'),
+          },
+        },
+        {
+          path: 'mergeHeader',
+          name: 'MergeHeaderDemo',
+          component: () => import('/@/views/demo/table/MergeHeader.vue'),
+          meta: {
+            title: t('routes.demo.table.mergeHeader'),
+          },
+        },
+        {
+          path: 'expandTable',
+          name: 'ExpandTableDemo',
+          component: () => import('/@/views/demo/table/ExpandTable.vue'),
+          meta: {
+            title: t('routes.demo.table.expandTable'),
+          },
+        },
+        {
+          path: 'fixedHeight',
+          name: 'FixedHeightDemo',
+          component: () => import('/@/views/demo/table/FixedHeight.vue'),
+          meta: {
+            title: t('routes.demo.table.fixedHeight'),
+          },
+        },
+        {
+          path: 'footerTable',
+          name: 'FooterTableDemo',
+          component: () => import('/@/views/demo/table/FooterTable.vue'),
+          meta: {
+            title: t('routes.demo.table.footerTable'),
+          },
+        },
+        {
+          path: 'editCellTable',
+          name: 'EditCellTableDemo',
+          component: () => import('/@/views/demo/table/EditCellTable.vue'),
+          meta: {
+            title: t('routes.demo.table.editCellTable'),
+          },
+        },
+        {
+          path: 'editRowTable',
+          name: 'EditRowTableDemo',
+          component: () => import('/@/views/demo/table/EditRowTable.vue'),
+          meta: {
+            title: t('routes.demo.table.editRowTable'),
+          },
+        },
+      ],
+    },
     {
       path: 'transition',
       name: 'transitionDemo',
@@ -38,7 +240,89 @@ const comp: AppRouteModule = {
         title: t('routes.demo.comp.countTo'),
       },
     },
-
+    {
+      path: 'tree',
+      name: 'TreeDemo',
+      redirect: '/comp/tree/basic',
+      component: getParentLayout('TreeDemo'),
+      meta: {
+        // icon: 'clarity:tree-view-line',
+        title: t('routes.demo.comp.tree'),
+      },
+      children: [
+        {
+          path: 'basic',
+          name: 'BasicTreeDemo',
+          component: () => import('/@/views/demo/tree/index.vue'),
+          meta: {
+            title: t('routes.demo.comp.treeBasic'),
+          },
+        },
+        {
+          path: 'editTree',
+          name: 'EditTreeDemo',
+          component: () => import('/@/views/demo/tree/EditTree.vue'),
+          meta: {
+            title: t('routes.demo.comp.editTree'),
+          },
+        },
+        {
+          path: 'actionTree',
+          name: 'ActionTreeDemo',
+          component: () => import('/@/views/demo/tree/ActionTree.vue'),
+          meta: {
+            title: t('routes.demo.comp.actionTree'),
+          },
+        },
+      ],
+    },
+    {
+      path: 'editor',
+      name: 'EditorDemo',
+      redirect: '/comp/editor/markdown',
+      component: getParentLayout('EditorDemo'),
+      meta: {
+        // icon: 'carbon:table-split',
+        title: t('routes.demo.editor.editor'),
+      },
+      children: [
+        {
+          path: 'markdown',
+          name: 'MarkdownDemo',
+          component: () => import('/@/views/demo/editor/Markdown.vue'),
+          meta: {
+            title: t('routes.demo.editor.markdown'),
+          },
+        },
+        {
+          path: 'tinymce',
+          component: getParentLayout('TinymceDemo'),
+          name: 'TinymceDemo',
+          meta: {
+            title: t('routes.demo.editor.tinymce'),
+          },
+          redirect: '/comp/editor/tinymce/index',
+          children: [
+            {
+              path: 'index',
+              name: 'TinymceBasicDemo',
+              component: () => import('/@/views/demo/editor/tinymce/index.vue'),
+              meta: {
+                title: t('routes.demo.editor.tinymceBasic'),
+              },
+            },
+            {
+              path: 'editor',
+              name: 'TinymceFormDemo',
+              component: () => import('/@/views/demo/editor/tinymce/Editor.vue'),
+              meta: {
+                title: t('routes.demo.editor.tinymceForm'),
+              },
+            },
+          ],
+        },
+      ],
+    },
     {
       path: 'scroll',
       name: 'ScrollDemo',

+ 0 - 54
src/router/routes/modules/demo/editor.ts

@@ -1,54 +0,0 @@
-import type { AppRouteModule } from '/@/router/types';
-
-import { getParentLayout, LAYOUT } from '/@/router/constant';
-import { t } from '/@/hooks/web/useI18n';
-
-const editor: AppRouteModule = {
-  path: '/editor',
-  name: 'Editor',
-  component: LAYOUT,
-  redirect: '/editor/markdown',
-  meta: {
-    icon: 'carbon:table-split',
-    title: t('routes.demo.editor.editor'),
-  },
-  children: [
-    {
-      path: 'markdown',
-      name: 'MarkdownDemo',
-      component: () => import('/@/views/demo/editor/Markdown.vue'),
-      meta: {
-        title: t('routes.demo.editor.markdown'),
-      },
-    },
-    {
-      path: 'tinymce',
-      component: getParentLayout('TinymceDemo'),
-      name: 'TinymceDemo',
-      meta: {
-        title: t('routes.demo.editor.tinymce'),
-      },
-      redirect: '/editor/tinymce/index',
-      children: [
-        {
-          path: 'index',
-          name: 'TinymceBasicDemo',
-          component: () => import('/@/views/demo/editor/tinymce/index.vue'),
-          meta: {
-            title: t('routes.demo.editor.tinymceBasic'),
-          },
-        },
-        {
-          path: 'editor',
-          name: 'TinymceFormDemo',
-          component: () => import('/@/views/demo/editor/tinymce/Editor.vue'),
-          meta: {
-            title: t('routes.demo.editor.tinymceForm'),
-          },
-        },
-      ],
-    },
-  ],
-};
-
-export default editor;

+ 0 - 52
src/router/routes/modules/demo/excel.ts

@@ -1,52 +0,0 @@
-import type { AppRouteModule } from '/@/router/types';
-
-import { LAYOUT } from '/@/router/constant';
-import { t } from '/@/hooks/web/useI18n';
-
-const excel: AppRouteModule = {
-  path: '/excel',
-  name: 'Excel',
-  component: LAYOUT,
-  redirect: '/excel/customExport',
-  meta: {
-    icon: 'mdi:microsoft-excel',
-    title: t('routes.demo.excel.excel'),
-  },
-
-  children: [
-    {
-      path: 'customExport',
-      name: 'CustomExport',
-      component: () => import('/@/views/demo/excel/CustomExport.vue'),
-      meta: {
-        title: t('routes.demo.excel.customExport'),
-      },
-    },
-    {
-      path: 'jsonExport',
-      name: 'JsonExport',
-      component: () => import('/@/views/demo/excel/JsonExport.vue'),
-      meta: {
-        title: t('routes.demo.excel.jsonExport'),
-      },
-    },
-    {
-      path: 'arrayExport',
-      name: 'ArrayExport',
-      component: () => import('/@/views/demo/excel/ArrayExport.vue'),
-      meta: {
-        title: t('routes.demo.excel.arrayExport'),
-      },
-    },
-    {
-      path: 'importExcel',
-      name: 'ImportExcel',
-      component: () => import('/@/views/demo/excel/ImportExcel.vue'),
-      meta: {
-        title: t('routes.demo.excel.importExcel'),
-      },
-    },
-  ],
-};
-
-export default excel;

+ 46 - 1
src/router/routes/modules/demo/feat.ts

@@ -1,6 +1,6 @@
 import type { AppRouteModule } from '/@/router/types';
 
-import { LAYOUT } from '/@/router/constant';
+import { getParentLayout, LAYOUT } from '/@/router/constant';
 import { t } from '/@/hooks/web/useI18n';
 
 const feat: AppRouteModule = {
@@ -111,6 +111,51 @@ const feat: AppRouteModule = {
       },
     },
     {
+      path: 'excel',
+      name: 'Excel',
+      redirect: '/feat/excel/customExport',
+      component: getParentLayout('Excel'),
+      meta: {
+        // icon: 'mdi:microsoft-excel',
+        title: t('routes.demo.excel.excel'),
+      },
+
+      children: [
+        {
+          path: 'customExport',
+          name: 'CustomExport',
+          component: () => import('/@/views/demo/excel/CustomExport.vue'),
+          meta: {
+            title: t('routes.demo.excel.customExport'),
+          },
+        },
+        {
+          path: 'jsonExport',
+          name: 'JsonExport',
+          component: () => import('/@/views/demo/excel/JsonExport.vue'),
+          meta: {
+            title: t('routes.demo.excel.jsonExport'),
+          },
+        },
+        {
+          path: 'arrayExport',
+          name: 'ArrayExport',
+          component: () => import('/@/views/demo/excel/ArrayExport.vue'),
+          meta: {
+            title: t('routes.demo.excel.arrayExport'),
+          },
+        },
+        {
+          path: 'importExcel',
+          name: 'ImportExcel',
+          component: () => import('/@/views/demo/excel/ImportExcel.vue'),
+          meta: {
+            title: t('routes.demo.excel.importExcel'),
+          },
+        },
+      ],
+    },
+    {
       path: 'testTab/:id',
       name: 'TestTab',
       component: () => import('/@/views/demo/feat/tab-params/index.vue'),

+ 0 - 74
src/router/routes/modules/demo/form.ts

@@ -1,74 +0,0 @@
-import type { AppRouteModule } from '/@/router/types';
-
-import { LAYOUT } from '/@/router/constant';
-import { t } from '/@/hooks/web/useI18n';
-
-const form: AppRouteModule = {
-  path: '/form',
-  name: 'FormDemo',
-  component: LAYOUT,
-  redirect: '/form/basic',
-  meta: {
-    icon: 'mdi:form-select',
-    title: t('routes.demo.form.form'),
-  },
-  children: [
-    {
-      path: 'basic',
-      name: 'FormBasicDemo',
-      component: () => import('/@/views/demo/form/index.vue'),
-      meta: {
-        title: t('routes.demo.form.basic'),
-      },
-    },
-    {
-      path: 'useForm',
-      name: 'UseFormDemo',
-      component: () => import('/@/views/demo/form/UseForm.vue'),
-      meta: {
-        title: t('routes.demo.form.useForm'),
-      },
-    },
-    {
-      path: 'refForm',
-      name: 'RefFormDemo',
-      component: () => import('/@/views/demo/form/RefForm.vue'),
-      meta: {
-        title: t('routes.demo.form.refForm'),
-      },
-    },
-    {
-      path: 'advancedForm',
-      name: 'AdvancedFormDemo',
-      component: () => import('/@/views/demo/form/AdvancedForm.vue'),
-      meta: {
-        title: t('routes.demo.form.advancedForm'),
-      },
-    },
-    {
-      path: 'ruleForm',
-      name: 'RuleFormDemo',
-      component: () => import('/@/views/demo/form/RuleForm.vue'),
-      meta: {
-        title: t('routes.demo.form.ruleForm'),
-      },
-    },
-    {
-      path: 'dynamicForm',
-      name: 'DynamicFormDemo',
-      component: () => import('/@/views/demo/form/DynamicForm.vue'),
-      meta: {
-        title: t('routes.demo.form.dynamicForm'),
-      },
-    },
-    {
-      path: 'customerForm',
-      name: 'CustomerFormDemo',
-      component: () => import('/@/views/demo/form/CustomerForm.vue'),
-      meta: {
-        title: t('routes.demo.form.customerForm'),
-      },
-    },
-  ],
-};
-export default form;

+ 0 - 140
src/router/routes/modules/demo/table.ts

@@ -1,140 +0,0 @@
-import type { AppRouteModule } from '/@/router/types';
-
-import { LAYOUT } from '/@/router/constant';
-import { t } from '/@/hooks/web/useI18n';
-
-const table: AppRouteModule = {
-  path: '/table',
-  name: 'TableDemo',
-  component: LAYOUT,
-  redirect: '/table/basic',
-  meta: {
-    icon: 'carbon:table-split',
-    title: t('routes.demo.table.table'),
-  },
-
-  children: [
-    {
-      path: 'basic',
-      name: 'TableBasicDemo',
-      component: () => import('/@/views/demo/table/Basic.vue'),
-      meta: {
-        title: t('routes.demo.table.basic'),
-      },
-    },
-    {
-      path: 'treeTable',
-      name: 'TreeTableDemo',
-      component: () => import('/@/views/demo/table/TreeTable.vue'),
-      meta: {
-        title: t('routes.demo.table.treeTable'),
-      },
-    },
-    {
-      path: 'fetchTable',
-      name: 'FetchTableDemo',
-      component: () => import('/@/views/demo/table/FetchTable.vue'),
-      meta: {
-        title: t('routes.demo.table.fetchTable'),
-      },
-    },
-    {
-      path: 'fixedColumn',
-      name: 'FixedColumnDemo',
-      component: () => import('/@/views/demo/table/FixedColumn.vue'),
-      meta: {
-        title: t('routes.demo.table.fixedColumn'),
-      },
-    },
-    {
-      path: 'customerCell',
-      name: 'CustomerCellDemo',
-      component: () => import('/@/views/demo/table/CustomerCell.vue'),
-      meta: {
-        title: t('routes.demo.table.customerCell'),
-      },
-    },
-    {
-      path: 'formTable',
-      name: 'FormTableDemo',
-      component: () => import('/@/views/demo/table/FormTable.vue'),
-      meta: {
-        title: t('routes.demo.table.formTable'),
-      },
-    },
-    {
-      path: 'useTable',
-      name: 'UseTableDemo',
-      component: () => import('/@/views/demo/table/UseTable.vue'),
-      meta: {
-        title: t('routes.demo.table.useTable'),
-      },
-    },
-    {
-      path: 'refTable',
-      name: 'RefTableDemo',
-      component: () => import('/@/views/demo/table/RefTable.vue'),
-      meta: {
-        title: t('routes.demo.table.refTable'),
-      },
-    },
-    {
-      path: 'multipleHeader',
-      name: 'MultipleHeaderDemo',
-      component: () => import('/@/views/demo/table/MultipleHeader.vue'),
-      meta: {
-        title: t('routes.demo.table.multipleHeader'),
-      },
-    },
-    {
-      path: 'mergeHeader',
-      name: 'MergeHeaderDemo',
-      component: () => import('/@/views/demo/table/MergeHeader.vue'),
-      meta: {
-        title: t('routes.demo.table.mergeHeader'),
-      },
-    },
-    {
-      path: 'expandTable',
-      name: 'ExpandTableDemo',
-      component: () => import('/@/views/demo/table/ExpandTable.vue'),
-      meta: {
-        title: t('routes.demo.table.expandTable'),
-      },
-    },
-    {
-      path: 'fixedHeight',
-      name: 'FixedHeightDemo',
-      component: () => import('/@/views/demo/table/FixedHeight.vue'),
-      meta: {
-        title: t('routes.demo.table.fixedHeight'),
-      },
-    },
-    {
-      path: 'footerTable',
-      name: 'FooterTableDemo',
-      component: () => import('/@/views/demo/table/FooterTable.vue'),
-      meta: {
-        title: t('routes.demo.table.footerTable'),
-      },
-    },
-    {
-      path: 'editCellTable',
-      name: 'EditCellTableDemo',
-      component: () => import('/@/views/demo/table/EditCellTable.vue'),
-      meta: {
-        title: t('routes.demo.table.editCellTable'),
-      },
-    },
-    {
-      path: 'editRowTable',
-      name: 'EditRowTableDemo',
-      component: () => import('/@/views/demo/table/EditRowTable.vue'),
-      meta: {
-        title: t('routes.demo.table.editRowTable'),
-      },
-    },
-  ],
-};
-
-export default table;

+ 0 - 43
src/router/routes/modules/demo/tree.ts

@@ -1,43 +0,0 @@
-import type { AppRouteModule } from '/@/router/types';
-
-import { LAYOUT } from '/@/router/constant';
-import { t } from '/@/hooks/web/useI18n';
-
-const tree: AppRouteModule = {
-  path: '/tree',
-  name: 'TreeDemo',
-  component: LAYOUT,
-  redirect: '/tree/basic',
-  meta: {
-    icon: 'clarity:tree-view-line',
-    title: t('routes.demo.tree.tree'),
-  },
-  children: [
-    {
-      path: 'basic',
-      name: 'BasicTreeDemo',
-      component: () => import('/@/views/demo/tree/index.vue'),
-      meta: {
-        title: t('routes.demo.tree.basic'),
-      },
-    },
-    {
-      path: 'editTree',
-      name: 'EditTreeDemo',
-      component: () => import('/@/views/demo/tree/EditTree.vue'),
-      meta: {
-        title: t('routes.demo.tree.editTree'),
-      },
-    },
-    {
-      path: 'actionTree',
-      name: 'ActionTreeDemo',
-      component: () => import('/@/views/demo/tree/ActionTree.vue'),
-      meta: {
-        title: t('routes.demo.tree.actionTree'),
-      },
-    },
-  ],
-};
-
-export default tree;

+ 2 - 0
src/settings/colorSetting.ts

@@ -11,6 +11,7 @@ export const HEADER_PRESET_BG_COLOR_LIST: string[] = [
   '#24292e',
   '#394664',
   '#001529',
+  '#383f45',
 ];
 
 // sider preset color
@@ -24,4 +25,5 @@ export const SIDE_BAR_BG_COLOR_LIST: string[] = [
   '#001628',
   '#28333E',
   '#344058',
+  '#383f45',
 ];

+ 3 - 3
src/settings/projectSetting.ts

@@ -81,11 +81,9 @@ const setting: ProjectConfig = {
     fixed: true,
     // Menu collapse
     collapsed: false,
-    // Whether to display the menu name when folding the menu
-    collapsedShowTitle: false,
     // Whether it can be dragged
     // Only limited to the opening of the left menu, the mouse has a drag bar on the right side of the menu
-    canDrag: false,
+    canDrag: true,
     // Whether to show no dom
     show: true,
     // Whether to show dom
@@ -106,6 +104,8 @@ const setting: ProjectConfig = {
     trigger: TriggerEnum.HEADER,
     // Turn on accordion mode, only show a menu
     accordion: true,
+    // Switch page to close menu
+    closeMixSidebarOnChange: false,
   },
 
   // Multi-label

+ 3 - 2
src/store/modules/tab.ts

@@ -88,8 +88,9 @@ class Tab extends VuexModule {
       if (item.meta?.affix) {
         const name = item.name as string;
         pageCacheSet.add(name);
-      } else if (item.matched && needCache) {
-        const matched = item.matched;
+      } else if (item?.matched && needCache) {
+        const matched = item?.matched;
+        if (!matched) return;
         const len = matched.length;
 
         if (len < 2) return;

+ 2 - 1
src/types/config.d.ts

@@ -7,7 +7,6 @@ export interface MenuSetting {
   bgColor: string;
   fixed: boolean;
   collapsed: boolean;
-  collapsedShowTitle: boolean;
   canDrag: boolean;
   show: boolean;
   hidden: boolean;
@@ -19,6 +18,7 @@ export interface MenuSetting {
   topMenuAlign: 'start' | 'center' | 'end';
   trigger: TriggerEnum;
   accordion: boolean;
+  closeMixSidebarOnChange: boolean;
 }
 
 export interface MultiTabsSetting {
@@ -109,6 +109,7 @@ export interface ProjectConfig {
   // pageLayout是否开启keep-alive
   openKeepAlive: boolean;
 
+  //
   // 锁屏时间
   lockTime: number;
   // 显示面包屑

+ 2 - 0
src/utils/factory/createAsyncComponent.tsx

@@ -34,7 +34,9 @@ export function createAsyncComponent(loader: Fn, options: Options = {}) {
     loadingComponent: loading ? <Spin spinning={true} size={size} /> : undefined,
     // The error component will be displayed if a timeout is
     // provided and exceeded. Default: Infinity.
+    // TODO
     timeout,
+    // errorComponent
     // Defining if component is suspensible. Default: true.
     // suspensible: false,
     delay,

+ 155 - 162
yarn.lock

@@ -1041,53 +1041,58 @@
   resolved "https://registry.npmjs.org/@iconify/iconify/-/iconify-2.0.0-rc.1.tgz#a8bae29d71016d5af98c69f56a73c4a040217b3a"
   integrity sha512-ji5H04VjYtR4seIEgVVLPxg1KRhrFquOiyfPyLVS6vYPkuqV6bcWdssi05YSmf/OAzG4E7Qsg80/bOKyd5tYTw==
 
-"@iconify/iconify@>=2.0.0-rc.1", "@iconify/iconify@^2.0.0-rc.2":
+"@iconify/iconify@>=2.0.0-rc.1":
   version "2.0.0-rc.2"
   resolved "https://registry.npmjs.org/@iconify/iconify/-/iconify-2.0.0-rc.2.tgz#c4a95ddc06ca9b9496df03604e66fdefb39f4c4b"
   integrity sha512-BybEHU5/I9EQ0CcwKAqmreZ2bMnAXrqLCTptAc6vPetHMbrXdZfejP5mt57e/8PNSt/qE7BHniU5PCYA+PGIHw==
 
-"@iconify/json@^1.1.272":
-  version "1.1.272"
-  resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.272.tgz#27c7caee9764e0304161261ec08ffc2794944b66"
-  integrity sha512-FyiTc7UiXJ5cDfk09lv70sYOSi5uLyK+a0LnF1KgWmofkikL06p98ksNRN7stmHryOYarSy75xgi6MbgAwtltQ==
-
-"@intlify/core-base@9.0.0-beta.13":
-  version "9.0.0-beta.13"
-  resolved "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.0.0-beta.13.tgz#fb6bc278209cb7bef44853a42160fedb0560c3f8"
-  integrity sha512-ukImWV+QvRmNZtCTLrSW391z46eMuBheCMPZh801nM3v0Dosfu2PtWO5/z8Q9Bsom4Q+PNQ5eBtOQj2yCAhVEA==
-  dependencies:
-    "@intlify/message-compiler" "9.0.0-beta.13"
-    "@intlify/message-resolver" "9.0.0-beta.13"
-    "@intlify/runtime" "9.0.0-beta.13"
-    "@intlify/shared" "9.0.0-beta.13"
-
-"@intlify/message-compiler@9.0.0-beta.13":
-  version "9.0.0-beta.13"
-  resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.0.0-beta.13.tgz#3b8ddcb2be3f80b28c6e4f6028c0b3ec4e709849"
-  integrity sha512-1z7716InFM8FdTAz64wqZvFuT4wL7WKF63v+vUEW4s9FLoL0U+xIccor9P5XHAvvG1gPMH/Zxd0deg/ULZ1Mcg==
-  dependencies:
-    "@intlify/message-resolver" "9.0.0-beta.13"
-    "@intlify/shared" "9.0.0-beta.13"
+"@iconify/iconify@^2.0.0-rc.4":
+  version "2.0.0-rc.4"
+  resolved "https://registry.npmjs.org/@iconify/iconify/-/iconify-2.0.0-rc.4.tgz#46098fb544a4eb3af724219e4955c9022801835e"
+  integrity sha512-YCSECbeXKFJEIVkKgKMjUzJ439ysufmL/a31B1j7dCvnHaBWsX9J4XehhJgg/aTy3yvhHaVhI6xt1kSMZP799A==
+
+"@iconify/json@^1.1.275":
+  version "1.1.275"
+  resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.275.tgz#ac9a706cdc7c9e64ab8e8bb09ae770f551f7496f"
+  integrity sha512-Nt6tXJpZFd/gFRV24BvmlIdxnbMxgshIKFPQwOWgeVjKiOKEwiBKjXUzBE74As7/Olps/ac1gEB40N9/DGOJ3Q==
+
+"@intlify/core-base@9.0.0-beta.14":
+  version "9.0.0-beta.14"
+  resolved "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.0.0-beta.14.tgz#b190d1dd95d28977b26353c77121d2d84d15b7ee"
+  integrity sha512-ZX+JJvBtcdVZxrTg8oO6flXC965aURIvAeDOYT3DqlUWekHKQ2hUVc1J8SP7rzEgFUqDqCMrDMtv2gZNvxD2Aw==
+  dependencies:
+    "@intlify/message-compiler" "9.0.0-beta.14"
+    "@intlify/message-resolver" "9.0.0-beta.14"
+    "@intlify/runtime" "9.0.0-beta.14"
+    "@intlify/shared" "9.0.0-beta.14"
+
+"@intlify/message-compiler@9.0.0-beta.14":
+  version "9.0.0-beta.14"
+  resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.0.0-beta.14.tgz#4b5a4467459c402e71652075e9d95e5d85e85588"
+  integrity sha512-fXgiQuLKsYINnzhnCQD3OJnT2/59HrPw8WWiG8MpuMSDcUkUXV4ie0DW+9x48QQqpel7TU11Lage3zuFxw27iQ==
+  dependencies:
+    "@intlify/message-resolver" "9.0.0-beta.14"
+    "@intlify/shared" "9.0.0-beta.14"
     source-map "0.6.1"
 
-"@intlify/message-resolver@9.0.0-beta.13":
-  version "9.0.0-beta.13"
-  resolved "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.0.0-beta.13.tgz#ae6de0bf0e54093160442d465e719bf03fd0f146"
-  integrity sha512-mR1eSpRtB4jh11TpQTUyzjEwqZ6D30mJYREEfSrl5YKfUKwDQrulrOaIO8T5gVQG2m09vfxJHVrgfJ2hR8z/0Q==
+"@intlify/message-resolver@9.0.0-beta.14":
+  version "9.0.0-beta.14"
+  resolved "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.0.0-beta.14.tgz#f964706650d71ef06669c17c29cb60500ef2617a"
+  integrity sha512-/PPLMHX0w/ECkG+Fmne8L3WVVVwAp3tpdisf5G775b49Fspy4dKXqkLXM2NZKhdguJbXWHXXXiRkr+mlhq8G9Q==
 
-"@intlify/runtime@9.0.0-beta.13":
-  version "9.0.0-beta.13"
-  resolved "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.0.0-beta.13.tgz#8deff103ee6982c6d531314e9f965b90768d8a27"
-  integrity sha512-hcb3sg75SokuzNDG8IC6PJmwjsS/xdgevd99UNG1zKb7s5qFFb90ApvPDpiH0+R9TMQe11fZqg5dyrVBKqAV4A==
+"@intlify/runtime@9.0.0-beta.14":
+  version "9.0.0-beta.14"
+  resolved "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.0.0-beta.14.tgz#367f6b09c991c71905b73224e238aa8382976b2d"
+  integrity sha512-WgFnXkniJIMrZfW1kcxejQ96te56r3us632/WME7SL1IYsGV+WErXiJMBrHf8ngwAy15WmfYoKWr32sRxWjCtw==
   dependencies:
-    "@intlify/message-compiler" "9.0.0-beta.13"
-    "@intlify/message-resolver" "9.0.0-beta.13"
-    "@intlify/shared" "9.0.0-beta.13"
+    "@intlify/message-compiler" "9.0.0-beta.14"
+    "@intlify/message-resolver" "9.0.0-beta.14"
+    "@intlify/shared" "9.0.0-beta.14"
 
-"@intlify/shared@9.0.0-beta.13":
-  version "9.0.0-beta.13"
-  resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.0.0-beta.13.tgz#2d93d695f19fd699ea8b336066f9d6dfc185f094"
-  integrity sha512-/rqC3YEGHs3uu3XSsF1zdBKJb+on34Yn8Z58K3YxJsFxKPHa8mH73EUtN79hTZWh6Js4zEa/WsCgZCM62b8eJA==
+"@intlify/shared@9.0.0-beta.14":
+  version "9.0.0-beta.14"
+  resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.0.0-beta.14.tgz#c221a45f666a40935f998d2e3c14451a516b9f56"
+  integrity sha512-f+Gev5GnrNLyieJCB6sB/qqe03XaMf6yJiAXG3IamZ4mp45oj2Lw9Oj3hAepDW36VUZK2wCIDmWy53pxJNIFpQ==
 
 "@koa/cors@^3.1.0":
   version "3.1.0"
@@ -1558,10 +1563,10 @@
   resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
   integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
 
-"@types/yargs@^15.0.11":
-  version "15.0.11"
-  resolved "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.11.tgz#361d7579ecdac1527687bcebf9946621c12ab78c"
-  integrity sha512-jfcNBxHFYJ4nPIacsi3woz1+kvUO6s1CyeEhtnDHBjHUMNj5UlW2GynmnSgiJJEdNg9yW5C8lfoNRZrHGv5EqA==
+"@types/yargs@^15.0.12":
+  version "15.0.12"
+  resolved "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz#6234ce3e3e3fa32c5db301a170f96a599c960d74"
+  integrity sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==
   dependencies:
     "@types/yargs-parser" "*"
 
@@ -1575,61 +1580,61 @@
   resolved "https://registry.npmjs.org/@types/zxcvbn/-/zxcvbn-4.4.0.tgz#fbc1d941cc6d9d37d18405c513ba6b294f89b609"
   integrity sha512-GQLOT+SN20a+AI51y3fAimhyTF4Y0RG+YP3gf91OibIZ7CJmPFgoZi+ZR5a+vRbS01LbQosITWum4ATmJ1Z6Pg==
 
-"@typescript-eslint/eslint-plugin@^4.10.0":
-  version "4.10.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.10.0.tgz#19ed3baf4bc4232c5a7fcd32eaca75c3a5baf9f3"
-  integrity sha512-h6/V46o6aXpKRlarP1AiJEXuCJ7cMQdlpfMDrcllIgX3dFkLwEBTXAoNP98ZoOmqd1xvymMVRAI4e7yVvlzWEg==
+"@typescript-eslint/eslint-plugin@^4.11.0":
+  version "4.11.0"
+  resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.11.0.tgz#bc6c1e4175c0cf42083da4314f7931ad12f731cc"
+  integrity sha512-x4arJMXBxyD6aBXLm3W7mSDZRiABzy+2PCLJbL7OPqlp53VXhaA1HKK7R2rTee5OlRhnUgnp8lZyVIqjnyPT6g==
   dependencies:
-    "@typescript-eslint/experimental-utils" "4.10.0"
-    "@typescript-eslint/scope-manager" "4.10.0"
+    "@typescript-eslint/experimental-utils" "4.11.0"
+    "@typescript-eslint/scope-manager" "4.11.0"
     debug "^4.1.1"
     functional-red-black-tree "^1.0.1"
     regexpp "^3.0.0"
     semver "^7.3.2"
     tsutils "^3.17.1"
 
-"@typescript-eslint/experimental-utils@4.10.0":
-  version "4.10.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.10.0.tgz#dbf5d0f89802d5feaf7d11e5b32df29bbc2f3a0e"
-  integrity sha512-opX+7ai1sdWBOIoBgpVJrH5e89ra1KoLrJTz0UtWAa4IekkKmqDosk5r6xqRaNJfCXEfteW4HXQAwMdx+jjEmw==
+"@typescript-eslint/experimental-utils@4.11.0":
+  version "4.11.0"
+  resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.11.0.tgz#d1a47cc6cfe1c080ce4ead79267574b9881a1565"
+  integrity sha512-1VC6mSbYwl1FguKt8OgPs8xxaJgtqFpjY/UzUYDBKq4pfQ5lBvN2WVeqYkzf7evW42axUHYl2jm9tNyFsb8oLg==
   dependencies:
     "@types/json-schema" "^7.0.3"
-    "@typescript-eslint/scope-manager" "4.10.0"
-    "@typescript-eslint/types" "4.10.0"
-    "@typescript-eslint/typescript-estree" "4.10.0"
+    "@typescript-eslint/scope-manager" "4.11.0"
+    "@typescript-eslint/types" "4.11.0"
+    "@typescript-eslint/typescript-estree" "4.11.0"
     eslint-scope "^5.0.0"
     eslint-utils "^2.0.0"
 
-"@typescript-eslint/parser@^4.10.0":
-  version "4.10.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.10.0.tgz#1a622b0847b765b2d8f0ede6f0cdd85f03d76031"
-  integrity sha512-amBvUUGBMadzCW6c/qaZmfr3t9PyevcSWw7hY2FuevdZVp5QPw/K76VSQ5Sw3BxlgYCHZcK6DjIhSZK0PQNsQg==
+"@typescript-eslint/parser@^4.11.0":
+  version "4.11.0"
+  resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.11.0.tgz#1dd3d7e42708c10ce9f3aa64c63c0ab99868b4e2"
+  integrity sha512-NBTtKCC7ZtuxEV5CrHUO4Pg2s784pvavc3cnz6V+oJvVbK4tH9135f/RBP6eUA2KHiFKAollSrgSctQGmHbqJQ==
   dependencies:
-    "@typescript-eslint/scope-manager" "4.10.0"
-    "@typescript-eslint/types" "4.10.0"
-    "@typescript-eslint/typescript-estree" "4.10.0"
+    "@typescript-eslint/scope-manager" "4.11.0"
+    "@typescript-eslint/types" "4.11.0"
+    "@typescript-eslint/typescript-estree" "4.11.0"
     debug "^4.1.1"
 
-"@typescript-eslint/scope-manager@4.10.0":
-  version "4.10.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.10.0.tgz#dbd7e1fc63d7363e3aaff742a6f2b8afdbac9d27"
-  integrity sha512-WAPVw35P+fcnOa8DEic0tQUhoJJsgt+g6DEcz257G7vHFMwmag58EfowdVbiNcdfcV27EFR0tUBVXkDoIvfisQ==
+"@typescript-eslint/scope-manager@4.11.0":
+  version "4.11.0"
+  resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.11.0.tgz#2d906537db8a3a946721699e4fc0833810490254"
+  integrity sha512-6VSTm/4vC2dHM3ySDW9Kl48en+yLNfVV6LECU8jodBHQOhO8adAVizaZ1fV0QGZnLQjQ/y0aBj5/KXPp2hBTjA==
   dependencies:
-    "@typescript-eslint/types" "4.10.0"
-    "@typescript-eslint/visitor-keys" "4.10.0"
+    "@typescript-eslint/types" "4.11.0"
+    "@typescript-eslint/visitor-keys" "4.11.0"
 
-"@typescript-eslint/types@4.10.0":
-  version "4.10.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.10.0.tgz#12f983750ebad867f0c806e705c1953cd6415789"
-  integrity sha512-+dt5w1+Lqyd7wIPMa4XhJxUuE8+YF+vxQ6zxHyhLGHJjHiunPf0wSV8LtQwkpmAsRi1lEOoOIR30FG5S2HS33g==
+"@typescript-eslint/types@4.11.0":
+  version "4.11.0"
+  resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.11.0.tgz#86cf95e7eac4ccfd183f9fcf1480cece7caf4ca4"
+  integrity sha512-XXOdt/NPX++txOQHM1kUMgJUS43KSlXGdR/aDyEwuAEETwuPt02Nc7v+s57PzuSqMbNLclblQdv3YcWOdXhQ7g==
 
-"@typescript-eslint/typescript-estree@4.10.0":
-  version "4.10.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.10.0.tgz#1e62e45fd57866afd42daf5e9fb6bd4e8dbcfa75"
-  integrity sha512-mGK0YRp9TOk6ZqZ98F++bW6X5kMTzCRROJkGXH62d2azhghmq+1LNLylkGe6uGUOQzD452NOAEth5VAF6PDo5g==
+"@typescript-eslint/typescript-estree@4.11.0":
+  version "4.11.0"
+  resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.11.0.tgz#1144d145841e5987d61c4c845442a24b24165a4b"
+  integrity sha512-eA6sT5dE5RHAFhtcC+b5WDlUIGwnO9b0yrfGa1mIOIAjqwSQCpXbLiFmKTdRbQN/xH2EZkGqqLDrKUuYOZ0+Hg==
   dependencies:
-    "@typescript-eslint/types" "4.10.0"
-    "@typescript-eslint/visitor-keys" "4.10.0"
+    "@typescript-eslint/types" "4.11.0"
+    "@typescript-eslint/visitor-keys" "4.11.0"
     debug "^4.1.1"
     globby "^11.0.1"
     is-glob "^4.0.1"
@@ -1637,12 +1642,12 @@
     semver "^7.3.2"
     tsutils "^3.17.1"
 
-"@typescript-eslint/visitor-keys@4.10.0":
-  version "4.10.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.10.0.tgz#9478822329a9bc8ebcc80623d7f79a01da5ee451"
-  integrity sha512-hPyz5qmDMuZWFtHZkjcCpkAKHX8vdu1G3YsCLEd25ryZgnJfj6FQuJ5/O7R+dB1ueszilJmAFMtlU4CA6se3Jg==
+"@typescript-eslint/visitor-keys@4.11.0":
+  version "4.11.0"
+  resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.11.0.tgz#906669a50f06aa744378bb84c7d5c4fdbc5b7d51"
+  integrity sha512-tRYKyY0i7cMk6v4UIOCjl1LhuepC/pc6adQqJk4Is3YcC6k46HvsV9Wl7vQoLbm9qADgeujiT7KdLrylvFIQ+A==
   dependencies:
-    "@typescript-eslint/types" "4.10.0"
+    "@typescript-eslint/types" "4.11.0"
     eslint-visitor-keys "^2.0.0"
 
 "@vue/compiler-core@*", "@vue/compiler-core@3.0.4", "@vue/compiler-core@^3.0.0-rc.5":
@@ -1800,18 +1805,18 @@
     vscode-languageserver-textdocument "^1.0.1"
     vscode-uri "^2.1.2"
 
-"@vueuse/core@^4.0.0-rc.9":
-  version "4.0.0-rc.9"
-  resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0-rc.9.tgz#5e5f6029704ddc8f851286389ebf7faadf7073d5"
-  integrity sha512-h/6qJ523kT9qASaX7ZgJKdrnubpuTmzFg+RIi1XL/Z/Uj9OpCCru1ORc8VPmFy6TfOcE4j/8Q9ZDmXJV3IfcIQ==
+"@vueuse/core@^4.0.0":
+  version "4.0.0"
+  resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0.tgz#5bea3eaa848e3b3e00427f5053fb98e7e4834b0f"
+  integrity sha512-BBkqriC2j9SH/LuHCggS2MP7VSwBfGkTB9qQh1lzadodk2TnM1JHwM76f3G0hCGqqhEF7ab8Xs+1M1PlvuEQYA==
   dependencies:
-    "@vueuse/shared" "4.0.0-rc.9"
+    "@vueuse/shared" "4.0.0"
     vue-demi latest
 
-"@vueuse/shared@4.0.0-rc.9":
-  version "4.0.0-rc.9"
-  resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0-rc.9.tgz#a54ddff35ad6f2d595e21163ddaa064ace9d7220"
-  integrity sha512-JeEbKhE5TxMJZzfQt7GWyjY/RALr+orQJsoS0bKgtH0OG0L+iId3AOOuMkeBXsKWg6Q7wrtAYGLeM12SViD/NQ==
+"@vueuse/shared@4.0.0":
+  version "4.0.0"
+  resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0.tgz#d495b8fd2f28a453ef0fccae175ca848a4a84bb0"
+  integrity sha512-8tn1BpnaMJU2LqFyFzzN6Dvmc1uDsSlb3Neli5bwwb9f+rcASpuOS3nAWAY6/rIODZP1iwXDNCL4rNFR3YxYtQ==
   dependencies:
     vue-demi latest
 
@@ -1869,7 +1874,7 @@ aggregate-error@^3.0.0:
     clean-stack "^2.0.0"
     indent-string "^4.0.0"
 
-ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4:
+ajv@^6.10.0, ajv@^6.12.4:
   version "6.12.6"
   resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
   integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -1972,10 +1977,10 @@ anymatch@~3.1.1:
     normalize-path "^3.0.0"
     picomatch "^2.0.4"
 
-apexcharts@^3.22.3:
-  version "3.22.3"
-  resolved "https://registry.npmjs.org/apexcharts/-/apexcharts-3.22.3.tgz#a829c4500db8478069b80e227741830b77d89467"
-  integrity sha512-ZRZWmAmSdyc+tFhHMZ10ZxbvSbomWe46izpi8yQj5cKLxuujw2XeXVQ0jxnPl9yE5Q7W2hAbDWStaouBN4mSuw==
+apexcharts@^3.23.0:
+  version "3.23.0"
+  resolved "https://registry.npmjs.org/apexcharts/-/apexcharts-3.23.0.tgz#12877aa789d658aef5eb930af6c3b8850fefd925"
+  integrity sha512-1mV6qouuopvYR6UFSXi/Ge4jRMe//zyAN3aK05mAs4Iuet8mA0w31Q6OU6syD77bawt9p3YKNOmNF7OO2u9w0g==
   dependencies:
     svg.draggable.js "^2.2.2"
     svg.easing.js "^2.0.0"
@@ -2046,11 +2051,6 @@ assign-symbols@^1.0.0:
   resolved "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
   integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
 
-astral-regex@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
-  integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
-
 astral-regex@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
@@ -2918,6 +2918,13 @@ cookies@~0.8.0:
     depd "~2.0.0"
     keygrip "~1.1.0"
 
+copy-anything@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.1.tgz#2afbce6da684bdfcbec93752fa762819cb480d9a"
+  integrity sha512-lA57e7viQHOdPQcrytv5jFeudZZOXuyk47lZym279FiDQ8jeZomXiGuVf6ffMKkJ+3TIai3J1J3yi6M+/4U35g==
+  dependencies:
+    is-what "^3.7.1"
+
 copy-descriptor@^0.1.0:
   version "0.1.1"
   resolved "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
@@ -3432,10 +3439,10 @@ escape-string-regexp@^1.0.5:
   resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
 
-eslint-config-prettier@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.0.0.tgz#c1ae4106f74e6c0357f44adb076771d032ac0e97"
-  integrity sha512-8Y8lGLVPPZdaNA7JXqnvETVC7IiVRgAP6afQu9gOQRn90YY3otMNh+x7Vr2vMePQntF+5erdSUBqSzCmU/AxaQ==
+eslint-config-prettier@^7.1.0:
+  version "7.1.0"
+  resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz#5402eb559aa94b894effd6bddfa0b1ca051c858f"
+  integrity sha512-9sm5/PxaFG7qNJvJzTROMM1Bk1ozXVTKI0buKOyb0Bsr1hrwi0H/TzxF/COtf1uxikIK8SwhX7K6zg78jAzbeA==
 
 eslint-plugin-prettier@^3.3.0:
   version "3.3.0"
@@ -3444,15 +3451,15 @@ eslint-plugin-prettier@^3.3.0:
   dependencies:
     prettier-linter-helpers "^1.0.0"
 
-eslint-plugin-vue@^7.2.0:
-  version "7.2.0"
-  resolved "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.2.0.tgz#dd8323fe7ca28fe9377ce3f5f6cf17afe2686f2a"
-  integrity sha512-4mt0yIv6rBDNtvis/g22a0ozJ12GfcdEzX77u0ICYjKlxOVtGrKGEvo0cbOObHaKDg9a9kJcoaNodqE4TPfS2A==
+eslint-plugin-vue@^7.3.0:
+  version "7.3.0"
+  resolved "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.3.0.tgz#0faf0fcf0e1b1052bf800d4dee42d64f50679cb0"
+  integrity sha512-4rc9xrZgwT4aLz3XE6lrHu+FZtDLWennYvtzVvvS81kW9c65U4DUzQQWAFjDCgCFvN6HYWxi7ueEtxZVSB+f0g==
   dependencies:
     eslint-utils "^2.1.0"
     natural-compare "^1.4.0"
     semver "^7.3.2"
-    vue-eslint-parser "^7.2.0"
+    vue-eslint-parser "^7.3.0"
 
 eslint-scope@^5.0.0, eslint-scope@^5.1.1:
   version "5.1.1"
@@ -3479,10 +3486,10 @@ eslint-visitor-keys@^2.0.0:
   resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
   integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
 
-eslint@^7.15.0:
-  version "7.15.0"
-  resolved "https://registry.npmjs.org/eslint/-/eslint-7.15.0.tgz#eb155fb8ed0865fcf5d903f76be2e5b6cd7e0bc7"
-  integrity sha512-Vr64xFDT8w30wFll643e7cGrIkPEU50yIiI36OdSIDoSGguIeaLzBo0vpGvzo9RECUqq7htURfwEtKqwytkqzA==
+eslint@^7.16.0:
+  version "7.16.0"
+  resolved "https://registry.npmjs.org/eslint/-/eslint-7.16.0.tgz#a761605bf9a7b32d24bb7cde59aeb0fd76f06092"
+  integrity sha512-iVWPS785RuDA4dWuhhgXTNrGxHHK3a8HLSMBgbbU59ruJDubUraXN8N5rn7kb8tG6sjg74eE0RA3YWT51eusEw==
   dependencies:
     "@babel/code-frame" "^7.0.0"
     "@eslint/eslintrc" "^0.2.2"
@@ -3518,7 +3525,7 @@ eslint@^7.15.0:
     semver "^7.2.1"
     strip-ansi "^6.0.0"
     strip-json-comments "^3.1.0"
-    table "^5.2.3"
+    table "^6.0.4"
     text-table "^0.2.0"
     v8-compile-cache "^2.0.3"
 
@@ -4788,6 +4795,11 @@ is-utf8@^0.2.0, is-utf8@^0.2.1:
   resolved "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
   integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
 
+is-what@^3.7.1:
+  version "3.12.0"
+  resolved "https://registry.npmjs.org/is-what/-/is-what-3.12.0.tgz#f4405ce4bd6dd420d3ced51a026fb90e03705e55"
+  integrity sha512-2ilQz5/f/o9V7WRWJQmpFYNmQFZ9iM+OXRonZKcYgTkCzjb949Vi4h282PD1UfmgHk666rcWonbRJ++KI41VGw==
+
 is-windows@^1.0.1, is-windows@^1.0.2:
   version "1.0.2"
   resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
@@ -5091,11 +5103,13 @@ koa@^2.13.0:
     type-is "^1.6.16"
     vary "^1.1.2"
 
-less@^3.13.0:
-  version "3.13.0"
-  resolved "https://registry.npmjs.org/less/-/less-3.13.0.tgz#6a47bb19d97edcf7a53d444b099275dd6b17c85a"
-  integrity sha512-uPhr9uoSGVKKYVGz0rXcYBK1zjwcIWRGcbnSgNt66XuIZYrYPaQiS+LeUOvqedBwrwdBYYaLqSff5ytGYuT7rA==
+less@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.npmjs.org/less/-/less-4.0.0.tgz#d238cc25576c1f722794dbca4ac82e5e3c7e9e65"
+  integrity sha512-av1eEa2D0xZfF7fjLJS/Dld7zAYSLU7EOEJvuOELeaNI3i6L/81AdjbK5/pytaRkBwi7ZEa0433IDvMLskKCOw==
   dependencies:
+    copy-anything "^2.0.1"
+    parse-node-version "^1.0.1"
     tslib "^1.10.0"
   optionalDependencies:
     errno "^0.1.1"
@@ -5219,6 +5233,11 @@ lodash-es@^4.17.15:
   resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
   integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
 
+lodash-es@^4.17.20:
+  version "4.17.20"
+  resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.20.tgz#29f6332eefc60e849f869c264bc71126ad61e8f7"
+  integrity sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA==
+
 lodash._reinterpolate@^3.0.0:
   version "3.0.0"
   resolved "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@@ -6036,6 +6055,11 @@ parse-json@^5.0.0:
     json-parse-even-better-errors "^2.3.0"
     lines-and-columns "^1.1.6"
 
+parse-node-version@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b"
+  integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==
+
 parse-passwd@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
@@ -7141,15 +7165,6 @@ slash@^3.0.0:
   resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
   integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
 
-slice-ansi@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
-  integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==
-  dependencies:
-    ansi-styles "^3.2.0"
-    astral-regex "^1.0.0"
-    is-fullwidth-code-point "^2.0.0"
-
 slice-ansi@^3.0.0:
   version "3.0.0"
   resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
@@ -7648,17 +7663,7 @@ svg.select.js@^3.0.1:
   dependencies:
     svg.js "^2.6.5"
 
-table@^5.2.3:
-  version "5.4.6"
-  resolved "https://registry.npmjs.org/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
-  integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==
-  dependencies:
-    ajv "^6.10.2"
-    lodash "^4.17.14"
-    slice-ansi "^2.1.0"
-    string-width "^3.0.0"
-
-table@^6.0.3:
+table@^6.0.3, table@^6.0.4:
   version "6.0.4"
   resolved "https://registry.npmjs.org/table/-/table-6.0.4.tgz#c523dd182177e926c723eb20e1b341238188aa0d"
   integrity sha512-sBT4xRLdALd+NFBvwOz8bw4b15htyythha+q+DVZqy2RS08PPC8O2sZFgJYEY7bJvbCFKccs+WIZ/cd+xxTWCw==
@@ -8090,10 +8095,10 @@ vary@^1.1.2:
   resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
   integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
 
-vditor@^3.7.2:
-  version "3.7.2"
-  resolved "https://registry.npmjs.org/vditor/-/vditor-3.7.2.tgz#d1308fa34a7457a89384f5667be53f05c6a3eec6"
-  integrity sha512-CtDba2Jr/op+/E7aRKBoOY8rl8SMkkTdQHxuAbZzb7qv044BXadJIhiBLVv84UHhHaTgsDb/SWMklyoinxegAw==
+vditor@^3.7.3:
+  version "3.7.3"
+  resolved "https://registry.npmjs.org/vditor/-/vditor-3.7.3.tgz#6f7bdee7dca758985b29be1533ed952178f0aac4"
+  integrity sha512-2EHwAc9l+HOo6dcScSJDPmVTsVuEqHK2ucZwAHgvctpua3pMz/CAGMHgPoyB5X1Pju7yrLfsESHZh8V6Ndh6rg==
   dependencies:
     diff-match-patch "^1.0.5"
 
@@ -8232,18 +8237,6 @@ vue-demi@latest:
   resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.4.5.tgz#ea422a4468cb6321a746826a368a770607f87791"
   integrity sha512-51xf1B6hV2PfjnzYHO/yUForFCRQ49KS8ngQb5T6l1HDEmfghTFtsxtRa5tbx4eqQsH76ll/0gIxuf1gei0ubw==
 
-vue-eslint-parser@^7.2.0:
-  version "7.2.0"
-  resolved "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.2.0.tgz#1e17ae94ca71e617025e05143c8ac5593aacb6ef"
-  integrity sha512-uVcQqe8sUNzdHGcRHMd2Z/hl6qEaWrAmglTKP92Fnq9TYU9un8xsyFgEdFJaXh/1rd7h8Aic1GaiQow5nVneow==
-  dependencies:
-    debug "^4.1.1"
-    eslint-scope "^5.0.0"
-    eslint-visitor-keys "^1.1.0"
-    espree "^6.2.1"
-    esquery "^1.0.1"
-    lodash "^4.17.15"
-
 vue-eslint-parser@^7.3.0:
   version "7.3.0"
   resolved "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.3.0.tgz#894085839d99d81296fa081d19643733f23d7559"
@@ -8256,13 +8249,13 @@ vue-eslint-parser@^7.3.0:
     esquery "^1.0.1"
     lodash "^4.17.15"
 
-vue-i18n@^9.0.0-beta.13:
-  version "9.0.0-beta.13"
-  resolved "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.0.0-beta.13.tgz#89cf5dd1566025f441132231d15ed621ef70ba96"
-  integrity sha512-ZN6r5ITODu9NYAAbe1IGVUkNeamuleaXTLn5NMn/YZQ+5NSjDjysyVZVLkVOEOIw6bT2tLveyjsWlAZBVtfcPw==
+vue-i18n@9.0.0-beta.14:
+  version "9.0.0-beta.14"
+  resolved "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.0.0-beta.14.tgz#2b2b89bc5371263d499bd17e3c0696bb9582c4f9"
+  integrity sha512-jPFtBXcRLuGc4YrI97JJuvj70BndqOhpQNJNthAIPC5x8eJIno44NkK/P769nqQWkbsnYO1sNwb+6oCO6QhaxA==
   dependencies:
-    "@intlify/core-base" "9.0.0-beta.13"
-    "@intlify/shared" "9.0.0-beta.13"
+    "@intlify/core-base" "9.0.0-beta.14"
+    "@intlify/shared" "9.0.0-beta.14"
     "@vue/devtools-api" "^6.0.0-beta.2"
 
 vue-router@^4.0.1: