Browse Source

refactor: refactor layout

vben 4 years ago
parent
commit
0692b4798c
51 changed files with 1200 additions and 869 deletions
  1. 5 0
      CHANGELOG.zh_CN.md
  2. 5 3
      build/vite/plugin/transform/globby/index.ts
  3. 0 1
      package.json
  4. 19 3
      src/components/Application/src/AppLogo.vue
  5. 1 1
      src/components/Drawer/src/BasicDrawer.tsx
  6. 1 1
      src/components/Menu/src/BasicMenu.tsx
  7. 1 5
      src/components/Modal/src/BasicModal.tsx
  8. 2 0
      src/components/Modal/src/useModal.ts
  9. 0 1
      src/components/Table/src/hooks/useTableScroll.ts
  10. 6 8
      src/design/index.less
  11. 0 11
      src/design/reset.less
  12. 22 0
      src/hooks/core/useContext.ts
  13. 44 5
      src/hooks/setting/useHeaderSetting.ts
  14. 34 20
      src/hooks/setting/useMenuSetting.ts
  15. 5 2
      src/hooks/setting/useMultipleTabSetting.ts
  16. 30 0
      src/hooks/setting/useRootSetting.ts
  17. 0 32
      src/layouts/default/LayoutContent.tsx
  18. 18 14
      src/layouts/default/LayoutTrigger.tsx
  19. 21 0
      src/layouts/default/content/index.less
  20. 26 0
      src/layouts/default/content/index.tsx
  21. 28 0
      src/layouts/default/footer/index.less
  22. 31 0
      src/layouts/default/footer/index.tsx
  23. 63 59
      src/layouts/default/header/LayoutHeader.tsx
  24. 10 0
      src/layouts/default/header/LayoutMultipleHeader.less
  25. 118 0
      src/layouts/default/header/LayoutMultipleHeader.tsx
  26. 28 26
      src/layouts/default/header/UserDropdown.tsx
  27. 10 1
      src/layouts/default/header/index.less
  28. 6 33
      src/layouts/default/index.less
  29. 50 87
      src/layouts/default/index.tsx
  30. 0 0
      src/layouts/default/lock/LockAction.less
  31. 1 1
      src/layouts/default/lock/LockAction.tsx
  32. 17 0
      src/layouts/default/lock/index.tsx
  33. 11 11
      src/layouts/default/menu/index.tsx
  34. 1 9
      src/layouts/default/multitabs/TabContent.tsx
  35. 277 285
      src/layouts/default/setting/SettingDrawer.tsx
  36. 29 2
      src/layouts/default/setting/enum.ts
  37. 65 127
      src/layouts/default/setting/handler.ts
  38. 1 1
      src/layouts/default/setting/index.vue
  39. 0 77
      src/layouts/default/sider/LayoutSideBar.tsx
  40. 9 1
      src/layouts/default/sider/index.less
  41. 150 0
      src/layouts/default/sider/index.tsx
  42. 2 2
      src/layouts/default/sider/useLayoutSider.tsx
  43. 16 0
      src/layouts/default/useLayoutContext.ts
  44. 2 2
      src/layouts/iframe/useFrameKeepAlive.ts
  45. 2 2
      src/layouts/page/index.tsx
  46. 10 8
      src/settings/projectSetting.ts
  47. 2 0
      src/settings/siteSetting.ts
  48. 7 2
      src/setup/App.ts
  49. 6 6
      src/types/config.d.ts
  50. 8 0
      src/types/global.d.ts
  51. 0 20
      yarn.lock

+ 5 - 0
CHANGELOG.zh_CN.md

@@ -1,5 +1,9 @@
 ## Wip
 
+### ✨ Refactor
+
+- 重构整体 layout。更改代码实现方式。代码更精简
+
 ### ✨ Features
 
 - 缓存可以配置是否加密
@@ -7,6 +11,7 @@
 ### 🎫 Chores
 
 - 移除 messageSetting 配置
+- 暂时删除 `@vueuse/core`.等稳定后在集成。目前不太稳定。
 
 ## 2.0.0-rc.11 (2020-11-18)
 

+ 5 - 3
build/vite/plugin/transform/globby/index.ts

