소스 검색

feat(tabs): added tab folding

vben 4 년 전
부모
커밋
0e7c57bd5e

+ 1 - 0
CHANGELOG.zh_CN.md

@@ -6,6 +6,7 @@
 - 新增`mixSideFixed`配置。用于固定左侧混合模式菜单
 - modal 组件新增`height`和`min-height`属性
 - 新增`PageWrapper`组件。并应用于示例页面
+- 新增标签页折叠功能
 
 ### 🐛 Bug Fixes
 

+ 9 - 1
mock/_createProductionServer.ts

@@ -2,10 +2,18 @@ import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
 import userMock from './sys/user';
 import menuMock from './sys/menu';
 import tableDemoMock from './demo/table-demo';
+import accountDemoMock from './demo/account';
+import selectDemoMock from './demo/select-demo';
 
 /**
  * Used in a production environment. Need to manually import all modules
  */
 export function setupProdMockServer() {
-  createProdMockServer([...userMock, ...menuMock, ...tableDemoMock]);
+  createProdMockServer([
+    ...userMock,
+    ...menuMock,
+    ...tableDemoMock,
+    ...accountDemoMock,
+    ...selectDemoMock,
+  ]);
 }

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

@@ -74,7 +74,7 @@
         }
       );
 
-      // watch(() => props.icon, update, { flush: 'post' });
+      watch(() => props.icon, update, { flush: 'post' });
 
       onMounted(update);
 

+ 6 - 2
src/components/Menu/src/useOpenKeys.ts

