vben
2021-01-06 0e7c57bd5ecafd8283bcc950b24bb63b59b70e5a
feat(tabs): added tab folding
2个文件已添加
28个文件已修改
446 ■■■■■ 已修改文件
CHANGELOG.zh_CN.md 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/_createProductionServer.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Icon/src/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Menu/src/useOpenKeys.ts 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Modal/src/BasicModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Modal/src/components/ModalWrapper.vue 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Modal/src/index.less 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Scrollbar/src/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/src/components/settings/ColumnSetting.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/src/const.ts 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/hooks/setting/useMenuSetting.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/hooks/setting/useMultipleTabSetting.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/hooks/web/useTabs.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/default/setting/SettingDrawer.tsx 77 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/default/setting/enum.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/default/setting/handler.ts 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/default/sider/MixSider.vue 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/default/tabs/components/FoldButton.vue 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/default/tabs/index.less 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/default/tabs/index.vue 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/default/tabs/useTabDropdown.ts 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/locales/lang/en/layout/multipleTab.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/locales/lang/en/layout/setting.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/locales/lang/zh_CN/layout/multipleTab.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/locales/lang/zh_CN/layout/setting.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/settings/componentSetting.ts 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/settings/encryptionSetting.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/settings/projectSetting.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/config.d.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/demo/page/desc/high/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
CHANGELOG.zh_CN.md
@@ -6,6 +6,7 @@
- 新增`mixSideFixed`配置。用于固定左侧混合模式菜单
- modal 组件新增`height`和`min-height`属性
- 新增`PageWrapper`组件。并应用于示例页面
- 新增标签页折叠功能
### 🐛 Bug Fixes
mock/_createProductionServer.ts
@@ -2,10 +2,18 @@
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,
  ]);
}
src/components/Icon/src/index.vue
@@ -74,7 +74,7 @@
        }
      );
      // watch(() => props.icon, update, { flush: 'post' });
      watch(() => props.icon, update, { flush: 'post' });
      onMounted(update);
src/components/Menu/src/useOpenKeys.ts
@@ -16,16 +16,20 @@
  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,
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),
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>
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;
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],
src/components/Table/src/components/settings/ColumnSetting.vue
@@ -421,7 +421,7 @@
        // flex-wrap: wrap;
      }
      .scroll-container {
      .scrollbar {
        height: 220px;
      }
    }
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 const DEFAULT_SORT_FN = defaultSortFn;
export function DEFAULT_FILTER_FN(data: Partial<Recordable<string[]>>) {
  return data;
}
export const DEFAULT_FILTER_FN = defaultFilterFn;
//  表格单元格默认布局
export const DEFAULT_ALIGN = 'center';
src/hooks/setting/useMenuSetting.ts
@@ -78,7 +78,9 @@
});
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 @@
      ? SIDE_BAR_SHOW_TIT_MINI_WIDTH +
        (unref(getMixSideFixed) && unref(mixSideHasChildren) ? unref(getRealWidth) : 0)
      : unref(getRealWidth);
  return `calc(100% - ${unref(width)}px)`;
});
src/hooks/setting/useMultipleTabSetting.ts
@@ -12,6 +12,8 @@
const getShowRedo = computed(() => unref(getMultipleTabSetting).showRedo);
const getShowFold = computed(() => unref(getMultipleTabSetting).showFold);
function setMultipleTabSetting(multiTabsSetting: Partial<MultiTabsSetting>) {
  appStore.commitProjectConfigState({ multiTabsSetting });
}
@@ -24,5 +26,6 @@
    getShowMultipleTab,
    getShowQuick,
    getShowRedo,
    getShowFold,
  };
}
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 @@
    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),
  };
}
src/layouts/default/setting/SettingDrawer.tsx
@@ -85,7 +85,7 @@
      getShowSearch,
    } = useHeaderSetting();
    const { getShowMultipleTab, getShowQuick, getShowRedo } = useMultipleTabSetting();
    const { getShowMultipleTab, getShowQuick, getShowRedo, getShowFold } = useMultipleTabSetting();
    const getShowMenuRef = computed(() => {
      return unref(getShowMenu) && !unref(getIsHorizontal);
@@ -104,33 +104,6 @@
              });
            }}
            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 @@
      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 @@
            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