@@ -154,15 +154,17 @@ const globTransform = function (config: SharedConfig): Transform {
 
             const groups: Array<string>[] = [];
             const replaceFiles = files.map((f, i) => {
-              const fileNameWithAlias = resolver.fileToRequest(f);
-
-              const file = bareExporter + fileNameWithAlias + bareExporter;
+              const filePath = resolver.fileToRequest(f);
+              const file = bareExporter + filePath + bareExporter;
 
               if (isLocale) {
                 const globrexRes = globrex(globPath, { extended: true, globstar: true });
 
                 // Get segments for files like an en/system ch/modules for:
                 // ['en', 'system'] ['ch', 'modules']
+
+                // TODO The window system and mac system path are inconsistent?
+                const fileNameWithAlias = filePath.replace(/^(\/src\/)/, '/@/');
                 const matchedGroups = globrexRes.regex.exec(fileNameWithAlias);
 
                 if (matchedGroups && matchedGroups.length) {

+ 0 - 1
package.json

@@ -22,7 +22,6 @@
   },
   "dependencies": {
     "@iconify/iconify": "^2.0.0-rc.2",
-    "@vueuse/core": "^4.0.0-rc.3",
     "ant-design-vue": "2.0.0-beta.15",
     "apexcharts": "3.22.0",
     "axios": "^0.21.0",

+ 19 - 3
src/components/Application/src/AppLogo.vue

@@ -1,7 +1,11 @@
 <template>
-  <div class="app-logo anticon" :class="theme" @click="handleGoHome">
+  <div
+    class="app-logo anticon"
+    :class="{ theme, 'collapsed-show-title': getCollapsedShowTitle }"
+    @click="handleGoHome"
+  >
     <img src="/@/assets/images/logo.png" />
-    <div class="app-logo__title ml-2 ellipsis">{{ globSetting.title }}</div>
+    <div class="app-logo__title ml-2 ellipsis" v-show="showTitle">{{ globSetting.title }}</div>
   </div>
 </template>
 <script lang="ts">
@@ -10,6 +14,7 @@
 
   import { useGlobSetting } from '/@/hooks/setting';
   import { useGo } from '/@/hooks/web/usePage';
+  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
 
   import { PageEnum } from '/@/enums/pageEnum';
 
@@ -22,8 +27,13 @@
       theme: {
         type: String as PropType<string>,
       },
+      showTitle: {
+        type: Boolean,
+        default: true,
+      },
     },
     setup() {
+      const { getCollapsedShowTitle } = useMenuSetting();
       const globSetting = useGlobSetting();
       const go = useGo();
 
@@ -34,6 +44,7 @@
       return {
         handleGoHome,
         globSetting,
+        getCollapsedShowTitle,
       };
     },
   });
@@ -44,9 +55,13 @@
   .app-logo {
     display: flex;
     align-items: center;
-    padding-left: 16px;
+    padding-left: 10px;
     cursor: pointer;
 
+    &.collapsed-show-title {
+      padding-left: 20px;
+    }
+
     &.light {
       border-bottom: 1px solid @border-color-base;
     }
@@ -64,6 +79,7 @@
       font-weight: 700;
       opacity: 0;
       transition: all 0.5s;
+
       .respond-to(medium,{
        opacity: 1;
       });

+ 1 - 1
src/components/Drawer/src/BasicDrawer.tsx

@@ -49,7 +49,7 @@ export default defineComponent({
           ? `${opt.wrapClassName} ${prefixCls}__detail`
           : `${prefixCls}__detail`;
         if (!opt.getContainer) {
-          opt.getContainer = `.default-layout__main`;
+          opt.getContainer = '.layout-content';
         }
       }
       return opt;

+ 1 - 1
src/components/Menu/src/BasicMenu.tsx

@@ -80,7 +80,7 @@ export default defineComponent({
         offset += 46;
       }
       return {
-        height: `calc(100% - ${offset - 12}px)`,
+        height: `calc(100% - ${offset}px)`,
         position: 'relative',
         overflowY: 'auto',
       };

+ 1 - 5
src/components/Modal/src/BasicModal.tsx

@@ -219,11 +219,7 @@ export default defineComponent({
     emit('register', modalMethods, uuid);
 
     return () => (
-      <Modal
-        onCancel={handleCancel}
-        getContainer={() => document.querySelector('.default-layout__main')}
-        {...{ ...attrs, ...props, ...unref(getProps) }}
-      >
+      <Modal onCancel={handleCancel} {...{ ...attrs, ...props, ...unref(getProps) }}>
         {{
           footer: () => renderFooter(),
           closeIcon: () => renderClose(),

+ 2 - 0
src/components/Modal/src/useModal.ts

@@ -33,6 +33,7 @@ export function useModal(): UseModalReturnType {
 
     modalRef.value = modalMethod;
   }
+
   const getInstance = () => {
     const instance = unref(modalRef);
     if (!instance) {
@@ -50,6 +51,7 @@ export function useModal(): UseModalReturnType {
       getInstance().setModalProps({
         visible: visible,
       });
+
       if (data) {
         dataTransferRef[unref(uidRef)] = openOnSet
           ? {

+ 0 - 1
src/components/Table/src/hooks/useTableScroll.ts

@@ -43,7 +43,6 @@ export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRe
     const tableEl: Element = table.$el;
     if (!tableEl) return;
     const headEl = tableEl.querySelector('.ant-table-thead ');
-    // const layoutMain: Element | null = document.querySelector('.default-layout__main ');
     if (!headEl) return;
 
     // 表格距离底部高度

+ 6 - 8
src/design/index.less

@@ -35,7 +35,7 @@ html,
 body {
   width: 100%;
   height: 100%;
-  overflow: hidden;
+  overflow-x: hidden;
 
   &.color-weak {
     filter: invert(80%);
@@ -160,9 +160,7 @@ object {
   vertical-align: baseline !important;
 }
 
-#app,
-#app > div,
-.ant-layout {
+#app {
   width: 100%;
   height: 100%;
 }
@@ -170,8 +168,8 @@ object {
 .ant-layout {
   background: #f0f2f5;
 
-  &-content {
-    position: relative;
-    overflow: hidden;
-  }
+  // &-content {
+  //   position: relative;
+  //   overflow: hidden;
+  // }
 }

+ 0 - 11
src/design/reset.less

@@ -101,14 +101,3 @@
     }
   }
 }
-
-.reset-layout() {
-  .ant-layout {
-    background: #f1f1f6 !important;
-
-    &-content {
-      position: relative;
-      overflow: hidden;
-    }
-  }
-}

+ 22 - 0
src/hooks/core/useContext.ts

@@ -0,0 +1,22 @@
+import { InjectionKey, provide, inject, reactive, readonly } from 'vue';
+
+export const createContext = <T>(
+  context: any,
+  contextInjectKey: InjectionKey<T> = Symbol(),
+  _readonly = true
+) => {
+  const state = reactive({
+    ...context,
+  });
+  const provideData = _readonly ? readonly(state) : state;
+  provide(contextInjectKey, provideData);
+};
+
+export const useContext = <T>(
+  contextInjectKey: InjectionKey<T> = Symbol(),
+  defaultValue?: any,
+  _readonly = true
+): T => {
+  const state = inject(contextInjectKey, defaultValue || {});
+  return _readonly ? readonly(state) : state;
+};

+ 44 - 5
src/hooks/setting/useHeaderSetting.ts

@@ -7,20 +7,50 @@ import { appStore } from '/@/store/modules/app';
 import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
 import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
 import { useRootSetting } from '/@/hooks/setting/useRootSetting';
+import { useFullContent } from '/@/hooks/web/useFullContent';
 
 import { MenuModeEnum } from '/@/enums/menuEnum';
 
 export function useHeaderSetting() {
-  const { getShow: getShowMultipleTab } = useMultipleTabSetting();
-  const { getMode, getSplit, getShowHeaderTrigger, getIsSidebarType } = useMenuSetting();
+  const { getFullContent } = useFullContent();
+  const { getShowMultipleTab } = useMultipleTabSetting();
+  const {
+    getMenuMode,
+    getSplit,
+    getShowHeaderTrigger,
+    getIsSidebarType,
+    getIsTopMenu,
+  } = useMenuSetting();
   const { getShowBreadCrumb, getShowLogo } = useRootSetting();
 
+  const getShowMixHeaderRef = computed(() => !unref(getIsSidebarType) && unref(getShowHeader));
+
+  const getShowFullHeaderRef = computed(() => {
+    return (
+      !unref(getFullContent) &&
+      unref(getShowMixHeaderRef) &&
+      unref(getShowHeader) &&
+      !unref(getIsTopMenu)
+    );
+  });
+
+  const getShowInsetHeaderRef = computed(() => {
+    const need = !unref(getFullContent) && unref(getShowHeader);
+    return (need && !unref(getShowMixHeaderRef)) || (need && unref(getIsTopMenu));
+  });
+
   // Get header configuration
   const getHeaderSetting = computed(() => appStore.getProjectConfig.headerSetting);
 
   const getShowDoc = computed(() => unref(getHeaderSetting).showDoc);
 
-  const getTheme = computed(() => unref(getHeaderSetting).theme);
+  const getHeaderTheme = computed(() => unref(getHeaderSetting).theme);
+
+  const getShowHeader = computed(() => unref(getHeaderSetting).show);
+
+  const getFixed = computed(() => unref(getHeaderSetting).fixed);
+
+  const getHeaderBgColor = computed(() => unref(getHeaderSetting).bgColor);
 
   const getShowRedo = computed(() => unref(getHeaderSetting).showRedo && unref(getShowMultipleTab));
 
@@ -30,9 +60,11 @@ export function useHeaderSetting() {
 
   const getShowNotice = computed(() => unref(getHeaderSetting).showNotice);
 
+  const getUnFixedAndFull = computed(() => !unref(getFixed) && !unref(getShowFullHeaderRef));
+
   const getShowBread = computed(() => {
     return (
-      unref(getMode) !== MenuModeEnum.HORIZONTAL && unref(getShowBreadCrumb) && !unref(getSplit)
+      unref(getMenuMode) !== MenuModeEnum.HORIZONTAL && unref(getShowBreadCrumb) && !unref(getSplit)
     );
   });
 
@@ -55,7 +87,7 @@ export function useHeaderSetting() {
     getHeaderSetting,
 
     getShowDoc,
-    getTheme,
+    getHeaderTheme,
     getShowRedo,
     getUseLockPage,
     getShowFullScreen,
@@ -63,5 +95,12 @@ export function useHeaderSetting() {
     getShowBread,
     getShowContent,
     getShowHeaderLogo,
+    getShowHeader,
+    getFixed,
+    getShowMixHeaderRef,
+    getShowFullHeaderRef,
+    getShowInsetHeaderRef,
+    getUnFixedAndFull,
+    getHeaderBgColor,
   };
 }

+ 34 - 20
src/hooks/setting/useMenuSetting.ts

@@ -11,24 +11,28 @@ export function useMenuSetting() {
   // Get menu configuration
   const getMenuSetting = computed(() => appStore.getProjectConfig.menuSetting);
 
-  const getMiniWidth = computed(() => unref(getMenuSetting).menuWidth);
-
   const getCollapsed = computed(() => unref(getMenuSetting).collapsed);
 
-  const getType = computed(() => unref(getMenuSetting).type);
+  const getMenuType = computed(() => unref(getMenuSetting).type);
+
+  const getMenuMode = computed(() => unref(getMenuSetting).mode);
 
-  const getMode = computed(() => unref(getMenuSetting).mode);
+  const getMenuFixed = computed(() => unref(getMenuSetting).fixed);
 
-  const getShow = computed(() => unref(getMenuSetting).show);
+  const getShowMenu = computed(() => unref(getMenuSetting).show);
+
+  const getMenuHidden = computed(() => unref(getMenuSetting).hidden);
 
   const getMenuWidth = computed(() => unref(getMenuSetting).menuWidth);
 
   const getTrigger = computed(() => unref(getMenuSetting).trigger);
 
-  const getTheme = computed(() => unref(getMenuSetting).theme);
+  const getMenuTheme = computed(() => unref(getMenuSetting).theme);
 
   const getSplit = computed(() => unref(getMenuSetting).split);
 
+  const getMenuBgColor = computed(() => unref(getMenuSetting).bgColor);
+
   const getHasDrag = computed(() => unref(getMenuSetting).hasDrag);
 
   const getAccordion = computed(() => unref(getMenuSetting).accordion);
@@ -39,17 +43,19 @@ export function useMenuSetting() {
 
   const getTopMenuAlign = computed(() => unref(getMenuSetting).topMenuAlign);
 
-  const getIsSidebarType = computed(() => unref(getType) === MenuTypeEnum.SIDEBAR);
+  const getIsSidebarType = computed(() => unref(getMenuType) === MenuTypeEnum.SIDEBAR);
+
+  const getIsTopMenu = computed(() => unref(getMenuType) === MenuTypeEnum.TOP_MENU);
 
   const getShowTopMenu = computed(() => {
-    return unref(getMode) === MenuModeEnum.HORIZONTAL || unref(getSplit);
+    return unref(getMenuMode) === MenuModeEnum.HORIZONTAL || unref(getSplit);
   });
 
   const getShowHeaderTrigger = computed(() => {
     if (
-      unref(getType) === MenuTypeEnum.TOP_MENU ||
-      !unref(getShow) ||
-      !unref(getMenuSetting).hidden
+      unref(getMenuType) === MenuTypeEnum.TOP_MENU ||
+      !unref(getShowMenu) ||
+      !unref(getMenuHidden)
     ) {
       return false;
     }
@@ -60,12 +66,16 @@ export function useMenuSetting() {
   const getShowSearch = computed(() => {
     return (
       unref(getMenuSetting).showSearch &&
-      !(unref(getType) === MenuTypeEnum.MIX && unref(getMode) === MenuModeEnum.HORIZONTAL)
+      !(unref(getMenuType) === MenuTypeEnum.MIX && unref(getMenuMode) === MenuModeEnum.HORIZONTAL)
     );
   });
 
   const getIsHorizontal = computed(() => {
-    return unref(getMode) === MenuModeEnum.HORIZONTAL;
+    return unref(getMenuMode) === MenuModeEnum.HORIZONTAL;
+  });
+
+  const getRealWidth = computed(() => {
+    return unref(getCollapsed) ? unref(getMiniWidthNumber) : unref(getMenuWidth);
   });
 
   const getMiniWidthNumber = computed(() => {
@@ -74,8 +84,8 @@ export function useMenuSetting() {
   });
 
   const getCalcContentWidth = computed(() => {
-    const width = unref(getCollapsed) ? unref(getMiniWidthNumber) : unref(getMiniWidth);
-    return `calc(100% - ${width}px)`;
+    const width = unref(getIsTopMenu) || !unref(getShowMenu) ? 0 : unref(getRealWidth);
+    return `calc(100% - ${unref(width)}px)`;
   });
 
   // Set menu configuration
@@ -94,18 +104,19 @@ export function useMenuSetting() {
 
     toggleCollapsed,
 
+    getMenuFixed,
     getMenuSetting,
-    getMiniWidth,
-    getType,
-    getMode,
-    getShow,
+    getRealWidth,
+    getMenuType,
+    getMenuMode,
+    getShowMenu,
     getCollapsed,
     getMiniWidthNumber,
     getCalcContentWidth,
     getMenuWidth,
     getTrigger,
     getSplit,
-    getTheme,
+    getMenuTheme,
     getHasDrag,
     getIsHorizontal,
     getShowSearch,
@@ -116,5 +127,8 @@ export function useMenuSetting() {
     getShowTopMenu,
     getShowHeaderTrigger,
     getTopMenuAlign,
+    getMenuHidden,
+    getIsTopMenu,
+    getMenuBgColor,
   };
 }

+ 5 - 2
src/hooks/setting/useMultipleTabSetting.ts

@@ -9,7 +9,9 @@ export function useMultipleTabSetting() {
 
   const getMax = computed(() => unref(getMultipleTabSetting).max);
 
-  const getShow = computed(() => unref(getMultipleTabSetting).show);
+  const getShowMultipleTab = computed(() => unref(getMultipleTabSetting).show);
+
+  const getShowQuick = computed(() => unref(getMultipleTabSetting).showQuick);
 
   function setMultipleTabSetting(multiTabsSetting: Partial<MultiTabsSetting>) {
     appStore.commitProjectConfigState({ multiTabsSetting });
@@ -20,6 +22,7 @@ export function useMultipleTabSetting() {
 
     getMultipleTabSetting,
     getMax,
-    getShow,
+    getShowMultipleTab,
+    getShowQuick,
   };
 }

+ 30 - 0
src/hooks/setting/useRootSetting.ts

@@ -3,6 +3,7 @@ import type { ProjectConfig } from '/@/types/config';
 import { computed, unref } from 'vue';
 
 import { appStore } from '/@/store/modules/app';
+import { ContentEnum } from '/@/enums/appEnum';
 
 type RootSetting = Omit<
   ProjectConfig,
@@ -13,6 +14,8 @@ export function useRootSetting() {
 
   const getOpenPageLoading = computed(() => unref(getRootSetting).openPageLoading);
 
+  const getPageLoading = computed(() => appStore.getPageLoading);
+
   const getOpenRouterTransition = computed(() => unref(getRootSetting).openRouterTransition);
 
   const getOpenKeepAlive = computed(() => unref(getRootSetting).openKeepAlive);
@@ -25,12 +28,30 @@ export function useRootSetting() {
 
   const getShowLogo = computed(() => unref(getRootSetting).showLogo);
 
+  const getContentMode = computed(() => unref(getRootSetting).contentMode);
+
+  const getUseOpenBackTop = computed(() => unref(getRootSetting).useOpenBackTop);
+
+  const getShowSettingButton = computed(() => unref(getRootSetting).showSettingButton);
+
   const getUseErrorHandle = computed(() => unref(getRootSetting).useErrorHandle);
 
+  const getShowFooter = computed(() => unref(getRootSetting).showFooter);
+
   const getShowBreadCrumb = computed(() => unref(getRootSetting).showBreadCrumb);
 
   const getShowBreadCrumbIcon = computed(() => unref(getRootSetting).showBreadCrumbIcon);
 
+  const getFullContent = computed(() => unref(getRootSetting).fullContent);
+
+  const getColorWeak = computed(() => unref(getRootSetting).colorWeak);
+
+  const getGrayMode = computed(() => unref(getRootSetting).grayMode);
+
+  const getLayoutContentMode = computed(() =>
+    unref(getRootSetting).contentMode === ContentEnum.FULL ? ContentEnum.FULL : ContentEnum.FIXED
+  );
+
   function setRootSetting(setting: RootSetting) {
     appStore.commitProjectConfigState(setting);
   }
@@ -38,7 +59,12 @@ export function useRootSetting() {
   return {
     setRootSetting,
 
+    getFullContent,
+    getColorWeak,
+    getGrayMode,
     getRootSetting,
+    getLayoutContentMode,
+    getPageLoading,
     getOpenPageLoading,
     getOpenRouterTransition,
     getOpenKeepAlive,
@@ -49,5 +75,9 @@ export function useRootSetting() {
     getUseErrorHandle,
     getShowBreadCrumb,
     getShowBreadCrumbIcon,
+    getUseOpenBackTop,
+    getShowSettingButton,
+    getShowFooter,
+    getContentMode,
   };
 }

+ 0 - 32
src/layouts/default/LayoutContent.tsx

@@ -1,32 +0,0 @@
-import { computed, defineComponent, unref } from 'vue';
-import { Layout } from 'ant-design-vue';
-import { FullLoading } from '/@/components/Loading/index';
-
-import { RouterView } from 'vue-router';
-
-import { ContentEnum } from '/@/enums/appEnum';
-import { appStore } from '/@/store/modules/app';
-export default defineComponent({
-  name: 'DefaultLayoutContent',
-  setup() {
-    const getProjectConfigRef = computed(() => {
-      return appStore.getProjectConfig;
-    });
-
-    return () => {
-      const { contentMode, openPageLoading } = unref(getProjectConfigRef);
-      const { getPageLoading } = appStore;
-      const wrapClass = contentMode === ContentEnum.FULL ? 'full' : 'fixed';
-      return (
-        <div class={[`default-layout__main`]}>
-          {openPageLoading && (
-            <FullLoading class={[`default-layout__loading`, !getPageLoading && 'hidden']} />
-          )}
-          <Layout.Content class={`layout-content ${wrapClass} `}>
-            {() => <RouterView />}
-          </Layout.Content>
-        </div>
-      );
-    };
-  },
-});

+ 18 - 14
src/layouts/default/LayoutTrigger.tsx

@@ -1,4 +1,4 @@
-import type { PropType } from 'vue';
+import type { PropType, FunctionalComponent } from 'vue';
 
 import { defineComponent, unref } from 'vue';
 import {
@@ -10,6 +10,22 @@ import {
 
 import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
 
+const SiderTrigger: FunctionalComponent = () => {
+  const { getCollapsed } = useMenuSetting();
+  return unref(getCollapsed) ? <DoubleRightOutlined /> : <DoubleLeftOutlined />;
+};
+
+const HeaderTrigger: FunctionalComponent<{
+  theme?: string;
+}> = (props) => {
+  const { toggleCollapsed, getCollapsed } = useMenuSetting();
+  return (
+    <span class={['layout-trigger', props.theme]} onClick={toggleCollapsed}>
+      {unref(getCollapsed) ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
+    </span>
+  );
+};
+
 export default defineComponent({
   name: 'LayoutTrigger',
   props: {
@@ -22,20 +38,8 @@ export default defineComponent({
     },
   },
   setup(props) {
-    const { toggleCollapsed, getCollapsed } = useMenuSetting();
-
     return () => {
-      const siderTrigger = unref(getCollapsed) ? <DoubleRightOutlined /> : <DoubleLeftOutlined />;
-
-      if (props.sider) {
-        return siderTrigger;
-      }
-
-      return (
-        <span class={['layout-trigger', props.theme]} onClick={toggleCollapsed}>
-          {unref(getCollapsed) ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
-        </span>
-      );
+      return props.sider ? <SiderTrigger /> : <HeaderTrigger theme={props.theme} />;
     };
   },
 });

+ 21 - 0
src/layouts/default/content/index.less

@@ -0,0 +1,21 @@
+@import (reference) '../../../design/index.less';
+
+.layout-content {
+  position: relative;
+  flex: 1 1 auto;
+  min-height: 0;
+
+  &.fixed {
+    width: 1200px;
+    margin: 0 auto;
+  }
+
+  &__loading {
+    position: fixed;
+    z-index: @page-loading-z-index;
+
+    > .basic-loading {
+      margin-bottom: 20%;
+    }
+  }
+}

+ 26 - 0
src/layouts/default/content/index.tsx

@@ -0,0 +1,26 @@
+import './index.less';
+
+import { defineComponent, unref } from 'vue';
+import { FullLoading } from '/@/components/Loading/index';
+
+import { RouterView } from 'vue-router';
+
+import { useRootSetting } from '/@/hooks/setting/useRootSetting';
+
+export default defineComponent({
+  name: 'LayoutContent',
+  setup() {
+    const { getOpenPageLoading, getLayoutContentMode, getPageLoading } = useRootSetting();
+
+    return () => {
+      return (
+        <div class={['layout-content', unref(getLayoutContentMode)]}>
+          {unref(getOpenPageLoading) && (
+            <FullLoading class={[`layout-content__loading`, { hidden: !unref(getPageLoading) }]} />
+          )}
+          <RouterView />
+        </div>
+      );
+    };
+  },
+});

+ 28 - 0
src/layouts/default/footer/index.less

@@ -0,0 +1,28 @@
+@normal-color: rgba(0, 0, 0, 0.45);
+
+@hover-color: rgba(0, 0, 0, 0.85);
+
+.layout-footer {
+  color: @normal-color;
+  text-align: center;
+
+  &__links {
+    margin-bottom: 8px;
+
+    a {
+      color: @normal-color;
+
+      &:hover {
+        color: @hover-color;
+      }
+    }
+
+    .github {
+      margin: 0 30px;
+
+      &:hover {
+        color: @hover-color;
+      }
+    }
+  }
+}

+ 31 - 0
src/layouts/default/footer/index.tsx

@@ -0,0 +1,31 @@
+import './index.less';
+
+import { defineComponent } from 'vue';
+import { Layout } from 'ant-design-vue';
+
+import { GithubFilled } from '@ant-design/icons-vue';
+
+import { DOC_URL, GITHUB_URL, SITE_URL } from '/@/settings/siteSetting';
+import { openWindow } from '/@/utils';
+
+export default defineComponent({
+  name: 'LayoutContent',
+  setup() {
+    return () => {
+      return (
+        <Layout.Footer class="layout-footer">
+          {() => (
+            <>
+              <div class="layout-footer__links">
+                <a onClick={() => openWindow(SITE_URL)}>在线预览</a>
+                <GithubFilled onClick={() => openWindow(GITHUB_URL)} class="github" />
+                <a onClick={() => openWindow(DOC_URL)}>在线文档</a>
+              </div>
+              <div>Copyright &copy;2020 Vben Admin</div>
+            </>
+          )}
+        </Layout.Footer>
+      );
+    };
+  },
+});

+ 63 - 59
src/layouts/default/header/LayoutHeader.tsx

@@ -1,13 +1,15 @@
 import './index.less';
 
+import type { FunctionalComponent } from 'vue';
+
 import { defineComponent, unref, computed, ref, nextTick } from 'vue';
 
 import { Layout, Tooltip, Badge } from 'ant-design-vue';
 import { AppLogo } from '/@/components/Application';
 import UserDropdown from './UserDropdown';
-import LayoutMenu from '/@/layouts/default/menu/LayoutMenu';
+import LayoutMenu from '../menu';
 import LayoutBreadcrumb from './LayoutBreadcrumb';
-import LockAction from './LockActionItem';
+import LockAction from '../lock/LockAction';
 import LayoutTrigger from '../LayoutTrigger';
 import NoticeAction from './notice/NoticeActionItem.vue';
 import {
@@ -34,9 +36,30 @@ import { PageEnum } from '/@/enums/pageEnum';
 import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
 import { Component } from '/@/components/types';
 
+interface TooltipItemProps {
+  title: string;
+}
+
+const TooltipItem: FunctionalComponent<TooltipItemProps> = (props, { slots }) => {
+  return (
+    <Tooltip>
+      {{
+        title: () => props.title,
+        default: () => slots.default?.(),
+      }}
+    </Tooltip>
+  );
+};
+
 export default defineComponent({
   name: 'LayoutHeader',
-  setup() {
+  props: {
+    fixed: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  setup(props) {
     let logoEl: Element | null;
 
     const logoWidthRef = ref(200);
@@ -48,7 +71,7 @@ export default defineComponent({
     const { getUseErrorHandle, getShowBreadCrumbIcon } = useRootSetting();
 
     const {
-      getTheme,
+      getHeaderTheme,
       getShowRedo,
       getUseLockPage,
       getShowFullScreen,
@@ -69,8 +92,7 @@ export default defineComponent({
           let width = 0;
           if (!logoEl) {
             logoEl = logoRef.value.$el;
-          }
-          if (logoEl) {
+          } else {
             width += logoEl.clientWidth;
           }
           logoWidthRef.value = width + 80;
@@ -81,7 +103,7 @@ export default defineComponent({
     );
 
     const headerClass = computed(() => {
-      const theme = unref(getTheme);
+      const theme = unref(getHeaderTheme);
       return theme ? `layout-header__header--${theme}` : '';
     });
 
@@ -99,9 +121,6 @@ export default defineComponent({
       });
     }
 
-    /**
-     * @description: 锁定屏幕
-     */
     function handleLockPage() {
       openModal(true);
     }
@@ -111,13 +130,13 @@ export default defineComponent({
       return (
         <div class="layout-header__content ">
           {unref(getShowHeaderLogo) && (
-            <AppLogo class={`layout-header__logo`} ref={logoRef} theme={unref(getTheme)} />
+            <AppLogo class={`layout-header__logo`} ref={logoRef} theme={unref(getHeaderTheme)} />
           )}
 
           {unref(getShowContent) && (
             <div class="layout-header__left">
               {unref(getShowHeaderTrigger) && (
-                <LayoutTrigger theme={unref(getTheme)} sider={false} />
+                <LayoutTrigger theme={unref(getHeaderTheme)} sider={false} />
               )}
               {unref(getShowBread) && <LayoutBreadcrumb showIcon={unref(getShowBreadCrumbIcon)} />}
             </div>
@@ -128,7 +147,7 @@ export default defineComponent({
               <LayoutMenu
                 isHorizontal={true}
                 class={`justify-${unref(getTopMenuAlign)}`}
-                theme={unref(getTheme)}
+                theme={unref(getHeaderTheme)}
                 splitType={unref(getSplitType)}
                 menuMode={unref(getMenuMode)}
                 showSearch={false}
@@ -151,64 +170,47 @@ export default defineComponent({
       return (
         <div class={`layout-header__action`}>
           {unref(getUseErrorHandle) && (
-            <Tooltip>
-              {{
-                title: () => '错误日志',
-                default: () => (
-                  <Badge
-                    count={errorStore.getErrorListCountState}
-                    offset={[0, 10]}
-                    dot
-                    overflowCount={99}
-                  >
-                    {() => renderActionDefault(BugOutlined, handleToErrorList)}
-                  </Badge>
-                ),
-              }}
-            </Tooltip>
+            <TooltipItem title="错误日志">
+              {() => (
+                <Badge
+                  count={errorStore.getErrorListCountState}
+                  offset={[0, 10]}
+                  dot
+                  overflowCount={99}
+                >
+                  {() => renderActionDefault(BugOutlined, handleToErrorList)}
+                </Badge>
+              )}
+            </TooltipItem>
           )}
 
           {unref(getUseLockPage) && (
-            <Tooltip>
-              {{
-                title: () => '锁定屏幕',
-                default: () => renderActionDefault(LockOutlined, handleLockPage),
-              }}
-            </Tooltip>
+            <TooltipItem title="锁定屏幕">
+              {() => renderActionDefault(LockOutlined, handleLockPage)}
+            </TooltipItem>
           )}
 
           {unref(getShowNotice) && (
-            <Tooltip>
-              {{
-                title: () => '消息通知',
-                default: () => <NoticeAction />,
-              }}
-            </Tooltip>
+            <TooltipItem title="消息通知">{() => <NoticeAction />}</TooltipItem>
           )}
 
           {unref(getShowRedo) && (
-            <Tooltip>
-              {{
-                title: () => '刷新',
-                default: () => renderActionDefault(RedoOutlined, refreshPage),
-              }}
-            </Tooltip>
+            <TooltipItem title="刷新">
+              {() => renderActionDefault(RedoOutlined, refreshPage)}
+            </TooltipItem>
           )}
 
           {unref(getShowFullScreen) && (
-            <Tooltip>
-              {{
-                title: () => (unref(isFullscreenRef) ? '退出全屏' : '全屏'),
-                default: () => {
-                  const Icon = !unref(isFullscreenRef) ? (
-                    <FullscreenOutlined />
-                  ) : (
-                    <FullscreenExitOutlined />
-                  );
-                  return renderActionDefault(Icon, toggleFullscreen);
-                },
+            <TooltipItem title={unref(isFullscreenRef) ? '退出全屏' : '全屏'}>
+              {() => {
+                const Icon = !unref(isFullscreenRef) ? (
+                  <FullscreenOutlined />
+                ) : (
+                  <FullscreenExitOutlined />
+                );
+                return renderActionDefault(Icon, toggleFullscreen);
               }}
-            </Tooltip>
+            </TooltipItem>
           )}
           <UserDropdown class={`layout-header__user-dropdown`} />
         </div>
@@ -227,7 +229,9 @@ export default defineComponent({
 
     return () => {
       return (
-        <Layout.Header class={['layout-header', 'flex p-0 px-4 ', unref(headerClass)]}>
+        <Layout.Header
+          class={['layout-header', 'flex p-0 px-4 ', unref(headerClass), { fixed: props.fixed }]}
+        >
           {() => renderHeaderDefault()}
         </Layout.Header>
       );

+ 10 - 0
src/layouts/default/header/LayoutMultipleHeader.less

@@ -0,0 +1,10 @@
+.multiple-tab-header {
+  flex: 0 0 auto;
+
+  &.fixed {
+    position: fixed;
+    top: 0;
+    z-index: 100;
+    width: 100%;
+  }
+}

+ 118 - 0
src/layouts/default/header/LayoutMultipleHeader.tsx

@@ -0,0 +1,118 @@
+import './LayoutMultipleHeader.less';
+
+import { defineComponent, unref, computed, ref, watch, nextTick, CSSProperties } from 'vue';
+
+import LayoutHeader from './LayoutHeader';
+import MultipleTabs from '../multitabs/index';
+
+import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
+import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+import { useFullContent } from '/@/hooks/web/useFullContent';
+import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
+import { useLayoutContext } from '../useLayoutContext';
+
+export default defineComponent({
+  name: 'LayoutMultipleHeader',
+  setup() {
+    const placeholderHeightRef = ref(0);
+    const fullHeaderHeightRef = ref(0);
+    const headerElRef = ref<ComponentRef>(null);
+    const tabElRef = ref<ComponentRef>(null);
+
+    const injectValue = useLayoutContext();
+
+    const { getCalcContentWidth } = useMenuSetting();
+
+    const {
+      getFixed,
+      getShowInsetHeaderRef,
+      getShowFullHeaderRef,
+      getShowHeader,
+      getUnFixedAndFull,
+    } = useHeaderSetting();
+
+    const { getFullContent } = useFullContent();
+
+    const { getShowMultipleTab } = useMultipleTabSetting();
+
+    const showTabsRef = computed(() => {
+      return unref(getShowMultipleTab) && !unref(getFullContent);
+    });
+
+    const getPlaceholderDomStyle = computed(() => {
+      return {
+        height: `${unref(placeholderHeightRef)}px`,
+      };
+    });
+
+    const getIsShowPlaceholderDom = computed(() => {
+      return unref(getFixed) || unref(getShowFullHeaderRef);
+    });
+
+    const getWrapStyle = computed(() => {
+      const style: CSSProperties = {};
+      if (unref(getFixed)) {
+        style.width = unref(getCalcContentWidth);
+      }
+      if (unref(getShowFullHeaderRef)) {
+        style.top = `${unref(fullHeaderHeightRef)}px`;
+      }
+      return style;
+    });
+
+    const getIsFixed = computed(() => {
+      return unref(getFixed) || unref(getShowFullHeaderRef);
+    });
+
+    watch(
+      () => [
+        unref(getFixed),
+        unref(getShowFullHeaderRef),
+        unref(getShowHeader),
+        unref(getShowMultipleTab),
+      ],
+      () => {
+        if (unref(getUnFixedAndFull)) return;
+        nextTick(() => {
+          const headerEl = unref(headerElRef)?.$el;
+          const tabEl = unref(tabElRef)?.$el;
+          const fullHeaderEl = unref(injectValue.fullHeaderRef)?.$el;
+
+          let height = 0;
+          if (headerEl && !unref(getShowFullHeaderRef)) {
+            height += headerEl.offsetHeight;
+          }
+
+          if (tabEl) {
+            height += tabEl.offsetHeight;
+          }
+
+          if (fullHeaderEl && unref(getShowFullHeaderRef)) {
+            const fullHeaderHeight = fullHeaderEl.offsetHeight;
+            height += fullHeaderHeight;
+            fullHeaderHeightRef.value = fullHeaderHeight;
+          }
+          placeholderHeightRef.value = height;
+        });
+      },
+      {
+        immediate: true,
+      }
+    );
+
+    return () => {
+      return (
+        <>
+          {unref(getIsShowPlaceholderDom) && <div style={unref(getPlaceholderDomStyle)} />}
+          <div
+            style={unref(getWrapStyle)}
+            class={['multiple-tab-header', { fixed: unref(getIsFixed) }]}
+          >
+            {unref(getShowInsetHeaderRef) && <LayoutHeader ref={headerElRef} />}
+            {unref(showTabsRef) && <MultipleTabs ref={tabElRef} />}
+          </div>
+        </>
+      );
+    };
+  },
+});

+ 28 - 26
src/layouts/default/header/UserDropdown.tsx

@@ -15,15 +15,31 @@ import { DOC_URL } from '/@/settings/siteSetting';
 import { openWindow } from '/@/utils';
 
 import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
+import { FunctionalComponent } from 'vue';
 
-interface RenderItemParams {
+type MenuEvent = 'loginOut' | 'doc';
+interface MenuItemProps {
   icon: string;
   text: string;
-  key: string;
+  key: MenuEvent;
 }
 
 const prefixCls = 'user-dropdown';
 
+const MenuItem: FunctionalComponent<MenuItemProps> = (props) => {
+  const { key, icon, text } = props;
+  return (
+    <Menu.Item key={key}>
+      {() => (
+        <span class="flex items-center">
+          <Icon icon={icon} class="mr-1" />
+          <span>{text}</span>
+        </span>
+      )}
+    </Menu.Item>
+  );
+};
+
 export default defineComponent({
   name: 'UserDropdown',
   setup() {
@@ -44,27 +60,17 @@ export default defineComponent({
       openWindow(DOC_URL);
     }
 
-    function handleMenuClick(e: any) {
-      if (e.key === 'loginOut') {
-        handleLoginOut();
-      } else if (e.key === 'doc') {
-        openDoc();
+    function handleMenuClick(e: { key: MenuEvent }) {
+      switch (e.key) {
+        case 'loginOut':
+          handleLoginOut();
+          break;
+        case 'doc':
+          openDoc();
+          break;
       }
     }
 
-    function renderItem({ icon, text, key }: RenderItemParams) {
-      return (
-        <Menu.Item key={key}>
-          {() => (
-            <span class="flex items-center">
-              <Icon icon={icon} class="mr-1" />
-              <span>{text}</span>
-            </span>
-          )}
-        </Menu.Item>
-      );
-    }
-
     function renderSlotsDefault() {
       const { realName } = unref(getUserInfo);
       return (
@@ -83,13 +89,9 @@ export default defineComponent({
         <Menu onClick={handleMenuClick}>
           {() => (
             <>
-              {showDoc && renderItem({ key: 'doc', text: '文档', icon: 'gg:loadbar-doc' })}
+              {showDoc && <MenuItem key="doc" text="文档" icon="gg:loadbar-doc" />}
               {showDoc && <Divider />}
-              {renderItem({
-                key: 'loginOut',
-                text: '退出系统',
-                icon: 'ant-design:poweroff-outlined',
-              })}
+              <MenuItem key="loginOut" text="退出系统" icon="ant-design:poweroff-outlined" />
             </>
           )}
         </Menu>

+ 10 - 1
src/layouts/default/header/index.less

@@ -10,13 +10,21 @@
   align-items: center;
   justify-content: space-between;
 
+  &.fixed {
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 1000;
+    width: 100%;
+  }
+
   &__left {
     display: flex;
     // flex-grow: 1;
     align-items: center;
 
     .layout-trigger {
-      padding: 4px 10px 0 16px;
+      padding: 1px 10px 0 16px;
       cursor: pointer;
 
       .anticon {
@@ -150,6 +158,7 @@
       }
 
       &__inner,
+      &__inner.is-link,
       &__separator {
         color: @white;
       }

+ 6 - 33
src/layouts/default/index.less

@@ -1,39 +1,12 @@
 @import (reference) '../../design/index.less';
 
 .default-layout {
-  &__content {
-    position: relative;
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  min-height: 100%;
 
-    &.fixed {
-      overflow: hidden;
-    }
-  }
-
-  &__loading {
-    position: absolute;
-    z-index: @page-loading-z-index;
-  }
-
-  &__main {
-    position: relative;
-    height: 100%;
-
-    &.fixed {
-      overflow-x: hidden;
-      overflow-y: auto;
-    }
-
-    &.fixed.lock {
-      overflow: hidden;
-    }
-  }
-
-  .layout-content {
-    position: relative;
-
-    &.fixed {
-      width: 1200px;
-      margin: 0 auto;
-    }
+  > .ant-layout {
+    min-height: 100%;
   }
 }

+ 50 - 87
src/layouts/default/index.tsx

@@ -1,129 +1,92 @@
-import { defineComponent, unref, computed } from 'vue';
+import './index.less';
+
+import { defineComponent, unref, computed, ref } from 'vue';
 import { Layout, BackTop } from 'ant-design-vue';
 import LayoutHeader from './header/LayoutHeader';
 
-import { appStore } from '/@/store/modules/app';
-import LayoutContent from './LayoutContent';
-import LayoutSideBar from './sider/LayoutSideBar';
+import LayoutContent from './content';
+import LayoutFooter from './footer';
+import LayoutLockPage from './lock';
+import LayoutSideBar from './sider';
 import SettingBtn from './setting/index.vue';
-import MultipleTabs from './multitabs/index';
+import LayoutMultipleHeader from './header/LayoutMultipleHeader';
 
-import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
+import { MenuModeEnum } from '/@/enums/menuEnum';
+
+import { useRouter } from 'vue-router';
 import { useFullContent } from '/@/hooks/web/useFullContent';
+import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
+import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+import { useRootSetting } from '/@/hooks/setting/useRootSetting';
+import { createLayoutContext } from './useLayoutContext';
 
-import LockPage from '/@/views/sys/lock/index.vue';
 import { registerGlobComp } from '/@/components/registerGlobComp';
 
-import './index.less';
 export default defineComponent({
   name: 'DefaultLayout',
   setup() {
+    const { currentRoute } = useRouter();
+    const headerRef = ref<ComponentRef>(null);
+
+    createLayoutContext({ fullHeaderRef: headerRef });
+
     // ! Only register global components here
     // ! Can reduce the size of the first screen code
     // default layout It is loaded after login. So it won’t be packaged to the first screen
     registerGlobComp();
 
-    const { getFullContent } = useFullContent();
-
-    const getProjectConfigRef = computed(() => appStore.getProjectConfig);
+    const { getShowFullHeaderRef } = useHeaderSetting();
 
-    const getLockMainScrollStateRef = computed(() => appStore.getLockMainScrollState);
+    const { getUseOpenBackTop, getShowSettingButton, getShowFooter } = useRootSetting();
 
-    const showHeaderRef = computed(() => {
-      const {
-        headerSetting: { show },
-      } = unref(getProjectConfigRef);
-      return show;
-    });
+    const { getShowMenu, getMenuMode, getSplit } = useMenuSetting();
 
-    const showMixHeaderRef = computed(() => {
-      const {
-        menuSetting: { type },
-      } = unref(getProjectConfigRef);
-      return type !== MenuTypeEnum.SIDEBAR && unref(showHeaderRef);
-    });
+    const { getFullContent } = useFullContent();
 
-    const getIsLockRef = computed(() => {
-      const { getLockInfo } = appStore;
-      const { isLock } = getLockInfo;
-      return isLock;
+    const getShowLayoutFooter = computed(() => {
+      return unref(getShowFooter) && !unref(currentRoute).meta?.hiddenFooter;
     });
 
     const showSideBarRef = computed(() => {
-      const {
-        menuSetting: { show, mode, split },
-      } = unref(getProjectConfigRef);
-      return split || (show && mode !== MenuModeEnum.HORIZONTAL && !unref(getFullContent));
-    });
-
-    const showFullHeaderRef = computed(() => {
-      return !unref(getFullContent) && unref(showMixHeaderRef) && unref(showHeaderRef);
-    });
-
-    const showInsetHeaderRef = computed(() => {
-      return !unref(getFullContent) && !unref(showMixHeaderRef) && unref(showHeaderRef);
-    });
-
-    const fixedHeaderClsRef = computed(() => {
-      const {
-        headerSetting: { fixed },
-      } = unref(getProjectConfigRef);
-      const fixedHeaderCls = fixed
-        ? 'fixed' + (unref(getLockMainScrollStateRef) ? ' lock' : '')
-        : '';
-      return fixedHeaderCls;
-    });
-
-    const showTabsRef = computed(() => {
-      const {
-        multiTabsSetting: { show },
-      } = unref(getProjectConfigRef);
-      return show && !unref(getFullContent);
-    });
-
-    const showClassSideBarRef = computed(() => {
-      const {
-        menuSetting: { split, hidden },
-      } = unref(getProjectConfigRef);
-      return split ? hidden : true;
+      return (
+        unref(getSplit) ||
+        (unref(getShowMenu) &&
+          unref(getMenuMode) !== MenuModeEnum.HORIZONTAL &&
+          !unref(getFullContent))
+      );
     });
 
-    function getTarget(): any {
-      const {
-        headerSetting: { fixed },
-      } = unref(getProjectConfigRef);
-      return document.querySelector(`.default-layout__${fixed ? 'main' : 'content'}`);
+    function renderFeatures() {
+      return (
+        <>
+          <LayoutLockPage />
+          {/* back top */}
+          {unref(getUseOpenBackTop) && <BackTop target={() => document.body} />}
+          {/* open setting drawer */}
+          {unref(getShowSettingButton) && <SettingBtn />}
+        </>
+      );
     }
 
     return () => {
-      const { useOpenBackTop, showSettingButton } = unref(getProjectConfigRef);
       return (
-        <Layout class="default-layout relative">
+        <Layout class="default-layout">
           {() => (
             <>
-              {/* lock page */}
-              {unref(getIsLockRef) && <LockPage />}
-              {/* back top */}
-              {useOpenBackTop && <BackTop target={getTarget} />}
-              {/* open setting drawer */}
-              {showSettingButton && <SettingBtn />}
+              {renderFeatures()}
 
-              {unref(showFullHeaderRef) && <LayoutHeader />}
+              {unref(getShowFullHeaderRef) && <LayoutHeader fixed={true} ref={headerRef} />}
 
               <Layout>
                 {() => (
                   <>
-                    {unref(showSideBarRef) && (
-                      <LayoutSideBar class={unref(showClassSideBarRef) ? '' : 'hidden'} />
-                    )}
-                    <Layout class={[`default-layout__content`, unref(fixedHeaderClsRef)]}>
+                    {unref(showSideBarRef) && <LayoutSideBar />}
+                    <Layout>
                       {() => (
                         <>
-                          {unref(showInsetHeaderRef) && <LayoutHeader />}
-
-                          {unref(showTabsRef) && <MultipleTabs />}
-
-                          <LayoutContent class={unref(fixedHeaderClsRef)} />
+                          <LayoutMultipleHeader />
+                          <LayoutContent />
+                          {unref(getShowLayoutFooter) && <LayoutFooter />}
                         </>
                       )}
                     </Layout>

+ 0 - 0
src/layouts/default/header/LockActionItem.less → src/layouts/default/lock/LockAction.less


+ 1 - 1
src/layouts/default/header/LockActionItem.tsx → src/layouts/default/lock/LockAction.tsx

@@ -1,4 +1,4 @@
-import './LockActionItem.less';
+import './LockAction.less';
 
 import { defineComponent } from 'vue';
 import { BasicModal, useModalInner } from '/@/components/Modal/index';

+ 17 - 0
src/layouts/default/lock/index.tsx

@@ -0,0 +1,17 @@
+import { defineComponent, unref, computed } from 'vue';
+import { appStore } from '/@/store/modules/app';
+import LockPage from '/@/views/sys/lock/index.vue';
+
+export default defineComponent({
+  name: 'LayoutLockPage',
+  setup() {
+    const getIsLockRef = computed(() => {
+      const { getLockInfo } = appStore;
+      const { isLock } = getLockInfo;
+      return isLock;
+    });
+    return () => {
+      return unref(getIsLockRef) ? <LockPage /> : null;
+    };
+  },
+});

+ 11 - 11
src/layouts/default/menu/LayoutMenu.tsx → src/layouts/default/menu/index.tsx

@@ -17,7 +17,7 @@ import { useSplitMenu } from './useLayoutMenu';
 import { openWindow } from '/@/utils';
 
 export default defineComponent({
-  name: 'DefaultLayoutMenu',
+  name: 'LayoutMenu',
   props: {
     theme: {
       type: String as PropType<string>,
@@ -50,12 +50,12 @@ export default defineComponent({
     const {
       setMenuSetting,
       getShowSearch,
-      getMode,
-      getType,
+      getMenuMode,
+      getMenuType,
       getCollapsedShowTitle,
       getCollapsedShowSearch,
       getIsSidebarType,
-      getTheme,
+      getMenuTheme,
       getCollapsed,
       getAccordion,
     } = useMenuSetting();
@@ -66,9 +66,9 @@ export default defineComponent({
 
     const showLogo = computed(() => unref(getShowLogo) && unref(getIsSidebarType));
 
-    const getMenuMode = computed(() => props.menuMode || unref(getMode));
+    const getComputedMenuMode = computed(() => props.menuMode || unref(getMenuMode));
 
-    const getMenuTheme = computed(() => props.theme || unref(getTheme));
+    const getComputedMenuTheme = computed(() => props.theme || unref(getMenuTheme));
 
     const appendClass = computed(() => props.splitType === MenuSplitTyeEnum.TOP);
 
@@ -111,8 +111,8 @@ export default defineComponent({
       return (
         <AppLogo
           showTitle={!unref(getCollapsed)}
-          class={[`layout-menu__logo`, unref(getMenuTheme)]}
-          theme={unref(getMenuTheme)}
+          class={[`layout-menu__logo`, unref(getComputedMenuTheme)]}
+          theme={unref(getComputedMenuTheme)}
         />
       );
     }
@@ -124,10 +124,10 @@ export default defineComponent({
           beforeClickFn={beforeMenuClickFn}
           isHorizontal={props.isHorizontal}
           appendClass={unref(appendClass)}
-          type={unref(getType)}
-          mode={unref(getMenuMode)}
+          type={unref(getMenuType)}
+          mode={unref(getComputedMenuMode)}
           collapsedShowTitle={unref(getCollapsedShowTitle)}
-          theme={unref(getMenuTheme)}
+          theme={unref(getComputedMenuTheme)}
           showLogo={unref(showLogo)}
           search={unref(showSearch)}
           items={unref(menusRef)}

+ 1 - 9
src/layouts/default/multitabs/TabContent.tsx

@@ -6,7 +6,6 @@ import { TabItem, tabStore } from '/@/store/modules/tab';
 import { getScaleAction, TabContentProps } from './tab.data';
 
 import { Dropdown } from '/@/components/Dropdown/index';
-import Icon from '/@/components/Icon/index';
 import { RightOutlined } from '@ant-design/icons-vue';
 import { appStore } from '/@/store/modules/app';
 
@@ -57,18 +56,11 @@ export default defineComponent({
     /**
      * @description: 渲染图标
      */
-    function renderIcon() {
-      const { tabItem } = props;
-      if (!tabItem) return;
-      const icon = tabItem.meta && tabItem.meta.icon;
-      if (!icon || !unref(getProjectConfigRef).multiTabsSetting.showIcon) return null;
-      return <Icon icon={icon} class="align-middle " style={{ marginBottom: '2px' }} />;
-    }
+
     function renderTabContent() {
       const { tabItem: { meta } = {} } = props;
       return (
         <div class={`multiple-tabs-content__content `} onContextmenu={handleContextMenu}>
-          {renderIcon()}
           <span class="ml-1">{meta && meta.title}</span>
         </div>
       );

+ 277 - 285
src/layouts/default/setting/SettingDrawer.tsx

@@ -1,29 +1,36 @@
-import { defineComponent, computed, unref, ref } from 'vue';
+import type { ProjectConfig } from '/@/types/config';
+
+import defaultSetting from '/@/settings/projectSetting';
+
+import { defineComponent, computed, unref, FunctionalComponent } from 'vue';
 import { BasicDrawer } from '/@/components/Drawer/index';
 import { Divider, Switch, Tooltip, InputNumber, Select } from 'ant-design-vue';
 import Button from '/@/components/Button/index.vue';
-import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
 import { CopyOutlined, RedoOutlined, CheckOutlined } from '@ant-design/icons-vue';
+
+import { MenuTypeEnum } from '/@/enums/menuEnum';
 import { appStore } from '/@/store/modules/app';
-import { ProjectConfig } from '/@/types/config';
 
 import { useMessage } from '/@/hooks/web/useMessage';
 import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
+import { useRootSetting } from '/@/hooks/setting/useRootSetting';
+import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
+import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
 
-import defaultSetting from '/@/settings/projectSetting';
-
-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 { updateColorWeak, updateGrayMode } from '/@/setup/theme';
+
 import { baseHandler } from './handler';
+
 import {
   HandlerEnum,
   contentModeOptions,
   topMenuAlignOptions,
   menuTriggerOptions,
   routerTransitionOptions,
-} from './const';
+  menuTypeList,
+} from './enum';
+
 import { HEADER_PRESET_BG_COLOR_LIST, SIDE_BAR_BG_COLOR_LIST } from '/@/settings/colorSetting';
 
 interface SwitchOptions {
@@ -40,215 +47,280 @@ interface SelectConfig {
   handler?: Fn;
 }
 
-interface ThemeOptions {
-  def?: string;
-  handler?: Fn;
+interface ThemePickerProps {
+  colorList: string[];
+  handler: Fn;
+  def: string;
 }
 
+const { createSuccessModal, createMessage } = useMessage();
+
+/**
+ * Menu type Picker comp
+ */
+const MenuTypePicker: FunctionalComponent = () => {
+  const { getIsHorizontal, getMenuType } = useMenuSetting();
+  return (
+    <div class={`setting-drawer__siderbar`}>
+      {menuTypeList.map((item) => {
+        const { title, type: ItemType, mode, src } = item;
+        return (
+          <Tooltip title={title} placement="bottom" key={title}>
+            {{
+              default: () => (
+                <div
+                  onClick={baseHandler.bind(null, HandlerEnum.CHANGE_LAYOUT, {
+                    mode: mode,
+                    type: ItemType,
+                    split: unref(getIsHorizontal) ? false : undefined,
+                  })}
+                >
+                  <CheckOutlined
+                    class={['check-icon', unref(getMenuType) === ItemType ? 'active' : '']}
+                  />
+                  <img src={src} />
+                </div>
+              ),
+            }}
+          </Tooltip>
+        );
+      })}
+    </div>
+  );
+};
+
+const ThemePicker: FunctionalComponent<ThemePickerProps> = (props) => {
+  return (
+    <div class={`setting-drawer__theme-item`}>
+      {props.colorList.map((color) => {
+        return (
+          <span
+            onClick={() => props.handler?.(color)}
+            key={color}
+            class={[props.def === color ? 'active' : '']}
+            style={{
+              background: color,
+            }}
+          >
+            <CheckOutlined class="icon" />
+          </span>
+        );
+      })}
+    </div>
+  );
+};
+
+/**
+ * FooterButton component
+ */
+const FooterButton: FunctionalComponent = () => {
+  const { getRootSetting } = useRootSetting();
+  function handleCopy() {
+    const { isSuccessRef } = useCopyToClipboard(JSON.stringify(unref(getRootSetting), null, 2));
+    unref(isSuccessRef) &&
+      createSuccessModal({
+        title: '操作成功',
+        content: '复制成功,请到 src/settings/projectSetting.ts 中修改配置!',
+      });
+  }
+  function handleResetSetting() {
+    try {
+      appStore.commitProjectConfigState(defaultSetting);
+      const { colorWeak, grayMode } = defaultSetting;
+      // updateTheme(themeColor);
+      updateColorWeak(colorWeak);
+      updateGrayMode(grayMode);
+      createMessage.success('重置成功!');
+    } catch (error) {
+      createMessage.error(error);
+    }
+  }
+
+  function handleClearAndRedo() {
+    localStorage.clear();
+    appStore.resumeAllState();
+    location.reload();
+  }
+
+  return (
+    <div class="setting-drawer__footer">
+      <Button type="primary" block onClick={handleCopy}>
+        {() => (
+          <>
+            <CopyOutlined class="mr-2" />
+            拷贝
+          </>
+        )}
+      </Button>
+      <Button block class="mt-2" onClick={handleResetSetting} color="warning">
+        {() => (
+          <>
+            <RedoOutlined class="mr-2" />
+            重置
+          </>
+        )}
+      </Button>
+      <Button block class="mt-2" onClick={handleClearAndRedo} color="error">
+        {() => (
+          <>
+            <RedoOutlined class="mr-2" />
+            清空缓存并返回登录页
+          </>
+        )}
+      </Button>
+    </div>
+  );
+};
+
 export default defineComponent({
   name: 'SettingDrawer',
   setup(_, { attrs }) {
-    const { createSuccessModal, createMessage } = useMessage();
+    const {
+      getContentMode,
+      getRouterTransition,
+      getOpenRouterTransition,
+      getOpenPageLoading,
+      getShowFooter,
+      getShowBreadCrumb,
+      getShowBreadCrumbIcon,
+      getShowLogo,
+      getFullContent,
+      getColorWeak,
+      getGrayMode,
+    } = useRootSetting();
 
-    const getProjectConfigRef = computed(() => {
-      return appStore.getProjectConfig;
-    });
+    const {
+      getIsHorizontal,
+      getShowMenu,
+      getMenuType,
+      getTrigger,
+      getCollapsedShowTitle,
+      getMenuFixed,
+      getCollapsed,
+      getShowSearch,
+      getHasDrag,
+      getTopMenuAlign,
+      getAccordion,
+      getMenuWidth,
+      getMenuBgColor,
+      getIsTopMenu,
+      getSplit,
+    } = useMenuSetting();
 
-    const getIsHorizontalRef = computed(() => {
-      return unref(getProjectConfigRef).menuSetting.mode === MenuModeEnum.HORIZONTAL;
-    });
+    const { getShowHeader, getFixed: getHeaderFixed, getHeaderBgColor } = useHeaderSetting();
 
-    const getShowHeaderRef = computed(() => {
-      return unref(getProjectConfigRef).headerSetting.show;
-    });
+    const { getShowMultipleTab, getShowQuick } = useMultipleTabSetting();
 
     const getShowMenuRef = computed(() => {
-      return unref(getProjectConfigRef).menuSetting.show && !unref(getIsHorizontalRef);
+      return unref(getShowMenu) && !unref(getIsHorizontal);
     });
 
-    const getShowTabsRef = computed(() => {
-      return unref(getProjectConfigRef).multiTabsSetting.show;
-    });
-
-    function handleCopy() {
-      const { isSuccessRef } = useCopyToClipboard(
-        JSON.stringify(unref(getProjectConfigRef), null, 2)
+    function renderSidebar() {
+      return (
+        <>
+          <MenuTypePicker />
+          {renderSwitchItem('分割菜单', {
+            handler: (e) => {
+              baseHandler(HandlerEnum.MENU_SPLIT, e);
+            },
+            def: unref(getSplit),
+            disabled: !unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX,
+          })}
+        </>
       );
-      unref(isSuccessRef) &&
-        createSuccessModal({
-          title: '操作成功',
-          content: '复制成功,请到 src/settings/projectSetting.ts 中修改配置!',
-        });
     }
 
-    function handleResetSetting() {
-      try {
-        appStore.commitProjectConfigState(defaultSetting);
-        const { colorWeak, grayMode } = defaultSetting;
-        // updateTheme(themeColor);
-        updateColorWeak(colorWeak);
-        updateGrayMode(grayMode);
-        createMessage.success('重置成功!');
-      } catch (error) {
-        createMessage.error(error);
-      }
-    }
-
-    function handleClearAndRedo() {
-      localStorage.clear();
-      appStore.resumeAllState();
-      location.reload();
+    function renderTheme() {
+      return (
+        <>
+          <Divider>{() => '顶栏主题'}</Divider>
+          <ThemePicker
+            colorList={HEADER_PRESET_BG_COLOR_LIST}
+            def={unref(getHeaderBgColor)}
+            handler={(e) => {
+              baseHandler(HandlerEnum.HEADER_THEME, e);
+            }}
+          />
+          <Divider>{() => '菜单主题'}</Divider>
+          <ThemePicker
+            colorList={SIDE_BAR_BG_COLOR_LIST}
+            def={unref(getMenuBgColor)}
+            handler={(e) => {
+              baseHandler(HandlerEnum.MENU_THEME, e);
+            }}
+          />
+        </>
+      );
     }
 
-    function renderSidebar() {
-      const {
-        menuSetting: { type, split },
-      } = unref(getProjectConfigRef);
-
-      const typeList = ref([
-        {
-          title: '左侧菜单模式',
-          mode: MenuModeEnum.INLINE,
-          type: MenuTypeEnum.SIDEBAR,
-          src: sidebarImg,
-        },
-        {
-          title: '混合模式',
-          mode: MenuModeEnum.INLINE,
-          type: MenuTypeEnum.MIX,
-          src: mixImg,
-        },
-
-        {
-          title: '顶部菜单模式',
-          mode: MenuModeEnum.HORIZONTAL,
-          type: MenuTypeEnum.TOP_MENU,
-          src: menuTopImg,
-        },
-      ]);
-      return [
-        <div class={`setting-drawer__siderbar`}>
-          {unref(typeList).map((item) => {
-            const { title, type: ItemType, mode, src } = item;
-            return (
-              <Tooltip title={title} placement="bottom" key={title}>
-                {{
-                  default: () => (
-                    <div
-                      onClick={baseHandler.bind(null, HandlerEnum.CHANGE_LAYOUT, {
-                        mode: mode,
-                        type: ItemType,
-                        split: unref(getIsHorizontalRef) ? false : undefined,
-                      })}
-                    >
-                      <CheckOutlined class={['check-icon', type === ItemType ? 'active' : '']} />
-                      <img src={src} />
-                    </div>
-                  ),
-                }}
-              </Tooltip>
-            );
-          })}
-        </div>,
-        renderSwitchItem('分割菜单', {
-          handler: (e) => {
-            baseHandler(HandlerEnum.MENU_SPLIT, e);
-          },
-          def: split,
-          disabled: !unref(getShowMenuRef) || type !== MenuTypeEnum.MIX,
-        }),
-        // renderSelectItem('顶栏主题', {
-        //   handler: (e) => {
-        //     baseHandler(HandlerEnum.HEADER_THEME, e);
-        //   },
-        //   def: headerTheme,
-        //   options: themeOptions,
-        //   disabled: !unref(getShowHeaderRef),
-        // }),
-        // renderSelectItem('菜单主题', {
-        //   handler: (e) => {
-        //     baseHandler(HandlerEnum.MENU_THEME, e);
-        //   },
-        //   def: menuTheme,
-        //   options: themeOptions,
-        //   disabled: !unref(getShowMenuRef),
-        // }),
-      ];
-    }
     /**
      * @description:
      */
     function renderFeatures() {
-      const {
-        contentMode,
-        headerSetting: { fixed },
-        menuSetting: {
-          hasDrag,
-          collapsed,
-          showSearch,
-          menuWidth,
-          topMenuAlign,
-          collapsedShowTitle,
-          trigger,
-          accordion,
-        } = {},
-      } = appStore.getProjectConfig;
       return [
         renderSwitchItem('侧边菜单拖拽', {
           handler: (e) => {
             baseHandler(HandlerEnum.MENU_HAS_DRAG, e);
           },
-          def: hasDrag,
+          def: unref(getHasDrag),
           disabled: !unref(getShowMenuRef),
         }),
         renderSwitchItem('侧边菜单搜索', {
           handler: (e) => {
             baseHandler(HandlerEnum.MENU_SHOW_SEARCH, e);
           },
-          def: showSearch,
+          def: unref(getShowSearch),
           disabled: !unref(getShowMenuRef),
         }),
         renderSwitchItem('侧边菜单手风琴模式', {
           handler: (e) => {
             baseHandler(HandlerEnum.MENU_ACCORDION, e);
           },
-          def: accordion,
+          def: unref(getAccordion),
           disabled: !unref(getShowMenuRef),
         }),
         renderSwitchItem('折叠菜单', {
           handler: (e) => {
             baseHandler(HandlerEnum.MENU_COLLAPSED, e);
           },
-          def: collapsed,
+          def: unref(getCollapsed),
           disabled: !unref(getShowMenuRef),
         }),
         renderSwitchItem('折叠菜单显示名称', {
           handler: (e) => {
             baseHandler(HandlerEnum.MENU_COLLAPSED_SHOW_TITLE, e);
           },
-          def: collapsedShowTitle,
-          disabled: !unref(getShowMenuRef) || !collapsed,
+          def: unref(getCollapsedShowTitle),
+          disabled: !unref(getShowMenuRef) || !unref(getCollapsed),
         }),
         renderSwitchItem('固定header', {
           handler: (e) => {
             baseHandler(HandlerEnum.HEADER_FIXED, e);
           },
-          def: fixed,
-          disabled: !unref(getShowHeaderRef),
+          def: unref(getHeaderFixed),
+          disabled: !unref(getShowHeader),
+        }),
+        renderSwitchItem('固定Siderbar', {
+          handler: (e) => {
+            baseHandler(HandlerEnum.MENU_FIXED, e);
+          },
+          def: unref(getMenuFixed),
+          disabled: !unref(getShowMenuRef),
         }),
         renderSelectItem('顶部菜单布局', {
           handler: (e) => {
             baseHandler(HandlerEnum.MENU_TOP_ALIGN, e);
           },
-          def: topMenuAlign,
+          def: unref(getTopMenuAlign),
           options: topMenuAlignOptions,
-          disabled: !unref(getShowHeaderRef),
+          disabled: !unref(getShowHeader) || (!unref(getIsTopMenu) && !unref(getSplit)),
         }),
         renderSelectItem('菜单折叠按钮', {
           handler: (e) => {
             baseHandler(HandlerEnum.MENU_TRIGGER, e);
           },
-          def: trigger,
+          disabled: !unref(getShowMenuRef),
+          def: unref(getTrigger),
           options: menuTriggerOptions,
         }),
 
@@ -256,7 +328,7 @@ export default defineComponent({
           handler: (e) => {
             baseHandler(HandlerEnum.CONTENT_MODE, e);
           },
-          def: contentMode,
+          def: unref(getContentMode),
           options: contentModeOptions,
         }),
         <div class={`setting-drawer__cell-item`}>
@@ -286,7 +358,7 @@ export default defineComponent({
             min={100}
             step={10}
             disabled={!unref(getShowMenuRef)}
-            defaultValue={menuWidth}
+            defaultValue={unref(getMenuWidth)}
             formatter={(value: string) => `${parseInt(value)}px`}
             onChange={(e: any) => {
               baseHandler(HandlerEnum.MENU_WIDTH, e);
@@ -295,121 +367,112 @@ export default defineComponent({
         </div>,
       ];
     }
-    function renderTransition() {
-      const { routerTransition, openRouterTransition, openPageLoading } = appStore.getProjectConfig;
 
-      return (
-        <>
-          {renderSwitchItem('页面切换loading', {
-            handler: (e) => {
-              baseHandler(HandlerEnum.OPEN_PAGE_LOADING, e);
-            },
-            def: openPageLoading,
-          })}
-          {renderSwitchItem('切换动画', {
-            handler: (e) => {
-              baseHandler(HandlerEnum.OPEN_ROUTE_TRANSITION, e);
-            },
-            def: openRouterTransition,
-          })}
-          {renderSelectItem('路由动画', {
-            handler: (e) => {
-              baseHandler(HandlerEnum.ROUTER_TRANSITION, e);
-            },
-            def: routerTransition,
-            options: routerTransitionOptions,
-            disabled: !openRouterTransition,
-          })}
-        </>
-      );
-    }
     function renderContent() {
-      const {
-        grayMode,
-        colorWeak,
-        fullContent,
-        showLogo,
-        headerSetting: { show: showHeader },
-        menuSetting: { show: showMenu },
-        multiTabsSetting: { show: showMultiple, showQuick, showIcon: showTabIcon },
-        showBreadCrumb,
-        showBreadCrumbIcon,
-      } = unref(getProjectConfigRef);
       return [
         renderSwitchItem('面包屑', {
           handler: (e) => {
             baseHandler(HandlerEnum.SHOW_BREADCRUMB, e);
           },
-          def: showBreadCrumb,
-          disabled: !unref(getShowHeaderRef),
+          def: unref(getShowBreadCrumb),
+          disabled: !unref(getShowHeader),
         }),
         renderSwitchItem('面包屑图标', {
           handler: (e) => {
             baseHandler(HandlerEnum.SHOW_BREADCRUMB_ICON, e);
           },
-          def: showBreadCrumbIcon,
-          disabled: !unref(getShowHeaderRef),
+          def: unref(getShowBreadCrumbIcon),
+          disabled: !unref(getShowHeader),
         }),
         renderSwitchItem('标签页', {
           handler: (e) => {
             baseHandler(HandlerEnum.TABS_SHOW, e);
           },
-          def: showMultiple,
+          def: unref(getShowMultipleTab),
         }),
         renderSwitchItem('标签页快捷按钮', {
           handler: (e) => {
             baseHandler(HandlerEnum.TABS_SHOW_QUICK, e);
           },
-          def: showQuick,
-          disabled: !unref(getShowTabsRef),
-        }),
-        renderSwitchItem('标签页图标', {
-          handler: (e) => {
-            baseHandler(HandlerEnum.TABS_SHOW_ICON, e);
-          },
-          def: showTabIcon,
-          disabled: !unref(getShowTabsRef),
+          def: unref(getShowQuick),
+          disabled: !unref(getShowMultipleTab),
         }),
+
         renderSwitchItem('左侧菜单', {
           handler: (e) => {
             baseHandler(HandlerEnum.MENU_SHOW_SIDEBAR, e);
           },
-          def: showMenu,
-          disabled: unref(getIsHorizontalRef),
+          def: unref(getShowMenu),
+          disabled: unref(getIsHorizontal),
         }),
         renderSwitchItem('顶栏', {
           handler: (e) => {
             baseHandler(HandlerEnum.HEADER_SHOW, e);
           },
-          def: showHeader,
+          def: unref(getShowHeader),
         }),
         renderSwitchItem('Logo', {
           handler: (e) => {
             baseHandler(HandlerEnum.SHOW_LOGO, e);
           },
-          def: showLogo,
+          def: unref(getShowLogo),
+        }),
+        renderSwitchItem('页脚', {
+          handler: (e) => {
+            baseHandler(HandlerEnum.SHOW_FOOTER, e);
+          },
+          def: unref(getShowFooter),
         }),
         renderSwitchItem('全屏内容', {
           handler: (e) => {
             baseHandler(HandlerEnum.FULL_CONTENT, e);
           },
-          def: fullContent,
+          def: unref(getFullContent),
         }),
         renderSwitchItem('灰色模式', {
           handler: (e) => {
             baseHandler(HandlerEnum.GRAY_MODE, e);
           },
-          def: grayMode,
+          def: unref(getGrayMode),
         }),
         renderSwitchItem('色弱模式', {
           handler: (e) => {
             baseHandler(HandlerEnum.COLOR_WEAK, e);
           },
-          def: colorWeak,
+          def: unref(getColorWeak),
         }),
       ];
     }
 
+    function renderTransition() {
+      return (
+        <>
+          {renderSwitchItem('页面切换loading', {
+            handler: (e) => {
+              baseHandler(HandlerEnum.OPEN_PAGE_LOADING, e);
+            },
+            def: unref(getOpenPageLoading),
+          })}
+
+          {renderSwitchItem('切换动画', {
+            handler: (e) => {
+              baseHandler(HandlerEnum.OPEN_ROUTE_TRANSITION, e);
+            },
+            def: unref(getOpenRouterTransition),
+          })}
+
+          {renderSelectItem('路由动画', {
+            handler: (e) => {
+              baseHandler(HandlerEnum.ROUTER_TRANSITION, e);
+            },
+            def: unref(getRouterTransition),
+            options: routerTransitionOptions,
+            disabled: !unref(getOpenRouterTransition),
+          })}
+        </>
+      );
+    }
+
     function renderSelectItem(text: string, config?: SelectConfig) {
       const { handler, def, disabled = false, options } = config || {};
       const opt = def ? { value: def, defaultValue: def } : {};
@@ -449,50 +512,6 @@ export default defineComponent({
       );
     }
 
-    function renderTheme() {
-      const { headerBgColor, menuBgColor } = unref(getProjectConfigRef);
-      return (
-        <>
-          <Divider>{() => '顶栏主题'}</Divider>
-          {renderThemeItem(HEADER_PRESET_BG_COLOR_LIST, {
-            def: headerBgColor,
-            handler: (e) => {
-              baseHandler(HandlerEnum.HEADER_THEME, e);
-            },
-          })}
-          <Divider>{() => '菜单主题'}</Divider>
-          {renderThemeItem(SIDE_BAR_BG_COLOR_LIST, {
-            def: menuBgColor,
-            handler: (e) => {
-              baseHandler(HandlerEnum.MENU_THEME, e);
-            },
-          })}
-        </>
-      );
-    }
-
-    function renderThemeItem(colorList: string[], opt: ThemeOptions) {
-      const { def, handler } = opt;
-      return (
-        <div class={`setting-drawer__theme-item`}>
-          {colorList.map((item) => {
-            return (
-              <span
-                onClick={() => handler && handler(item)}
-                key={item}
-                class={[def === item ? 'active' : '']}
-                style={{
-                  background: item,
-                }}
-              >
-                <CheckOutlined class="icon" />
-              </span>
-            );
-          })}
-        </div>
-      );
-    }
-
     return () => (
       <BasicDrawer {...attrs} title="项目配置" width={300} wrapClassName="setting-drawer">
         {{
@@ -500,9 +519,7 @@ export default defineComponent({
             <>
               <Divider>{() => '导航栏模式'}</Divider>
               {renderSidebar()}
-
               {renderTheme()}
-
               <Divider>{() => '界面功能'}</Divider>
               {renderFeatures()}
               <Divider>{() => '界面显示'}</Divider>
@@ -510,32 +527,7 @@ export default defineComponent({
               <Divider>{() => '切换动画'}</Divider>
               {renderTransition()}
               <Divider />
-              <div class="setting-drawer__footer">
-                <Button type="primary" block onClick={handleCopy}>
-                  {() => (
-                    <>
-                      <CopyOutlined class="mr-2" />
-                      拷贝
-                    </>
-                  )}
-                </Button>
-                <Button block class="mt-2" onClick={handleResetSetting} color="warning">
-                  {() => (
-                    <>
-                      <RedoOutlined class="mr-2" />
-                      重置
-                    </>
-                  )}
-                </Button>
-                <Button block class="mt-2" onClick={handleClearAndRedo} color="error">
-                  {() => (
-                    <>
-                      <RedoOutlined class="mr-2" />
-                      清空缓存并返回登录页
-                    </>
-                  )}
-                </Button>
-              </div>
+              <FooterButton />
             </>
           ),
         }}

+ 29 - 2
src/layouts/default/setting/const.ts → src/layouts/default/setting/enum.ts

@@ -1,5 +1,9 @@
 import { ContentEnum, RouterTransitionEnum, ThemeEnum } from '/@/enums/appEnum';
-import { TopMenuAlignEnum, TriggerEnum } from '/@/enums/menuEnum';
+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';
 
 export enum HandlerEnum {
   CHANGE_LAYOUT,
@@ -15,6 +19,7 @@ export enum HandlerEnum {
   MENU_THEME,
   MENU_SPLIT,
   MENU_SHOW_SEARCH,
+  MENU_FIXED,
 
   // header
   HEADER_SHOW,
@@ -23,7 +28,6 @@ export enum HandlerEnum {
 
   TABS_SHOW_QUICK,
   TABS_SHOW,
-  TABS_SHOW_ICON,
 
   OPEN_PAGE_LOADING,
   OPEN_ROUTE_TRANSITION,
@@ -36,6 +40,7 @@ export enum HandlerEnum {
   GRAY_MODE,
   COLOR_WEAK,
   SHOW_LOGO,
+  SHOW_FOOTER,
 }
 
 export const themeOptions = [
@@ -102,3 +107,25 @@ export const routerTransitionOptions = [
     value: item,
   };
 });
+
+export const menuTypeList = [
+  {
+    title: '左侧菜单模式',
+    mode: MenuModeEnum.INLINE,
+    type: MenuTypeEnum.SIDEBAR,
+    src: sidebarImg,
+  },
+  {
+    title: '混合模式',
+    mode: MenuModeEnum.INLINE,
+    type: MenuTypeEnum.MIX,
+    src: mixImg,
+  },
+
+  {
+    title: '顶部菜单模式',
+    mode: MenuModeEnum.HORIZONTAL,
+    type: MenuTypeEnum.TOP_MENU,
+    src: menuTopImg,
+  },
+];

+ 65 - 127
src/layouts/default/setting/handler.ts

@@ -1,5 +1,4 @@
-import { HandlerEnum } from './const';
-// import { MenuThemeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
+import { HandlerEnum } from './enum';
 import {
   updateColorWeak,
   updateGrayMode,
@@ -19,12 +18,7 @@ export function handler(event: HandlerEnum, value: any): DeepPartial<ProjectConf
     case HandlerEnum.CHANGE_LAYOUT:
       const { mode, type, split } = value;
       const splitOpt = split === undefined ? { split } : {};
-      // let headerSetting = {};
-      // if (type === MenuTypeEnum.TOP_MENU) {
-      //   headerSetting = {
-      //     theme: MenuThemeEnum.DARK,
-      //   };
-      // }
+
       return {
         menuSetting: {
           mode,
@@ -33,159 +27,103 @@ export function handler(event: HandlerEnum, value: any): DeepPartial<ProjectConf
           show: true,
           ...splitOpt,
         },
-        // headerSetting,
       };
 
     case HandlerEnum.MENU_HAS_DRAG:
-      return {
-        menuSetting: {
-          hasDrag: value,
-        },
-      };
+      return { menuSetting: { hasDrag: value } };
 
     case HandlerEnum.MENU_ACCORDION:
-      return {
-        menuSetting: {
-          accordion: value,
-        },
-      };
+      return { menuSetting: { accordion: value } };
+
     case HandlerEnum.MENU_TRIGGER:
-      return {
-        menuSetting: {
-          trigger: value,
-        },
-      };
+      return { menuSetting: { trigger: value } };
+
     case HandlerEnum.MENU_TOP_ALIGN:
-      return {
-        menuSetting: {
-          topMenuAlign: value,
-        },
-      };
+      return { menuSetting: { topMenuAlign: value } };
+
     case HandlerEnum.MENU_COLLAPSED:
-      return {
-        menuSetting: {
-          collapsed: value,
-        },
-      };
+      return { menuSetting: { collapsed: value } };
+
     case HandlerEnum.MENU_WIDTH:
-      return {
-        menuSetting: {
-          menuWidth: value,
-        },
-      };
+      return { menuSetting: { menuWidth: value } };
+
     case HandlerEnum.MENU_COLLAPSED_SHOW_TITLE:
-      return {
-        menuSetting: {
-          collapsedShowTitle: value,
-        },
-      };
+      return { menuSetting: { collapsedShowTitle: value } };
+
     case HandlerEnum.MENU_SHOW_SIDEBAR:
-      return {
-        menuSetting: {
-          show: value,
-        },
-      };
+      return { menuSetting: { show: value } };
+
     case HandlerEnum.MENU_THEME:
       updateSidebarBgColor(value);
-      return {
-        menuBgColor: value,
-        // menuSetting: {
-        //   theme: value,
-        // },
-      };
+      return { menuSetting: { bgColor: value } };
+
     case HandlerEnum.MENU_SPLIT:
-      return {
-        menuSetting: {
-          split: value,
-        },
-      };
+      return { menuSetting: { split: value } };
+
+    case HandlerEnum.MENU_FIXED:
+      return { menuSetting: { fixed: value } };
+
     case HandlerEnum.MENU_SHOW_SEARCH:
-      return {
-        menuSetting: {
-          showSearch: value,
-        },
-      };
+      return { menuSetting: { showSearch: value } };
+
+    // ============root==================
+
     case HandlerEnum.OPEN_PAGE_LOADING:
-      return {
-        openPageLoading: value,
-      };
+      appStore.commitPageLoadingState(false);
+      return { openPageLoading: value };
+
     case HandlerEnum.OPEN_ROUTE_TRANSITION:
-      return {
-        openRouterTransition: value,
-      };
+      return { openRouterTransition: value };
+
     case HandlerEnum.ROUTER_TRANSITION:
-      return {
-        routerTransition: value,
-      };
+      return { routerTransition: value };
+
     case HandlerEnum.LOCK_TIME:
-      return {
-        lockTime: value,
-      };
+      return { lockTime: value };
+
     case HandlerEnum.FULL_CONTENT:
-      return {
-        fullContent: value,
-      };
+      return { fullContent: value };
+
     case HandlerEnum.CONTENT_MODE:
-      return {
-        contentMode: value,
-      };
+      return { contentMode: value };
+
     case HandlerEnum.SHOW_BREADCRUMB:
-      return {
-        showBreadCrumb: value,
-      };
+      return { showBreadCrumb: value };
+
     case HandlerEnum.SHOW_BREADCRUMB_ICON:
-      return {
-        showBreadCrumbIcon: value,
-      };
+      return { showBreadCrumbIcon: value };
+
     case HandlerEnum.GRAY_MODE:
       updateGrayMode(value);
-      return {
-        grayMode: value,
-      };
+      return { grayMode: value };
+
+    case HandlerEnum.SHOW_FOOTER:
+      return { showFooter: value };
+
     case HandlerEnum.COLOR_WEAK:
       updateColorWeak(value);
-      return {
-        colorWeak: value,
-      };
+      return { colorWeak: value };
+
     case HandlerEnum.SHOW_LOGO:
-      return {
-        showLogo: value,
-      };
+      return { showLogo: value };
+
+    // ============tabs==================
     case HandlerEnum.TABS_SHOW_QUICK:
-      return {
-        multiTabsSetting: {
-          showQuick: value,
-        },
-      };
-    case HandlerEnum.TABS_SHOW_ICON:
-      return {
-        multiTabsSetting: {
-          showIcon: value,
-        },
-      };
+      return { multiTabsSetting: { showQuick: value } };
+
     case HandlerEnum.TABS_SHOW:
-      return {
-        multiTabsSetting: {
-          show: value,
-        },
-      };
+      return { multiTabsSetting: { show: value } };
+
+    // ============header==================
     case HandlerEnum.HEADER_THEME:
       updateHeaderBgColor(value);
-      return {
-        headerBgColor: value,
-      };
+      return { headerSetting: { bgColor: value } };
+
     case HandlerEnum.HEADER_FIXED:
-      return {
-        headerSetting: {
-          fixed: value,
-        },
-      };
+      return { headerSetting: { fixed: value } };
+
     case HandlerEnum.HEADER_SHOW:
-      return {
-        headerSetting: {
-          show: value,
-        },
-      };
+      return { headerSetting: { show: value } };
     default:
       return {};
   }

+ 1 - 1
src/layouts/default/setting/index.vue

@@ -10,7 +10,7 @@
   import SettingDrawer from './SettingDrawer';
 
   import { useDrawer } from '/@/components/Drawer';
-  //
+
   export default defineComponent({
     name: 'SettingBtn',
     components: { SettingOutlined, SettingDrawer },

+ 0 - 77
src/layouts/default/sider/LayoutSideBar.tsx

@@ -1,77 +0,0 @@
-import './index.less';
-
-import { computed, defineComponent, ref, unref } from 'vue';
-
-import { Layout } from 'ant-design-vue';
-import LayoutMenu from '/@/layouts/default/menu/LayoutMenu';
-
-import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
-
-import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
-import { useTrigger, useDragLine, useSiderEvent } from './useLayoutSider';
-
-export default defineComponent({
-  name: 'LayoutSideBar',
-  setup() {
-    const dragBarRef = ref<Nullable<HTMLDivElement>>(null);
-    const sideRef = ref<Nullable<HTMLDivElement>>(null);
-
-    const { getCollapsed, getMenuWidth, getSplit, getTheme } = useMenuSetting();
-
-    const { getTriggerAttr, getTriggerSlot } = useTrigger();
-
-    const { renderDragLine } = useDragLine(sideRef, dragBarRef);
-
-    const {
-      getCollapsedWidth,
-      onBreakpointChange,
-      onCollapseChange,
-      onSiderClick,
-    } = useSiderEvent();
-
-    const getMode = computed(() => {
-      return unref(getSplit) ? MenuModeEnum.INLINE : null;
-    });
-
-    const getSplitType = computed(() => {
-      return unref(getSplit) ? MenuSplitTyeEnum.LEFT : MenuSplitTyeEnum.NONE;
-    });
-
-    function renderDefault() {
-      return (
-        <>
-          <LayoutMenu
-            theme={unref(getTheme)}
-            menuMode={unref(getMode)}
-            splitType={unref(getSplitType)}
-          />
-          {renderDragLine()}
-        </>
-      );
-    }
-
-    return () => {
-      return (
-        <Layout.Sider
-          ref={sideRef}
-          class="layout-sidebar"
-          breakpoint="md"
-          collapsible
-          width={unref(getMenuWidth)}
-          collapsed={unref(getCollapsed)}
-          collapsedWidth={unref(getCollapsedWidth)}
-          theme={unref(getTheme)}
-          onClick={onSiderClick}
-          onCollapse={onCollapseChange}
-          onBreakpoint={onBreakpointChange}
-          {...unref(getTriggerAttr)}
-        >
-          {{
-            ...unref(getTriggerSlot),
-            default: () => renderDefault(),
-          }}
-        </Layout.Sider>
-      );
-    };
-  },
-});

+ 9 - 1
src/layouts/default/sider/index.less

@@ -1,7 +1,14 @@
 @import (reference) '../../../design/index.less';
 
 .layout-sidebar {
-  background-size: 100% 100%;
+  overflow: hidden;
+
+  &.fixed {
+    position: fixed;
+    top: 0;
+    left: 0;
+    height: 100%;
+  }
 
   &.ant-layout-sider-dark {
     background: @sider-dark-bg-color;
@@ -9,6 +16,7 @@
 
   &:not(.ant-layout-sider-dark) {
     border-right: 1px solid @border-color-light;
+    box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
   }
 
   .ant-layout-sider-zero-width-trigger {

+ 150 - 0
src/layouts/default/sider/index.tsx

@@ -0,0 +1,150 @@
+import './index.less';
+
+import { computed, defineComponent, ref, unref, watch, nextTick } from 'vue';
+
+import { Layout } from 'ant-design-vue';
+import LayoutMenu from '../menu';
+
+import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
+
+import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
+import { useTrigger, useDragLine, useSiderEvent } from './useLayoutSider';
+import { useLayoutContext } from '../useLayoutContext';
+
+export default defineComponent({
+  name: 'LayoutSideBar',
+  setup() {
+    const topRef = ref(0);
+    const dragBarRef = ref<ElRef>(null);
+    const sideRef = ref<ElRef>(null);
+
+    const {
+      getCollapsed,
+      getMenuWidth,
+      getSplit,
+      getMenuTheme,
+      getRealWidth,
+      getMenuHidden,
+      getMenuFixed,
+    } = useMenuSetting();
+
+    const { getShowFullHeaderRef, getUnFixedAndFull } = useHeaderSetting();
+
+    const injectValue = useLayoutContext();
+
+    const { getTriggerAttr, getTriggerSlot } = useTrigger();
+
+    const { renderDragLine } = useDragLine(sideRef, dragBarRef);
+
+    const {
+      getCollapsedWidth,
+      onBreakpointChange,
+      onCollapseChange,
+      onSiderClick,
+    } = useSiderEvent();
+
+    const getMode = computed(() => {
+      return unref(getSplit) ? MenuModeEnum.INLINE : null;
+    });
+
+    const getSplitType = computed(() => {
+      return unref(getSplit) ? MenuSplitTyeEnum.LEFT : MenuSplitTyeEnum.NONE;
+    });
+
+    const showClassSideBarRef = computed(() => {
+      return unref(getSplit) ? unref(getMenuHidden) : true;
+    });
+
+    const getSiderClass = computed(() => {
+      return {
+        'layout-sidebar': true,
+        fixed: unref(getMenuFixed),
+        hidden: !unref(showClassSideBarRef),
+      };
+    });
+
+    const getSiderStyle = computed(() => {
+      const top = `${unref(topRef)}px`;
+      if (!unref(getMenuFixed)) {
+        return { top };
+      }
+      return {
+        top,
+        height: `calc(100% - ${top})`,
+      };
+    });
+
+    watch(
+      () => getShowFullHeaderRef.value,
+      () => {
+        topRef.value = 0;
+        if (unref(getUnFixedAndFull)) return;
+        nextTick(() => {
+          const fullHeaderEl = unref(injectValue.fullHeaderRef)?.$el;
+          if (!fullHeaderEl) return;
+          topRef.value = fullHeaderEl.offsetHeight;
+        });
+      },
+      {
+        immediate: true,
+      }
+    );
+
+    const getHiddenDomStyle = computed(() => {
+      const width = `${unref(getRealWidth)}px`;
+      return {
+        width: width,
+        overflow: 'hidden',
+        flex: `0 0 ${width}`,
+        'max-width': width,
+        'min-width': width,
+        transition: 'all 0.2s',
+      };
+    });
+
+    function renderDefault() {
+      return (
+        <>
+          <LayoutMenu
+            theme={unref(getMenuTheme)}
+            menuMode={unref(getMode)}
+            splitType={unref(getSplitType)}
+          />
+          {renderDragLine()}
+        </>
+      );
+    }
+
+    return () => {
+      return (
+        <>
+          {unref(getMenuFixed) && (
+            <div style={unref(getHiddenDomStyle)} class={{ hidden: !unref(showClassSideBarRef) }} />
+          )}
+
+          <Layout.Sider
+            ref={sideRef}
+            breakpoint="md"
+            collapsible
+            class={unref(getSiderClass)}
+            style={unref(getSiderStyle)}
+            width={unref(getMenuWidth)}
+            collapsed={unref(getCollapsed)}
+            collapsedWidth={unref(getCollapsedWidth)}
+            theme={unref(getMenuTheme)}
+            onClick={onSiderClick}
+            onCollapse={onCollapseChange}
+            onBreakpoint={onBreakpointChange}
+            {...unref(getTriggerAttr)}
+          >
+            {{
+              ...unref(getTriggerSlot),
+              default: () => renderDefault(),
+            }}
+          </Layout.Sider>
+        </>
+      );
+    };
+  },
+});

+ 2 - 2
src/layouts/default/sider/useLayoutSider.tsx

@@ -16,7 +16,7 @@ export function useSiderEvent() {
   const brokenRef = ref(false);
   const collapseRef = ref(true);
 
-  const { setMenuSetting, getCollapsed, getMiniWidthNumber, getShow } = useMenuSetting();
+  const { setMenuSetting, getCollapsed, getMiniWidthNumber, getShowMenu } = useMenuSetting();
 
   const getCollapsedWidth = computed(() => {
     return unref(brokenRef) ? 0 : unref(getMiniWidthNumber);
@@ -38,7 +38,7 @@ export function useSiderEvent() {
 
   function onSiderClick(e: ChangeEvent) {
     if (!e || !e.target || e.target.className !== 'basic-menu__content') return;
-    if (!unref(getCollapsed) || !unref(getShow)) return;
+    if (!unref(getCollapsed) || !unref(getShowMenu)) return;
     setMenuSetting({ collapsed: false });
   }
   return { getCollapsedWidth, onCollapseChange, onBreakpointChange, onSiderClick };

+ 16 - 0
src/layouts/default/useLayoutContext.ts

@@ -0,0 +1,16 @@
+import { InjectionKey, Ref } from 'vue';
+import { createContext, useContext } from '/@/hooks/core/useContext';
+
+export interface LayoutContextProps {
+  fullHeaderRef: Ref<ComponentRef>;
+}
+
+const layoutContextInjectKey: InjectionKey<LayoutContextProps> = Symbol();
+
+export function createLayoutContext(context: LayoutContextProps) {
+  return createContext<LayoutContextProps>(context, layoutContextInjectKey);
+}
+
+export function useLayoutContext() {
+  return useContext<LayoutContextProps>(layoutContextInjectKey);
+}

+ 2 - 2
src/layouts/iframe/useFrameKeepAlive.ts

@@ -12,7 +12,7 @@ import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
 
 export function useFrameKeepAlive() {
   const { currentRoute } = useRouter();
-  const { getShow } = useMultipleTabSetting();
+  const { getShowMultipleTab } = useMultipleTabSetting();
 
   const getFramePages = computed(() => {
     const ret =
@@ -49,7 +49,7 @@ export function useFrameKeepAlive() {
   }
 
   function hasRenderFrame(path: string) {
-    return unref(getShow) ? unref(getOpenTabList).includes(path) : true;
+    return unref(getShowMultipleTab) ? unref(getOpenTabList).includes(path) : true;
   }
   return { hasRenderFrame, getFramePages, showIframe, getAllFramePages };
 }

+ 2 - 2
src/layouts/page/index.tsx

@@ -20,7 +20,7 @@ interface DefaultContext {
 export default defineComponent({
   name: 'PageLayout',
   setup() {
-    const { getShow } = useMenuSetting();
+    const { getShowMenu } = useMenuSetting();
     const {
       getOpenKeepAlive,
       getRouterTransition,
@@ -32,7 +32,7 @@ export default defineComponent({
 
     const transitionEvent = useTransition();
 
-    const openCacheRef = computed(() => unref(getOpenKeepAlive) && unref(getShow));
+    const openCacheRef = computed(() => unref(getOpenKeepAlive) && unref(getShowMenu));
 
     const getCacheTabsRef = computed(() => tabStore.getKeepAliveTabsState as string[]);
 

+ 10 - 8
src/settings/projectSetting.ts

@@ -21,12 +21,6 @@ const setting: ProjectConfig = {
   // TODO 主题色
   themeColor: primaryColor,
 
-  // header bg color
-  headerBgColor: '#ffffff',
-
-  // sidebar menu bg color
-  menuBgColor: '#273352',
-
   // Whether to show the configuration button
   showSettingButton: true,
 
@@ -48,8 +42,13 @@ const setting: ProjectConfig = {
   // 是否显示logo
   showLogo: true,
 
+  // 是否显示页脚
+  showFooter: true,
+
   // 头部配置
   headerSetting: {
+    // header bg color
+    bgColor: '#ffffff',
     fixed: true,
     // 是否显示顶部
     show: true,
@@ -69,6 +68,10 @@ const setting: ProjectConfig = {
 
   // 菜单配置
   menuSetting: {
+    // sidebar menu bg color
+    bgColor: '#273352',
+
+    fixed: true,
     // 菜单折叠
     collapsed: false,
     // 折叠菜单时候是否显示菜单名
@@ -107,8 +110,7 @@ const setting: ProjectConfig = {
     show: true,
     // 开启快速操作
     showQuick: true,
-    // 显示icon
-    showIcon: false,
+
     // 标签页缓存最大数量
     max: 12,
   },

+ 2 - 0
src/settings/siteSetting.ts

@@ -2,3 +2,5 @@
 export const GITHUB_URL = 'https://github.com/anncwb/vue-vben-admin';
 // vue-vben-admin-next-doc
 export const DOC_URL = 'https://vvbin.cn/doc-next/';
+// site url
+export const SITE_URL = 'https://vvbin.cn/next/';

+ 7 - 2
src/setup/App.ts

@@ -53,7 +53,12 @@ export function initAppConfigStore() {
   if (!projCfg) {
     projCfg = projectSetting;
   }
-  const { colorWeak, grayMode, headerBgColor, menuBgColor } = projCfg;
+  const {
+    colorWeak,
+    grayMode,
+    headerSetting: { bgColor: headerBgColor },
+    menuSetting: { bgColor },
+  } = projCfg;
   try {
     // if (
     //   themeColor !== primaryColor &&
@@ -63,7 +68,7 @@ export function initAppConfigStore() {
     //   updateTheme(themeColor);
     // }
     headerBgColor && updateHeaderBgColor(headerBgColor);
-    menuBgColor && updateSidebarBgColor(menuBgColor);
+    bgColor && updateSidebarBgColor(bgColor);
     grayMode && updateGrayMode(grayMode);
     colorWeak && updateColorWeak(colorWeak);
   } catch (error) {

+ 6 - 6
src/types/config.d.ts

@@ -4,6 +4,8 @@ import { ContentEnum, PermissionModeEnum, ThemeEnum, RouterTransitionEnum } from
 import type { LocaleType } from '/@/locales/types';
 
 export interface MenuSetting {
+  bgColor: string;
+  fixed: boolean;
   collapsed: boolean;
   collapsedShowTitle: boolean;
   hasDrag: boolean;
@@ -26,13 +28,13 @@ export interface MultiTabsSetting {
   show: boolean;
   // 开启快速操作
   showQuick: boolean;
-  // 显示icon
-  showIcon: boolean;
+
   // 缓存最大数量
   max: number;
 }
 
 export interface HeaderSetting {
+  bgColor: string;
   fixed: boolean;
   show: boolean;
   theme: ThemeEnum;
@@ -59,10 +61,7 @@ export interface LocaleSetting {
 
 export interface ProjectConfig {
   locale: LocaleSetting;
-  // header背景色
-  headerBgColor: string;
-  // 左侧菜单背景色
-  menuBgColor: string;
+
   // 是否显示配置按钮
   showSettingButton: boolean;
   // 权限模式
@@ -79,6 +78,7 @@ export interface ProjectConfig {
   contentMode: ContentEnum;
   // 是否显示logo
   showLogo: boolean;
+  showFooter: boolean;
   headerSetting: HeaderSetting;
   // 菜单类型
   // menuType: MenuTypeEnum;

+ 8 - 0
src/types/global.d.ts

@@ -55,3 +55,11 @@ declare type TargetContext = '_self' | '_blank';
 declare type TimeoutHandle = ReturnType<typeof setTimeout>;
 
 declare type IntervalHandle = ReturnType<typeof setInterval>;
+
+declare interface ComponentElRef<T extends HTMLElement = HTMLDivElement> {
+  $el: T;
+}
+
+declare type ComponentRef<T extends HTMLElement = HTMLDivElement> = ComponentElRef<T> | null;
+
+declare type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T>;

+ 0 - 20
yarn.lock

@@ -1737,21 +1737,6 @@
     vscode-languageserver-textdocument "^1.0.1"
     vscode-uri "^2.1.2"
 
-"@vueuse/core@^4.0.0-rc.3":
-  version "4.0.0-rc.3"
-  resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0-rc.3.tgz#5381ca657e10df596cd7027fc5c96b2d4b3a090c"
-  integrity sha512-dQ/FZgo0z7kBFOvDWxuzaUrmuO8X1AlQk17e3PU1TVtG2Uu+mCvjPNbuvI2fjhTjl5rzPJawwoU2WZFj+nlFvw==
-  dependencies:
-    "@vueuse/shared" "4.0.0-rc.3"
-    vue-demi latest
-
-"@vueuse/shared@4.0.0-rc.3":
-  version "4.0.0-rc.3"
-  resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0-rc.3.tgz#42fb56fed3779f3b8a17a82c16a364bad20d01b7"
-  integrity sha512-VY0x/XxpeTMHp/0FDiv1cgUUxkJGQl7liiM2AjR/J7+Ys/2Y2dijD5cAKViq9FGUPQQsOcLptMvMvUsDMoN4DA==
-  dependencies:
-    vue-demi latest
-
 JSONStream@^1.0.4:
   version "1.3.5"
   resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
@@ -8177,11 +8162,6 @@ vscode-uri@^2.1.2:
   resolved "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz#c8d40de93eb57af31f3c715dd650e2ca2c096f1c"
   integrity sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==
 
-vue-demi@latest:
-  version "0.4.3"
-  resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.4.3.tgz#6aaa9b52f02c32b4f9d4d11f02a1ae71031453c3"
-  integrity sha512-1DzLcZgHC9ZyFEYR4qZ83TdS1u9DglG8XVesBXqtbbmqFuO7sb8KG36kMfZCszieAweRDwAAVSAzjmEMG0+WwA==
-
 vue-eslint-parser@^7.1.1:
   version "7.1.1"
   resolved "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz#c43c1c715ff50778b9a7e9a4e16921185f3425d3"