@@ -16,16 +16,20 @@ export function useOpenKeys(
   mode: Ref<MenuModeEnum>,
   accordion: Ref<boolean>
 ) {
-  const { getCollapsed, getIsMixSidebar, getMixSideFixed } = useMenuSetting();
+  const { getCollapsed, getIsMixSidebar } = useMenuSetting();
 
   async function setOpenKeys(path: string) {
     if (mode.value === MenuModeEnum.HORIZONTAL) {
       return;
     }
-    const native = unref(getIsMixSidebar) && unref(getMixSideFixed);
+    const native = unref(getIsMixSidebar);
     useTimeoutFn(
       () => {
         const menuList = toRaw(menus.value);
+        if (menuList?.length === 0) {
+          menuState.openKeys = [];
+          return;
+        }
         if (!unref(accordion)) {
           menuState.openKeys = es6Unique([
             ...menuState.openKeys,

+ 0 - 2
src/components/Modal/src/BasicModal.vue

@@ -51,7 +51,6 @@
     watchEffect,
     toRef,
     getCurrentInstance,
-    nextTick,
   } from 'vue';
 
   import Modal from './components/Modal';
@@ -111,7 +110,6 @@
             visible: unref(visibleRef),
             title: undefined,
           };
-
           return {
             ...opt,
             wrapClassName: unref(getWrapClassName),

+ 3 - 12
src/components/Modal/src/components/ModalWrapper.vue

@@ -1,5 +1,5 @@
 <template>
-  <ScrollContainer ref="wrapperRef" :style="wrapStyle">
+  <ScrollContainer ref="wrapperRef">
     <div ref="spinRef" :style="spinStyle" v-loading="loading" :loading-tip="loadingTip">
       <slot />
     </div>
@@ -62,19 +62,10 @@
         redoModalHeight: setModalHeight,
       });
 
-      const wrapStyle = computed(
-        (): CSSProperties => {
-          return {
-            minHeight: `${props.minHeight}px`,
-            height: `${unref(realHeightRef)}px`,
-            // overflow: 'auto',
-          };
-        }
-      );
-
       const spinStyle = computed(
         (): CSSProperties => {
           return {
+            minHeight: `${props.minHeight}px`,
             // padding 28
             height: `${unref(realHeightRef) - 28}px`,
           };
@@ -159,7 +150,7 @@
         }
       }
 
-      return { wrapStyle, wrapperRef, spinRef, spinStyle };
+      return { wrapperRef, spinRef, spinStyle };
     },
   });
 </script>

+ 1 - 4
src/components/Modal/src/index.less

@@ -21,12 +21,9 @@
   width: 520px;
   padding-bottom: 0;
 
-  .scroll-container {
+  .scrollbar {
     padding: 14px;
   }
-  // .ant-spin-nested-loading {
-  //   padding: 16px;
-  // }
 
   &-title {
     font-size: 16px;

+ 3 - 2
src/components/Scrollbar/src/index.vue

@@ -18,7 +18,8 @@
 </template>
 <script lang="ts">
   import { addResizeListener, removeResizeListener } from '/@/utils/event/resizeEvent';
-
+  import componentSetting from '/@/settings/componentSetting';
+  const { scrollbar } = componentSetting;
   import { toObject } from './util';
   import {
     defineComponent,
@@ -38,7 +39,7 @@
     props: {
       native: {
         type: Boolean,
-        default: false,
+        default: scrollbar?.native ?? false,
       },
       wrapStyle: {
         type: [String, Array],

+ 1 - 1
src/components/Table/src/components/settings/ColumnSetting.vue

@@ -421,7 +421,7 @@
         // flex-wrap: wrap;
       }
 
-      .scroll-container {
+      .scrollbar {
         height: 220px;
       }
     }

+ 11 - 27
src/components/Table/src/const.ts

@@ -1,40 +1,24 @@
-import type { SorterResult } from './types/table';
+import componentSetting from '/@/settings/componentSetting';
+
+const { table } = componentSetting;
+
+const { pageSizeOptions, defaultPageSize, fetchSetting, defaultSortFn, defaultFilterFn } = table;
 
 export const ROW_KEY = 'key';
 
 // 可选的每页显示条数;
-export const PAGE_SIZE_OPTIONS = ['10', '50', '80', '100'];
+export const PAGE_SIZE_OPTIONS = pageSizeOptions;
 
 // 每页显示条数
-export const PAGE_SIZE = ~~PAGE_SIZE_OPTIONS[0];
+export const PAGE_SIZE = defaultPageSize;
 
 // 通用接口字段设置
-// 支持 xxx.xxx.xxx格式
-export const FETCH_SETTING = {
-  // 传给后台的当前页字段名
-  pageField: 'page',
-  // 传给后台的每页显示记录数字段名
-  sizeField: 'pageSize',
-  // 接口返回的表格数据字段名
-  listField: 'items',
-  // 接口返回的表格总数字段名
-  totalField: 'total',
-};
+export const FETCH_SETTING = fetchSetting;
 
 // 配置通用排序函数
-export function DEFAULT_SORT_FN(sortInfo: SorterResult) {
-  const { field, order } = sortInfo;
-  return {
-    // 传给后台的排序字段你
-    field,
-    // 传给后台的排序方式  asc/desc
-    order,
-  };
-}
-
-export function DEFAULT_FILTER_FN(data: Partial<Recordable<string[]>>) {
-  return data;
-}
+export const DEFAULT_SORT_FN = defaultSortFn;
+
+export const DEFAULT_FILTER_FN = defaultFilterFn;
 
 //  表格单元格默认布局
 export const DEFAULT_ALIGN = 'center';

+ 3 - 2
src/hooks/setting/useMenuSetting.ts

@@ -78,7 +78,9 @@ const getIsMixMode = computed(() => {
 });
 
 const getRealWidth = computed(() => {
-  return unref(getCollapsed) ? unref(getMiniWidthNumber) : unref(getMenuWidth);
+  return unref(getCollapsed) && !unref(getMixSideFixed)
+    ? unref(getMiniWidthNumber)
+    : unref(getMenuWidth);
 });
 
 const getMiniWidthNumber = computed(() => {
@@ -94,7 +96,6 @@ const getCalcContentWidth = computed(() => {
       ? SIDE_BAR_SHOW_TIT_MINI_WIDTH +
         (unref(getMixSideFixed) && unref(mixSideHasChildren) ? unref(getRealWidth) : 0)
       : unref(getRealWidth);
-
   return `calc(100% - ${unref(width)}px)`;
 });
 

+ 3 - 0
src/hooks/setting/useMultipleTabSetting.ts

@@ -12,6 +12,8 @@ const getShowQuick = computed(() => unref(getMultipleTabSetting).showQuick);
 
 const getShowRedo = computed(() => unref(getMultipleTabSetting).showRedo);
 
+const getShowFold = computed(() => unref(getMultipleTabSetting).showFold);
+
 function setMultipleTabSetting(multiTabsSetting: Partial<MultiTabsSetting>) {
   appStore.commitProjectConfigState({ multiTabsSetting });
 }
@@ -24,5 +26,6 @@ export function useMultipleTabSetting() {
     getShowMultipleTab,
     getShowQuick,
     getShowRedo,
+    getShowFold,
   };
 }

+ 3 - 0
src/hooks/web/useTabs.ts

@@ -1,5 +1,6 @@
 import { tabStore } from '/@/store/modules/tab';
 import { appStore } from '/@/store/modules/app';
+import type { RouteLocationNormalized } from 'vue-router';
 
 export function useTabs() {
   function canIUseFn(): boolean {
@@ -21,5 +22,7 @@ export function useTabs() {
     closeRight: () => canIUseFn() && tabStore.closeRightTabAction(tabStore.getCurrentTab),
     closeOther: () => canIUseFn() && tabStore.closeOtherTabAction(tabStore.getCurrentTab),
     closeCurrent: () => canIUseFn() && tabStore.closeTabAction(tabStore.getCurrentTab),
+    close: (tab?: RouteLocationNormalized) =>
+      canIUseFn() && tabStore.closeTabAction(tab || tabStore.getCurrentTab),
   };
 }

+ 42 - 35
src/layouts/default/setting/SettingDrawer.tsx

@@ -85,7 +85,7 @@ export default defineComponent({
       getShowSearch,
     } = useHeaderSetting();
 
-    const { getShowMultipleTab, getShowQuick, getShowRedo } = useMultipleTabSetting();
+    const { getShowMultipleTab, getShowQuick, getShowRedo, getShowFold } = useMultipleTabSetting();
 
     const getShowMenuRef = computed(() => {
       return unref(getShowMenu) && !unref(getIsHorizontal);
@@ -105,33 +105,6 @@ export default defineComponent({
             }}
             def={unref(getMenuType)}
           />
-          <SwitchItem
-            title={t('layout.setting.splitMenu')}
-            event={HandlerEnum.MENU_SPLIT}
-            def={unref(getSplit)}
-            disabled={!unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX}
-          />
-          <SwitchItem
-            title={t('layout.setting.mixSidebarFixed')}
-            event={HandlerEnum.MENU_FIXED_MIX_SIDEBAR}
-            def={unref(getMixSideFixed)}
-            disabled={!unref(getIsMixSidebar)}
-          />
-
-          <SwitchItem
-            title={t('layout.setting.closeMixSidebarOnChange')}
-            event={HandlerEnum.MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE}
-            def={unref(getCloseMixSidebarOnChange)}
-            disabled={!unref(getIsMixSidebar)}
-          />
-
-          <SelectItem
-            title={t('layout.setting.mixSidebarTrigger')}
-            event={HandlerEnum.MENU_TRIGGER_MIX_SIDEBAR}
-            def={unref(getMixSideTrigger)}
-            options={mixSidebarTriggerOptions}
-            disabled={!unref(getIsMixSidebar)}
-          />
         </>
       );
     }
@@ -171,6 +144,32 @@ export default defineComponent({
       return (
         <>
           <SwitchItem
+            title={t('layout.setting.splitMenu')}
+            event={HandlerEnum.MENU_SPLIT}
+            def={unref(getSplit)}
+            disabled={!unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX}
+          />
+          <SwitchItem
+            title={t('layout.setting.mixSidebarFixed')}
+            event={HandlerEnum.MENU_FIXED_MIX_SIDEBAR}
+            def={unref(getMixSideFixed)}
+            disabled={!unref(getIsMixSidebar)}
+          />
+
+          <SwitchItem
+            title={t('layout.setting.closeMixSidebarOnChange')}
+            event={HandlerEnum.MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE}
+            def={unref(getCloseMixSidebarOnChange)}
+            disabled={!unref(getIsMixSidebar)}
+          />
+          <SwitchItem
+            title={t('layout.setting.menuCollapse')}
+            event={HandlerEnum.MENU_COLLAPSED}
+            def={unref(getCollapsed)}
+            disabled={!unref(getShowMenuRef)}
+          />
+
+          <SwitchItem
             title={t('layout.setting.menuDrag')}
             event={HandlerEnum.MENU_HAS_DRAG}
             def={unref(getCanDrag)}
@@ -188,17 +187,12 @@ export default defineComponent({
             def={unref(getAccordion)}
             disabled={!unref(getShowMenuRef)}
           />
-          <SwitchItem
-            title={t('layout.setting.menuCollapse')}
-            event={HandlerEnum.MENU_COLLAPSED}
-            def={unref(getCollapsed)}
-            disabled={!unref(getShowMenuRef) || unref(getIsMixSidebar)}
-          />
+
           <SwitchItem
             title={t('layout.setting.collapseMenuDisplayName')}
             event={HandlerEnum.MENU_COLLAPSED_SHOW_TITLE}
             def={unref(getCollapsedShowTitle)}
-            disabled={!unref(getShowMenuRef) || !unref(getCollapsed)}
+            disabled={!unref(getShowMenuRef) || !unref(getCollapsed) || unref(getIsMixSidebar)}
           />
 
           <SwitchItem
@@ -214,6 +208,13 @@ export default defineComponent({
             disabled={!unref(getShowMenuRef) || unref(getIsMixSidebar)}
           />
           <SelectItem
+            title={t('layout.setting.mixSidebarTrigger')}
+            event={HandlerEnum.MENU_TRIGGER_MIX_SIDEBAR}
+            def={unref(getMixSideTrigger)}
+            options={mixSidebarTriggerOptions}
+            disabled={!unref(getIsMixSidebar)}
+          />
+          <SelectItem
             title={t('layout.setting.topMenuLayout')}
             event={HandlerEnum.MENU_TOP_ALIGN}
             def={unref(getTopMenuAlign)}
@@ -299,6 +300,12 @@ export default defineComponent({
             def={unref(getShowQuick)}
             disabled={!unref(getShowMultipleTab)}
           />
+          <SwitchItem
+            title={t('layout.setting.tabsFoldBtn')}
+            event={HandlerEnum.TABS_SHOW_FOLD}
+            def={unref(getShowFold)}
+            disabled={!unref(getShowMultipleTab)}
+          />
 
           <SwitchItem
             title={t('layout.setting.sidebar')}

+ 1 - 0
src/layouts/default/setting/enum.ts

@@ -39,6 +39,7 @@ export enum HandlerEnum {
   TABS_SHOW_QUICK,
   TABS_SHOW_REDO,
   TABS_SHOW,
+  TABS_SHOW_FOLD,
 
   LOCK_TIME,
   FULL_CONTENT,

+ 5 - 1
src/layouts/default/setting/handler.ts

@@ -71,7 +71,7 @@ export function handler(event: HandlerEnum, value: any): DeepPartial<ProjectConf
       return { menuSetting: { mixSideTrigger: value } };
 
     case HandlerEnum.MENU_FIXED_MIX_SIDEBAR:
-      return { menuSetting: { mixSideTrigger: value } };
+      return { menuSetting: { mixSideFixed: value } };
 
     // ============transition==================
     case HandlerEnum.OPEN_PAGE_LOADING:
@@ -123,9 +123,13 @@ export function handler(event: HandlerEnum, value: any): DeepPartial<ProjectConf
 
     case HandlerEnum.TABS_SHOW:
       return { multiTabsSetting: { show: value } };
+
     case HandlerEnum.TABS_SHOW_REDO:
       return { multiTabsSetting: { showRedo: value } };
 
+    case HandlerEnum.TABS_SHOW_FOLD:
+      return { multiTabsSetting: { showFold: value } };
+
     // ============header==================
     case HandlerEnum.HEADER_THEME:
       updateHeaderBgColor(value);

+ 60 - 38
src/layouts/default/sider/MixSider.vue

@@ -3,11 +3,13 @@
 
   <div
     v-click-outside="handleClickOutside"
+    :style="getWrapStyle"
     :class="[
       prefixCls,
       getMenuTheme,
       {
         open: openMenu,
+        mini: getCollapsed,
       },
     ]"
     v-bind="getMenuEvents"
@@ -29,7 +31,7 @@
           <MenuTag :item="item" :showTitle="false" :isHorizontal="false" />
           <Icon
             :class="`${prefixCls}-module__icon`"
-            :size="22"
+            :size="getCollapsed ? 16 : 20"
             :icon="item.meta && item.meta.icon"
           />
           <p :class="`${prefixCls}-module__name`">{{ t(item.name) }}</p>
@@ -50,12 +52,10 @@
         <span class="text"> {{ title }}</span>
         <Icon
           :size="16"
-          v-if="getMixSideFixed"
-          icon="ri:pushpin-2-fill"
+          :icon="getMixSideFixed ? 'ri:pushpin-2-fill' : 'ri:pushpin-2-line'"
           class="pushpin"
           @click="handleFixedMenu"
         />
-        <Icon :size="16" v-else icon="ri:pushpin-2-line" class="pushpin" @click="handleFixedMenu" />
       </div>
       <ScrollContainer :class="`${prefixCls}-menu-list__content`">
         <BasicMenu
@@ -92,7 +92,7 @@
   import { useDragLine } from './useLayoutSider';
   import { useGlobSetting } from '/@/hooks/setting';
 
-  import { SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum';
+  import { SIDE_BAR_SHOW_TIT_MINI_WIDTH, SIDE_BAR_MINI_WIDTH } from '/@/enums/appEnum';
 
   import clickOutside from '/@/directives/clickOutside';
 
@@ -130,6 +130,8 @@
         getMixSideFixed,
         mixSideHasChildren,
         setMenuSetting,
+        getIsMixSidebar,
+        getCollapsed,
       } = useMenuSetting();
 
       const { title } = useGlobSetting();
@@ -140,6 +142,7 @@
         (): CSSProperties => {
           return {
             width: unref(openMenu) ? `${unref(getMenuWidth)}px` : 0,
+            left: `${unref(getMixSideWidth)}px`,
           };
         }
       );
@@ -153,32 +156,33 @@
         return isFixed;
       });
 
+      const getMixSideWidth = computed(() => {
+        return unref(getCollapsed) ? SIDE_BAR_MINI_WIDTH : SIDE_BAR_SHOW_TIT_MINI_WIDTH;
+      });
+
       const getDomStyle = computed(
         (): CSSProperties => {
           const fixedWidth = unref(getIsFixed) ? unref(getRealWidth) : 0;
-          const width = `${SIDE_BAR_SHOW_TIT_MINI_WIDTH + fixedWidth}px`;
-          return {
-            width,
-            maxWidth: width,
-            minWidth: width,
-            flex: `0 0 ${width}`,
-          };
+          const width = `${unref(getMixSideWidth) + fixedWidth}px`;
+          return getWrapCommonStyle(width);
+        }
+      );
+
+      const getWrapStyle = computed(
+        (): CSSProperties => {
+          const width = `${unref(getMixSideWidth)}px`;
+          return getWrapCommonStyle(width);
         }
       );
 
       const getMenuEvents = computed(() => {
-        // return unref(getMixSideTrigger) === 'hover'
-        //   ? {
-        //       onMouseleave: () => {
-        //         closeMenu();
-        //       },
-        //     }
-        //   : {};
-        return {
-          onMouseleave: () => {
-            closeMenu();
-          },
-        };
+        return !unref(getMixSideFixed)
+          ? {
+              onMouseleave: () => {
+                closeMenu();
+              },
+            }
+          : {};
       });
 
       const getShowDragBar = computed(() => unref(getCanDrag));
@@ -195,6 +199,16 @@
         }
       });
 
+      function getWrapCommonStyle(width: string): CSSProperties {
+        return {
+          width,
+          maxWidth: width,
+          minWidth: width,
+          flex: `0 0 ${width}`,
+        };
+      }
+
+      // Process module menu click
       async function hanldeModuleClick(path: string, hover = false) {
         const children = await getChildrenMenus(path);
 
@@ -223,20 +237,24 @@
         chilrenMenus.value = children;
       }
 
+      // Set the currently active menu and submenu
       async function setActive(setChildren = false) {
         const path = currentRoute.value?.path;
         if (!path) return;
         const parentPath = await getCurrentParentPath(path);
         activePath.value = parentPath;
         // hanldeModuleClick(parentPath);
-        if (unref(getMixSideFixed)) {
+        if (unref(getIsMixSidebar)) {
           const activeMenu = unref(menuModules).find((item) => item.path === unref(activePath));
           const p = activeMenu?.path;
           if (p) {
             const children = await getChildrenMenus(p);
             if (setChildren) {
               chilrenMenus.value = children;
-              openMenu.value = children.length > 0;
+
+              if (unref(getMixSideFixed)) {
+                openMenu.value = children.length > 0;
+              }
             }
             if (children.length === 0) {
               chilrenMenus.value = [];
@@ -271,6 +289,7 @@
         });
       }
 
+      // Close menu
       function closeMenu() {
         if (!unref(getIsFixed)) {
           openMenu.value = false;
@@ -298,6 +317,8 @@
         getDomStyle,
         handleFixedMenu,
         getMixSideFixed,
+        getWrapStyle,
+        getCollapsed,
       };
     },
   });
@@ -312,14 +333,10 @@
     top: 0;
     left: 0;
     z-index: @layout-mix-sider-fixed-z-index;
-    width: @width;
     height: 100%;
-    max-width: @width;
-    min-width: @width;
     overflow: hidden;
     background: @sider-dark-bg-color;
-    transition: all 0.3s ease 0s;
-    flex: 0 0 @width;
+    transition: all 0.2s ease 0s;
     .@{tag-prefix-cls} {
       position: absolute;
       top: 6px;
@@ -327,13 +344,9 @@
     }
 
     &-dom {
-      width: @width;
       height: 100%;
-      max-width: @width;
-      min-width: @width;
       overflow: hidden;
       transition: all 0.2s ease 0s;
-      flex: 0 0 @width;
     }
 
     &-logo {
@@ -354,7 +367,7 @@
       }
 
       &.open {
-        > .scroll-container {
+        > .scrollbar {
           border-right: 1px solid rgb(238, 238, 238);
         }
       }
@@ -390,7 +403,7 @@
           border-bottom: 1px solid @border-color;
         }
 
-        > .scroll-container {
+        > .scrollbar {
           border-right: 1px solid @border-color;
         }
       }
@@ -409,6 +422,16 @@
       height: calc(100% - @header-height) !important;
     }
 
+    &.mini &-module {
+      &__name {
+        display: none;
+      }
+
+      &__icon {
+        margin-bottom: 0;
+      }
+    }
+
     &-module {
       position: relative;
       padding-top: 1px;
@@ -456,7 +479,6 @@
     &-menu-list {
       position: fixed;
       top: 0;
-      left: 80px;
       width: 0;
       width: 200px;
       height: calc(100%);

+ 47 - 0
src/layouts/default/tabs/components/FoldButton.vue

@@ -0,0 +1,47 @@
+<template>
+  <span :class="`${prefixCls}__extra-fold`" @click="handleFold">
+    <Icon :icon="getIcon" />
+  </span>
+</template>
+<script lang="ts">
+  import { defineComponent, unref, computed } from 'vue';
+  import { RedoOutlined } from '@ant-design/icons-vue';
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { Tooltip } from 'ant-design-vue';
+  import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
+  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+
+  import Icon from '/@/components/Icon';
+
+  export default defineComponent({
+    name: 'FoldButton',
+    components: { RedoOutlined, Tooltip, Icon },
+
+    setup() {
+      const { prefixCls } = useDesign('multiple-tabs-content');
+      const { getShowMenu, setMenuSetting } = useMenuSetting();
+      const { getShowHeader, setHeaderSetting } = useHeaderSetting();
+
+      const getIsUnFold = computed(() => {
+        return !unref(getShowMenu) && !unref(getShowHeader);
+      });
+
+      const getIcon = computed(() => {
+        return unref(getIsUnFold) ? 'codicon:screen-normal' : 'codicon:screen-full';
+      });
+
+      function handleFold() {
+        const isScale = !unref(getShowMenu) && !unref(getShowHeader);
+        setMenuSetting({
+          show: isScale,
+          hidden: !isScale,
+        });
+        setHeaderSetting({
+          show: isScale,
+        });
+      }
+
+      return { prefixCls, getIcon, handleFold };
+    },
+  });
+</script>

+ 2 - 1
src/layouts/default/tabs/index.less

@@ -153,7 +153,8 @@
 
   &-content {
     &__extra-quick,
-    &__extra-redo {
+    &__extra-redo,
+    &__extra-fold {
       display: inline-block;
       width: 36px;
       height: @multiple-height;

+ 4 - 1
src/layouts/default/tabs/index.vue

@@ -21,6 +21,7 @@
       <template #tabBarExtraContent v-if="getShowRedo || getShowQuick">
         <TabRedo v-if="getShowRedo" />
         <QuickButton v-if="getShowQuick" />
+        <FoldButton v-if="getShowFold" />
       </template>
     </Tabs>
   </div>
@@ -51,6 +52,7 @@
     components: {
       QuickButton: createAsyncComponent(() => import('./components/QuickButton.vue')),
       TabRedo: createAsyncComponent(() => import('./components/TabRedo.vue')),
+      FoldButton: createAsyncComponent(() => import('./components/FoldButton.vue')),
       Tabs,
       TabPane: Tabs.TabPane,
       TabContent,
@@ -62,7 +64,7 @@
       useTabsDrag(affixTextList);
       const { prefixCls } = useDesign('multiple-tabs');
       const go = useGo();
-      const { getShowQuick, getShowRedo } = useMultipleTabSetting();
+      const { getShowQuick, getShowRedo, getShowFold } = useMultipleTabSetting();
 
       const getTabsState = computed(() => {
         return tabStore.getTabsState.filter((item) => !item.meta?.hideTab);
@@ -125,6 +127,7 @@
         getTabsState,
         getShowQuick,
         getShowRedo,
+        getShowFold,
       };
     },
   });

+ 2 - 32
src/layouts/default/tabs/useTabDropdown.ts

@@ -8,8 +8,6 @@ import router from '/@/router';
 import { RouteLocationNormalized } from 'vue-router';
 import { useTabs } from '/@/hooks/web/useTabs';
 import { useI18n } from '/@/hooks/web/useI18n';
-import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
-import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
 
 const { t } = useI18n();
 
@@ -21,9 +19,6 @@ export function useTabDropdown(tabContentProps: TabContentProps) {
 
   const { currentRoute } = router;
 
-  const { getShowMenu, setMenuSetting } = useMenuSetting();
-  const { getShowHeader, setHeaderSetting } = useHeaderSetting();
-
   const isTabs = computed(() => tabContentProps.type === TabContentEnum.TAB_TYPE);
 
   const getCurrentTab = computed(
@@ -32,10 +27,6 @@ export function useTabDropdown(tabContentProps: TabContentProps) {
     }
   );
 
-  const getIsScale = computed(() => {
-    return !unref(getShowMenu) && !unref(getShowHeader);
-  });
-
   /**
    * @description: drop-down list
    */
@@ -98,16 +89,6 @@ export function useTabDropdown(tabContentProps: TabContentProps) {
       },
     ];
 
-    if (!unref(isTabs)) {
-      const isScale = unref(getIsScale);
-      dropMenuList.unshift({
-        icon: isScale ? 'codicon:screen-normal' : 'codicon:screen-full',
-        event: MenuEventEnum.SCALE,
-        text: isScale ? t('layout.multipleTab.putAway') : t('layout.multipleTab.unfold'),
-        disabled: false,
-      });
-    }
-
     return dropMenuList;
   });
 
@@ -125,20 +106,9 @@ export function useTabDropdown(tabContentProps: TabContentProps) {
     };
   }
 
-  function scaleScreen() {
-    const isScale = !unref(getShowMenu) && !unref(getShowHeader);
-    setMenuSetting({
-      show: isScale,
-      hidden: !isScale,
-    });
-    setHeaderSetting({
-      show: isScale,
-    });
-  }
-
   // Handle right click event
   function handleMenuEvent(menu: DropMenu): void {
-    const { refreshPage, closeAll, closeCurrent, closeLeft, closeOther, closeRight } = useTabs();
+    const { refreshPage, closeAll, close, closeLeft, closeOther, closeRight } = useTabs();
     const { event } = menu;
     switch (event) {
       case MenuEventEnum.SCALE:
@@ -150,7 +120,7 @@ export function useTabDropdown(tabContentProps: TabContentProps) {
         break;
       // Close current
       case MenuEventEnum.CLOSE_CURRENT:
-        closeCurrent();
+        close(tabContentProps.tabItem);
         break;
       // Close left
       case MenuEventEnum.CLOSE_LEFT:

+ 0 - 2
src/locales/lang/en/layout/multipleTab.ts

@@ -5,7 +5,5 @@ export default {
   closeRight: 'Close Right',
   closeOther: 'Close Other',
   closeAll: 'Close All',
-  putAway: 'PutAway',
-  unfold: 'Unfold',
   tooltipRedo: 'Refresh',
 };

+ 1 - 0
src/locales/lang/en/layout/setting.ts

@@ -56,6 +56,7 @@ export default {
   tabs: 'Tabs',
   tabsQuickBtn: 'Tabs quick button',
   tabsRedoBtn: 'Tabs redo button',
+  tabsFoldBtn: 'Tabs flod button',
   sidebar: 'Sidebar',
   header: 'Header',
   footer: 'Footer',

+ 6 - 8
src/locales/lang/zh_CN/layout/multipleTab.ts

@@ -1,11 +1,9 @@
 export default {
-  redo: '刷新当前',
-  close: '关闭当前',
-  closeLeft: '关闭左侧',
-  closeRight: '关闭右侧',
-  closeOther: '关闭其他',
-  closeAll: '关闭全部',
-  putAway: '收起',
-  unfold: '展开',
+  redo: '重新加载',
+  close: '关闭标签页',
+  closeLeft: '关闭左侧标签页',
+  closeRight: '关闭右侧标签页',
+  closeOther: '关闭其它标签页',
+  closeAll: '关闭全部标签页',
   tooltipRedo: '刷新',
 };

+ 1 - 0
src/locales/lang/zh_CN/layout/setting.ts

@@ -55,6 +55,7 @@ export default {
   tabs: '标签页',
   tabsQuickBtn: '标签页快捷按钮',
   tabsRedoBtn: '标签页刷新按钮',
+  tabsFoldBtn: '标签页折叠按钮',
   sidebar: '左侧菜单',
   header: '顶栏',
   footer: '页脚',

+ 45 - 0
src/settings/componentSetting.ts

@@ -0,0 +1,45 @@
+// Used to configure the general configuration of some components without modifying the components
+
+import type { SorterResult } from '../components/Table';
+
+export default {
+  // basic-table setting
+  table: {
+    // Form interface request general configuration
+    // support xxx.xxx.xxx
+    fetchSetting: {
+      // The field name of the current page passed to the background
+      pageField: 'page',
+      // The number field name of each page displayed in the background
+      sizeField: 'pageSize',
+      // Field name of the form data returned by the interface
+      listField: 'items',
+      // Total number of tables returned by the interface field name
+      totalField: 'total',
+    },
+    // Number of pages that can be selected
+    pageSizeOptions: ['10', '50', '80', '100'],
+    // Default display quantity on one page
+    defaultPageSize: 10,
+    // Custom general sort function
+    defaultSortFn: (sortInfo: SorterResult) => {
+      const { field, order } = sortInfo;
+      return {
+        // The sort field passed to the backend you
+        field,
+        // Sorting method passed to the background asc/desc
+        order,
+      };
+    },
+    // Custom general filter function
+    defaultFilterFn: (data: Partial<Recordable<string[]>>) => {
+      return data;
+    },
+  },
+  // scrollbar setting
+  scrollbar: {
+    // Whether to use native scroll bar
+    // After opening, the menu, modal, drawer will change the pop-up scroll bar to native
+    native: false,
+  },
+};

+ 2 - 2
src/settings/encryptionSetting.ts

@@ -5,8 +5,8 @@ export const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;
 
 // aes encryption key
 export const cacheCipher = {
-  key: '_12345678901234@',
-  iv: '@12345678901234_',
+  key: '_11111000001111@',
+  iv: '@11111000001111_',
 };
 
 // Whether the system cache is encrypted using aes

+ 2 - 0
src/settings/projectSetting.ts

@@ -125,6 +125,8 @@ const setting: ProjectConfig = {
 
     // Whether to show the refresh button
     showRedo: true,
+    // Whether to show the collapse button
+    showFold: true,
   },
 
   // Transition Setting

+ 3 - 0
src/types/config.d.ts

@@ -33,6 +33,9 @@ export interface MultiTabsSetting {
 
   // 显示刷新按钮
   showRedo: boolean;
+
+  // 显示折叠按钮
+  showFold: boolean;
 }
 
 export interface HeaderSetting {

+ 3 - 3
src/views/demo/page/desc/high/index.vue

@@ -1,9 +1,9 @@
 <template>
   <PageWrapper title="单号:234231029431" contentBackgrond>
     <template #extra>
-      <a-button key="3"> 操作一 </a-button>
-      <a-button key="2"> 操作二 </a-button>
-      <a-button key="1" type="primary"> 主操作 </a-button>
+      <a-button> 操作一 </a-button>
+      <a-button> 操作二 </a-button>
+      <a-button type="primary"> 主操作 </a-button>
     </template>
 
     <template #footer>