@@ -212,6 +206,13 @@
            event={HandlerEnum.MENU_FIXED}
            def={unref(getMenuFixed)}
            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')}
@@ -299,6 +300,12 @@
            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')}
src/layouts/default/setting/enum.ts
@@ -39,6 +39,7 @@
  TABS_SHOW_QUICK,
  TABS_SHOW_REDO,
  TABS_SHOW,
  TABS_SHOW_FOLD,
  LOCK_TIME,
  FULL_CONTENT,
src/layouts/default/setting/handler.ts
@@ -71,7 +71,7 @@
      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 @@
    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);
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;
        }
      }
@@ -407,6 +420,16 @@
    > .scrollbar {
      height: calc(100% - @header-height) !important;
    }
    &.mini &-module {
      &__name {
        display: none;
      }
      &__icon {
        margin-bottom: 0;
      }
    }
    &-module {
@@ -456,7 +479,6 @@
    &-menu-list {
      position: fixed;
      top: 0;
      left: 80px;
      width: 0;
      width: 200px;
      height: calc(100%);
src/layouts/default/tabs/components/FoldButton.vue
New file
@@ -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>
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;
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,
      };
    },
  });
src/layouts/default/tabs/useTabDropdown.ts
@@ -8,8 +8,6 @@
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 @@
  const { currentRoute } = router;
  const { getShowMenu, setMenuSetting } = useMenuSetting();
  const { getShowHeader, setHeaderSetting } = useHeaderSetting();
  const isTabs = computed(() => tabContentProps.type === TabContentEnum.TAB_TYPE);
  const getCurrentTab = computed(
@@ -31,10 +26,6 @@
      return unref(isTabs) ? tabContentProps.tabItem : unref(currentRoute);
    }
  );
  const getIsScale = computed(() => {
    return !unref(getShowMenu) && !unref(getShowHeader);
  });
  /**
   * @description: drop-down list
@@ -98,16 +89,6 @@
      },
    ];
    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 @@
    };
  }
  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 @@
        break;
      // Close current
      case MenuEventEnum.CLOSE_CURRENT:
        closeCurrent();
        close(tabContentProps.tabItem);
        break;
      // Close left
      case MenuEventEnum.CLOSE_LEFT:
src/locales/lang/en/layout/multipleTab.ts
@@ -5,7 +5,5 @@
  closeRight: 'Close Right',
  closeOther: 'Close Other',
  closeAll: 'Close All',
  putAway: 'PutAway',
  unfold: 'Unfold',
  tooltipRedo: 'Refresh',
};
src/locales/lang/en/layout/setting.ts
@@ -56,6 +56,7 @@
  tabs: 'Tabs',
  tabsQuickBtn: 'Tabs quick button',
  tabsRedoBtn: 'Tabs redo button',
  tabsFoldBtn: 'Tabs flod button',
  sidebar: 'Sidebar',
  header: 'Header',
  footer: 'Footer',
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: '刷新',
};
src/locales/lang/zh_CN/layout/setting.ts
@@ -55,6 +55,7 @@
  tabs: '标签页',
  tabsQuickBtn: '标签页快捷按钮',
  tabsRedoBtn: '标签页刷新按钮',
  tabsFoldBtn: '标签页折叠按钮',
  sidebar: '左侧菜单',
  header: '顶栏',
  footer: '页脚',
src/settings/componentSetting.ts
New file
@@ -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,
  },
};
src/settings/encryptionSetting.ts
@@ -5,8 +5,8 @@
// aes encryption key
export const cacheCipher = {
  key: '_12345678901234@',
  iv: '@12345678901234_',
  key: '_11111000001111@',
  iv: '@11111000001111_',
};
// Whether the system cache is encrypted using aes
src/settings/projectSetting.ts
@@ -125,6 +125,8 @@
    // Whether to show the refresh button
    showRedo: true,
    // Whether to show the collapse button
    showFold: true,
  },
  // Transition Setting
src/types/config.d.ts
@@ -33,6 +33,9 @@
  // 显示刷新按钮
  showRedo: boolean;
  // 显示折叠按钮
  showFold: boolean;
}
export interface HeaderSetting {
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>