diff --git a/CHANGELOG.zh_CN.md b/CHANGELOG.zh_CN.md index d2c55b1580b..9a294289130 100644 --- a/CHANGELOG.zh_CN.md +++ b/CHANGELOG.zh_CN.md @@ -2,13 +2,14 @@ ### ✨ Refactor -- 重构整体 layout。更改代码实现方式。代码更精简 +- 重构整体 layout。更改代码实现方式。代码更精简,并加入多语言支持 - 配置项重构 - 移除 messageSetting 配置 ### ✨ Features - 缓存可以配置是否加密,默认生产环境开启 Aes 加密 +- 新增标签页拖拽排序 ### 🎫 Chores diff --git a/package.json b/package.json index 040428a181e..8814eed888a 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@types/nprogress": "^0.2.0", "@types/qrcode": "^1.3.5", "@types/rollup-plugin-visualizer": "^2.6.0", + "@types/sortablejs": "^1.10.6", "@types/yargs": "^15.0.10", "@types/zxcvbn": "^4.4.0", "@typescript-eslint/eslint-plugin": "^4.8.2", diff --git a/src/hooks/setting/useMenuSetting.ts b/src/hooks/setting/useMenuSetting.ts index 2004316d284..f66ef8a3924 100644 --- a/src/hooks/setting/useMenuSetting.ts +++ b/src/hooks/setting/useMenuSetting.ts @@ -33,7 +33,7 @@ export function useMenuSetting() { const getMenuBgColor = computed(() => unref(getMenuSetting).bgColor); - const getHasDrag = computed(() => unref(getMenuSetting).hasDrag); + const getCanDrag = computed(() => unref(getMenuSetting).canDrag); const getAccordion = computed(() => unref(getMenuSetting).accordion); @@ -117,7 +117,7 @@ export function useMenuSetting() { getTrigger, getSplit, getMenuTheme, - getHasDrag, + getCanDrag, getIsHorizontal, getShowSearch, getCollapsedShowTitle, diff --git a/src/hooks/web/useTabs.ts b/src/hooks/web/useTabs.ts index e9cf345e892..775109e4688 100644 --- a/src/hooks/web/useTabs.ts +++ b/src/hooks/web/useTabs.ts @@ -1,14 +1,6 @@ -import { useTimeoutFn } from '/@/hooks/core/useTimeout'; -import { PageEnum } from '/@/enums/pageEnum'; import { TabItem, tabStore } from '/@/store/modules/tab'; import { appStore } from '/@/store/modules/app'; -import router from '/@/router'; -import { ref } from 'vue'; -import { pathToRegexp } from 'path-to-regexp'; -const activeKeyRef = ref(''); - -type Fn = () => void; type RouteFn = (tabItem: TabItem) => void; interface TabFn { @@ -28,6 +20,7 @@ let closeOther: RouteFn; let closeCurrent: RouteFn; export let isInitUseTab = false; + export function useTabs() { function initTabFn({ refreshPageFn, @@ -38,6 +31,7 @@ export function useTabs() { closeCurrentFn, }: TabFn) { if (isInitUseTab) return; + refreshPageFn && (refreshPage = refreshPageFn); closeAllFn && (closeAll = closeAllFn); closeLeftFn && (closeLeft = closeLeftFn); @@ -58,29 +52,13 @@ export function useTabs() { } function canIUseFn(): boolean { - const { getProjectConfig } = appStore; - const { multiTabsSetting: { show } = {} } = getProjectConfig; + const { multiTabsSetting: { show } = {} } = appStore.getProjectConfig; if (!show) { throw new Error('当前未开启多标签页,请在设置中打开!'); } return !!show; } - function getTo(path: string): any { - const routes = router.getRoutes(); - const fn = (p: string): any => { - const to = routes.find((item) => { - if (item.path === '/:path(.*)*') return; - const regexp = pathToRegexp(item.path); - return regexp.test(p); - }); - if (!to) return ''; - if (!to.redirect) return to; - if (to.redirect) { - return getTo(to.redirect as string); - } - }; - return fn(path); - } + return { initTabFn, refreshPage: () => canIUseFn() && refreshPage(tabStore.getCurrentTab), @@ -90,26 +68,5 @@ export function useTabs() { closeOther: () => canIUseFn() && closeOther(tabStore.getCurrentTab), closeCurrent: () => canIUseFn() && closeCurrent(tabStore.getCurrentTab), resetCache: () => canIUseFn() && resetCache(), - addTab: ( - path: PageEnum | string, - goTo = false, - opt?: { replace?: boolean; query?: any; params?: any } - ) => { - const to = getTo(path); - - if (!to) return; - useTimeoutFn(() => { - tabStore.addTabByPathAction(); - }, 0); - const { replace, query = {}, params = {} } = opt || {}; - activeKeyRef.value = path; - const data = { - path, - query, - params, - }; - goTo && replace ? router.replace(data) : router.push(data); - }, - activeKeyRef, }; } diff --git a/src/layouts/default/header/index.less b/src/layouts/default/header/index.less index 8f9bb671d09..60b98ef1364 100644 --- a/src/layouts/default/header/index.less +++ b/src/layouts/default/header/index.less @@ -4,6 +4,7 @@ display: flex; height: @header-height; padding: 0 20px 0 0; + margin-left: -1px; line-height: @header-height; color: @white; background: @white; diff --git a/src/layouts/default/index.less b/src/layouts/default/index.less index 9a933be807a..a8dcf0e8d73 100644 --- a/src/layouts/default/index.less +++ b/src/layouts/default/index.less @@ -9,4 +9,8 @@ > .ant-layout { min-height: 100%; } + + &__main { + margin-left: 2px; + } } diff --git a/src/layouts/default/index.tsx b/src/layouts/default/index.tsx index 4681cba2887..9802ec16e2b 100644 --- a/src/layouts/default/index.tsx +++ b/src/layouts/default/index.tsx @@ -81,7 +81,7 @@ export default defineComponent({ {() => ( <> {unref(showSideBarRef) && } - + {() => ( <> diff --git a/src/layouts/default/multitabs/TabContent.tsx b/src/layouts/default/multitabs/TabContent.tsx index 746b6aa9c66..348729802e7 100644 --- a/src/layouts/default/multitabs/TabContent.tsx +++ b/src/layouts/default/multitabs/TabContent.tsx @@ -1,16 +1,46 @@ -import { defineComponent, unref, computed } from 'vue'; - import type { PropType } from 'vue'; +import { defineComponent, unref, computed, FunctionalComponent } from 'vue'; + import { TabItem, tabStore } from '/@/store/modules/tab'; -import { getScaleAction, TabContentProps } from './tab.data'; +import { getScaleAction, TabContentProps } from './data'; import { Dropdown } from '/@/components/Dropdown/index'; import { RightOutlined } from '@ant-design/icons-vue'; -import { appStore } from '/@/store/modules/app'; -import { TabContentEnum } from './tab.data'; +import { TabContentEnum } from './data'; import { useTabDropdown } from './useTabDropdown'; +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; +import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; + +const ExtraContent: FunctionalComponent = () => { + return ( + + + + ); +}; + +const TabContent: FunctionalComponent<{ tabItem: TabItem }> = (props) => { + const { tabItem: { meta } = {} } = props; + + function handleContextMenu(e: Event) { + if (!props.tabItem) return; + const tableItem = props.tabItem; + e?.preventDefault(); + const index = unref(tabStore.getTabsState).findIndex((tab) => tab.path === tableItem.path); + + tabStore.commitCurrentContextMenuIndexState(index); + tabStore.commitCurrentContextMenuState(props.tabItem); + } + + return ( +
+ {meta && meta.title} +
+ ); +}; export default defineComponent({ name: 'TabContent', @@ -19,82 +49,39 @@ export default defineComponent({ type: Object as PropType, default: null, }, + type: { - type: Number as PropType, + type: Number as PropType, default: TabContentEnum.TAB_TYPE, }, - trigger: { - type: Array as PropType, - default: () => { - return ['contextmenu']; - }, - }, }, setup(props) { - const getProjectConfigRef = computed(() => { - return appStore.getProjectConfig; - }); + const { getShowMenu } = useMenuSetting(); + const { getShowHeader } = useHeaderSetting(); + const { getShowQuick } = useMultipleTabSetting(); - const getIsScaleRef = computed(() => { - const { - menuSetting: { show: showMenu }, - headerSetting: { show: showHeader }, - } = unref(getProjectConfigRef); - return !showMenu && !showHeader; + const getIsScale = computed(() => { + return !unref(getShowMenu) && !unref(getShowHeader); }); - function handleContextMenu(e: Event) { - if (!props.tabItem) return; - const tableItem = props.tabItem; - e.preventDefault(); - const index = unref(tabStore.getTabsState).findIndex((tab) => tab.path === tableItem.path); - - tabStore.commitCurrentContextMenuIndexState(index); - tabStore.commitCurrentContextMenuState(props.tabItem); - } - - /** - * @description: 渲染图标 - */ - - function renderTabContent() { - const { tabItem: { meta } = {} } = props; - return ( -
- {meta && meta.title} -
- ); - } - function renderExtraContent() { - return ( - - - - ); - } + const getIsTab = computed(() => { + return !unref(getShowQuick) ? true : props.type === TabContentEnum.TAB_TYPE; + }); const { getDropMenuList, handleMenuEvent } = useTabDropdown(props as TabContentProps); return () => { - const { trigger, type } = props; - const { - multiTabsSetting: { showQuick }, - } = unref(getProjectConfigRef); - - const isTab = !showQuick ? true : type === TabContentEnum.TAB_TYPE; - const scaleAction = getScaleAction( - unref(getIsScaleRef) ? '缩小' : '放大', - unref(getIsScaleRef) - ); + const scaleAction = getScaleAction(unref(getIsScale) ? '收起' : '展开', unref(getIsScale)); const dropMenuList = unref(getDropMenuList) || []; + const isTab = unref(getIsTab); return ( - {() => (isTab ? renderTabContent() : renderExtraContent())} + {() => (isTab ? : )} ); }; diff --git a/src/layouts/default/multitabs/tab.data.ts b/src/layouts/default/multitabs/data.ts similarity index 99% rename from src/layouts/default/multitabs/tab.data.ts rename to src/layouts/default/multitabs/data.ts index 8bd87828187..0c52a4319b3 100644 --- a/src/layouts/default/multitabs/tab.data.ts +++ b/src/layouts/default/multitabs/data.ts @@ -6,11 +6,13 @@ export enum TabContentEnum { TAB_TYPE, EXTRA_TYPE, } + export interface TabContentProps { tabItem: TabItem | AppRouteRecordRaw; type?: TabContentEnum; trigger?: Array<'click' | 'hover' | 'contextmenu'>; } + /** * @description: 右键:下拉菜单文字 */ diff --git a/src/layouts/default/multitabs/index.less b/src/layouts/default/multitabs/index.less index ff636b0e406..ac26938fa23 100644 --- a/src/layouts/default/multitabs/index.less +++ b/src/layouts/default/multitabs/index.less @@ -2,11 +2,12 @@ .multiple-tabs { z-index: 10; - height: @multiple-height+2; + height: @multiple-height + 2; padding: 0 0 2px 0; - line-height: @multiple-height+2; + margin-left: -1px; + line-height: @multiple-height + 2; background: @white; - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.08); + box-shadow: 0 1px 2px 0 rgba(29, 35, 41, 0.05); .ant-tabs-small { height: @multiple-height; @@ -32,19 +33,25 @@ color: @text-color-call-out; background: @white; border: 1px solid darken(@border-color-light, 8%); - border-radius: none !important; transition: none; + &:hover { + .ant-tabs-close-x { + opacity: 1; + } + } + .ant-tabs-close-x { - width: 12px; + width: 8px; height: 12px; font-size: 12px; color: inherit; + opacity: 0; transition: none; &:hover { svg { - width: 0.8em; + width: 0.75em; } } } @@ -61,12 +68,26 @@ } .ant-tabs-tab-active { + position: relative; + padding-left: 26px; color: @white; background: fade(@primary-color, 100%); border: 0; &::before { - display: none; + position: absolute; + top: calc(50% - 3px); + left: 8px; + width: 6px; + height: 6px; + background: #fff; + border-radius: 50%; + content: ''; + transition: none; + } + + .ant-tabs-close-x { + opacity: 1; } svg { @@ -78,6 +99,10 @@ .ant-tabs-nav > div:nth-child(1) { padding: 0 10px; + + .ant-tabs-tab { + margin-right: 3px !important; + } } } @@ -111,7 +136,10 @@ text-align: center; cursor: pointer; border-left: 1px solid #eee; - // box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); + + &:hover { + color: @text-color-base; + } span[role='img'] { transform: rotate(90deg); diff --git a/src/layouts/default/multitabs/index.tsx b/src/layouts/default/multitabs/index.tsx index 4defffd5a86..2b5cbe514fd 100644 --- a/src/layouts/default/multitabs/index.tsx +++ b/src/layouts/default/multitabs/index.tsx @@ -1,10 +1,12 @@ import './index.less'; -import type { TabContentProps } from './tab.data'; +import type { TabContentProps } from './data'; import type { TabItem } from '/@/store/modules/tab'; import type { AppRouteRecordRaw } from '/@/router/types'; -import { defineComponent, watch, computed, unref } from 'vue'; +import { defineComponent, watch, computed, unref, ref, onMounted, nextTick } from 'vue'; +import Sortable from 'sortablejs'; + import { useRouter } from 'vue-router'; import { Tabs } from 'ant-design-vue'; @@ -12,24 +14,28 @@ import TabContent from './TabContent'; import { useGo } from '/@/hooks/web/usePage'; -import { TabContentEnum } from './tab.data'; +import { TabContentEnum } from './data'; import { tabStore } from '/@/store/modules/tab'; import { userStore } from '/@/store/modules/user'; import { closeTab } from './useTabDropdown'; -import { useTabs } from '/@/hooks/web/useTabs'; -import { initAffixTabs } from './useAffixTabs'; +import { initAffixTabs } from './useMultipleTabs'; +import { isNullAndUnDef } from '/@/utils/is'; +import { useProjectSetting } from '/@/hooks/setting'; export default defineComponent({ name: 'MultipleTabs', setup() { - initAffixTabs(); + const activeKeyRef = ref(''); + + const affixTextList = initAffixTabs(); const go = useGo(); + const { multiTabsSetting } = useProjectSetting(); + const { currentRoute } = useRouter(); - const { activeKeyRef } = useTabs(); const getTabsState = computed(() => tabStore.getTabsState); @@ -41,24 +47,24 @@ export default defineComponent({ if (!lastChangeRoute || !userStore.getTokenState) return; - const { path, fullPath } = lastChangeRoute; - if (activeKeyRef.value !== (fullPath || path)) { - activeKeyRef.value = fullPath || path; + const { path, fullPath } = lastChangeRoute as AppRouteRecordRaw; + const p = fullPath || path; + if (activeKeyRef.value !== p) { + activeKeyRef.value = p; } - tabStore.commitAddTab((lastChangeRoute as unknown) as AppRouteRecordRaw); + tabStore.commitAddTab(lastChangeRoute); }, { immediate: true, } ); - // tab切换 function handleChange(activeKey: any) { activeKeyRef.value = activeKey; go(activeKey, false); } - // 关闭当前tab + // Close the current tab function handleEdit(targetKey: string) { // Added operation to hide, currently only use delete operation const index = unref(getTabsState).findIndex( @@ -71,30 +77,65 @@ export default defineComponent({ const tabContentProps: TabContentProps = { tabItem: (currentRoute as unknown) as AppRouteRecordRaw, type: TabContentEnum.EXTRA_TYPE, - trigger: ['click', 'contextmenu'], }; - return ( - - - - ); + return ; } function renderTabs() { return unref(getTabsState).map((item: TabItem) => { const key = item.query ? item.fullPath : item.path; const closable = !(item && item.meta && item.meta.affix); + + const slots = { + tab: () => , + }; return ( - {{ - tab: () => , - }} + {slots} ); }); } + function initSortableTabs() { + if (!multiTabsSetting.canDrag) return; + nextTick(() => { + const el = document.querySelectorAll( + '.multiple-tabs .ant-tabs-nav > div' + )?.[0] as HTMLElement; + + if (!el) return; + Sortable.create(el, { + animation: 500, + delay: 400, + delayOnTouchOnly: true, + filter: (e: ChangeEvent) => { + const text = e?.target?.innerText; + if (!text) return false; + return affixTextList.includes(text); + }, + onEnd: (evt) => { + const { oldIndex, newIndex } = evt; + + if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) { + return; + } + + tabStore.commitSortTabs({ oldIndex, newIndex }); + }, + }); + }); + } + + onMounted(() => { + initSortableTabs(); + }); + return () => { + const slots = { + default: () => renderTabs(), + tabBarExtraContent: () => renderQuick(), + }; return (
- {{ - default: () => renderTabs(), - tabBarExtraContent: () => renderQuick(), - }} + {slots}
); diff --git a/src/layouts/default/multitabs/useAffixTabs.ts b/src/layouts/default/multitabs/useMultipleTabs.ts similarity index 82% rename from src/layouts/default/multitabs/useAffixTabs.ts rename to src/layouts/default/multitabs/useMultipleTabs.ts index 24cb4bac233..69c4e914996 100644 --- a/src/layouts/default/multitabs/useAffixTabs.ts +++ b/src/layouts/default/multitabs/useMultipleTabs.ts @@ -1,9 +1,10 @@ -import { toRaw } from 'vue'; +import { toRaw, ref } from 'vue'; import router from '/@/router'; import { AppRouteRecordRaw } from '/@/router/types'; import { TabItem, tabStore } from '/@/store/modules/tab'; export function initAffixTabs() { + const affixList = ref([]); /** * @description: Filter all fixed routes */ @@ -23,13 +24,16 @@ export function initAffixTabs() { */ function addAffixTabs(): void { const affixTabs = filterAffixTabs((router.getRoutes() as unknown) as AppRouteRecordRaw[]); + affixList.value = affixTabs; for (const tab of affixTabs) { tabStore.commitAddTab(tab); } } + let isAddAffix = false; if (!isAddAffix) { addAffixTabs(); isAddAffix = true; } + return affixList.value.map((item) => item.meta?.title).filter(Boolean); } diff --git a/src/layouts/default/multitabs/useTabDropdown.ts b/src/layouts/default/multitabs/useTabDropdown.ts index 7fb893bf965..9ba348cc308 100644 --- a/src/layouts/default/multitabs/useTabDropdown.ts +++ b/src/layouts/default/multitabs/useTabDropdown.ts @@ -1,11 +1,11 @@ import type { AppRouteRecordRaw } from '/@/router/types'; -import type { TabContentProps } from './tab.data'; +import type { TabContentProps } from './data'; import type { Ref } from 'vue'; import type { TabItem } from '/@/store/modules/tab'; import type { DropMenu } from '/@/components/Dropdown'; import { computed, unref } from 'vue'; -import { TabContentEnum, MenuEventEnum, getActions } from './tab.data'; +import { TabContentEnum, MenuEventEnum, getActions } from './data'; import { tabStore } from '/@/store/modules/tab'; import { appStore } from '/@/store/modules/app'; import { PageEnum } from '/@/enums/pageEnum'; @@ -15,9 +15,7 @@ import { useTabs, isInitUseTab } from '/@/hooks/web/useTabs'; import { RouteLocationRaw } from 'vue-router'; const { initTabFn } = useTabs(); -/** - * @description: 右键下拉 - */ + export function useTabDropdown(tabContentProps: TabContentProps) { const { currentRoute } = router; const redo = useRedo(); @@ -30,26 +28,24 @@ export function useTabDropdown(tabContentProps: TabContentProps) { : ((unref(currentRoute) as any) as AppRouteRecordRaw); }); - // 当前tab列表 - const getTabsState = computed(() => { - return tabStore.getTabsState; - }); + // Current tab list + const getTabsState = computed(() => tabStore.getTabsState); /** - * @description: 下拉列表 + * @description: drop-down list */ const getDropMenuList = computed(() => { const dropMenuList = getActions(); - // 重置为初始状态 + // Reset to initial state for (const item of dropMenuList) { item.disabled = false; } - // 没有tab + // No tab if (!unref(getTabsState) || unref(getTabsState).length <= 0) { return dropMenuList; } else if (unref(getTabsState).length === 1) { - // 只有一个tab + // Only one tab for (const item of dropMenuList) { if (item.event !== MenuEventEnum.REFRESH_PAGE) { item.disabled = true; @@ -57,22 +53,20 @@ export function useTabDropdown(tabContentProps: TabContentProps) { } return dropMenuList; } - if (!unref(getCurrentTab)) { - return; - } + if (!unref(getCurrentTab)) return; const { meta, path } = unref(getCurrentTab); - // console.log(unref(getCurrentTab)); - // 刷新按钮 + // Refresh button const curItem = tabStore.getCurrentContextMenuState; const index = tabStore.getCurrentContextMenuIndexState; const refreshDisabled = curItem ? curItem.path !== path : true; - // 关闭左侧 + // Close left const closeLeftDisabled = index === 0; - // 关闭右侧 + // Close right const closeRightDisabled = index === unref(getTabsState).length - 1; - // 当前为固定tab + // Currently fixed tab + // TODO PERf dropMenuList[0].disabled = unref(isTabsRef) ? refreshDisabled : false; if (meta && meta.affix) { dropMenuList[1].disabled = true; @@ -84,7 +78,7 @@ export function useTabDropdown(tabContentProps: TabContentProps) { }); /** - * @description: 关闭所有页面时,跳转页面 + * @description: Jump to page when closing all pages */ function gotoPage() { const len = unref(getTabsState).length; @@ -99,14 +93,14 @@ export function useTabDropdown(tabContentProps: TabContentProps) { toPath = p; } } - // 跳到当前页面报错 + // Jump to the current page and report an error path !== toPath && go(toPath as PageEnum, true); } function isGotoPage(currentTab?: TabItem) { const { path } = unref(currentRoute); const currentPath = (currentTab || unref(getCurrentTab)).path; - // 不是当前tab,关闭左侧/右侧时,需跳转页面 + // Not the current tab, when you close the left/right side, you need to jump to the page if (path !== currentPath) { go(currentPath as PageEnum, true); } @@ -117,25 +111,31 @@ export function useTabDropdown(tabContentProps: TabContentProps) { } catch (error) {} redo(); } + function closeAll() { tabStore.commitCloseAllTab(); gotoPage(); } + function closeLeft(tabItem?: TabItem) { tabStore.closeLeftTabAction(tabItem || unref(getCurrentTab)); isGotoPage(tabItem); } + function closeRight(tabItem?: TabItem) { tabStore.closeRightTabAction(tabItem || unref(getCurrentTab)); isGotoPage(tabItem); } + function closeOther(tabItem?: TabItem) { tabStore.closeOtherTabAction(tabItem || unref(getCurrentTab)); isGotoPage(tabItem); } + function closeCurrent(tabItem?: TabItem) { closeTab(unref(tabItem || unref(getCurrentTab))); } + function scaleScreen() { const { headerSetting: { show: showHeader }, @@ -159,7 +159,7 @@ export function useTabDropdown(tabContentProps: TabContentProps) { }); } - // 处理右键事件 + // Handle right click event function handleMenuEvent(menu: DropMenu): void { const { event } = menu; @@ -168,76 +168,74 @@ export function useTabDropdown(tabContentProps: TabContentProps) { scaleScreen(); break; case MenuEventEnum.REFRESH_PAGE: - // 刷新页面 + // refresh page refreshPage(); break; - // 关闭当前 + // Close current case MenuEventEnum.CLOSE_CURRENT: closeCurrent(); break; - // 关闭左侧 + // Close left case MenuEventEnum.CLOSE_LEFT: closeLeft(); break; - // 关闭右侧 + // Close right case MenuEventEnum.CLOSE_RIGHT: closeRight(); break; - // 关闭其他 + // Close other case MenuEventEnum.CLOSE_OTHER: closeOther(); break; - // 关闭其他 + // Close all case MenuEventEnum.CLOSE_ALL: closeAll(); break; - default: - break; } } return { getDropMenuList, handleMenuEvent }; } + +export function getObj(tabItem: TabItem) { + const { params, path, query } = tabItem; + return { + params: params || {}, + path, + query: query || {}, + }; +} + export function closeTab(closedTab: TabItem | AppRouteRecordRaw) { const { currentRoute, replace } = router; - // 当前tab列表 - const getTabsState = computed(() => { - return tabStore.getTabsState; - }); + // Current tab list + const getTabsState = computed(() => tabStore.getTabsState); const { path } = unref(currentRoute); if (path !== closedTab.path) { - // 关闭的不是激活tab + // Closed is not the activation tab tabStore.commitCloseTab(closedTab); return; } - // 关闭的为激活atb + + // Closed is activated atb let toObj: RouteLocationRaw = {}; + const index = unref(getTabsState).findIndex((item) => item.path === path); - // 如果当前为最左边tab + // If the current is the leftmost tab if (index === 0) { - // 只有一个tab,则跳转至首页,否则跳转至右tab + // There is only one tab, then jump to the homepage, otherwise jump to the right tab if (unref(getTabsState).length === 1) { toObj = PageEnum.BASE_HOME; } else { - // 跳转至右边tab + // Jump to the right tab const page = unref(getTabsState)[index + 1]; - const { params, path, query } = page; - toObj = { - params, - path, - query, - }; + toObj = getObj(page); } } else { - // 跳转至左边tab + // Close the current tab const page = unref(getTabsState)[index - 1]; - const { params, path, query } = page; - toObj = { - params: params || {}, - path, - query: query || {}, - }; + toObj = getObj(page); } const route = (unref(currentRoute) as unknown) as AppRouteRecordRaw; tabStore.commitCloseTab(route); diff --git a/src/layouts/default/setting/SettingDrawer.tsx b/src/layouts/default/setting/SettingDrawer.tsx index 830c6ecb377..070b77c1a0b 100644 --- a/src/layouts/default/setting/SettingDrawer.tsx +++ b/src/layouts/default/setting/SettingDrawer.tsx @@ -203,7 +203,7 @@ export default defineComponent({ getMenuFixed, getCollapsed, getShowSearch, - getHasDrag, + getCanDrag, getTopMenuAlign, getAccordion, getMenuWidth, @@ -267,7 +267,7 @@ export default defineComponent({ handler: (e) => { baseHandler(HandlerEnum.MENU_HAS_DRAG, e); }, - def: unref(getHasDrag), + def: unref(getCanDrag), disabled: !unref(getShowMenuRef), }), renderSwitchItem('侧边菜单搜索', { diff --git a/src/layouts/default/setting/handler.ts b/src/layouts/default/setting/handler.ts index 783286c0bd5..637ef2b52f7 100644 --- a/src/layouts/default/setting/handler.ts +++ b/src/layouts/default/setting/handler.ts @@ -30,7 +30,7 @@ export function handler(event: HandlerEnum, value: any): DeepPartial, dragBarRef: Ref) { - const { getMiniWidthNumber, getCollapsed, setMenuSetting, getHasDrag } = useMenuSetting(); + const { getMiniWidthNumber, getCollapsed, setMenuSetting, getCanDrag } = useMenuSetting(); const getDragBarStyle = computed(() => { if (unref(getCollapsed)) { @@ -101,7 +101,7 @@ export function useDragLine(siderRef: Ref, dragBarRef: Ref) { function renderDragLine() { return (
diff --git a/src/settings/projectSetting.ts b/src/settings/projectSetting.ts index 9a4156d0c2d..a5180aedc74 100644 --- a/src/settings/projectSetting.ts +++ b/src/settings/projectSetting.ts @@ -83,7 +83,7 @@ const setting: ProjectConfig = { collapsedShowTitle: false, // Whether it can be dragged // Only limited to the opening of the left menu, the mouse has a drag bar on the right side of the menu - hasDrag: false, + canDrag: false, // Whether to show no dom show: true, // Whether to show dom @@ -114,6 +114,8 @@ const setting: ProjectConfig = { multiTabsSetting: { // Turn on show: true, + // Is it possible to drag and drop sorting tabs + canDrag: true, // Turn on quick actions showQuick: true, // Maximum number of tab cache diff --git a/src/store/modules/tab.ts b/src/store/modules/tab.ts index 3f28403dc95..f619c54cad9 100644 --- a/src/store/modules/tab.ts +++ b/src/store/modules/tab.ts @@ -175,6 +175,14 @@ class Tab extends VuexModule { this.keepAliveTabsState = []; } + @Mutation + commitSortTabs({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }): void { + const currentTab = this.tabsState[oldIndex]; + + this.tabsState.splice(oldIndex, 1); + this.tabsState.splice(newIndex, 0, currentTab); + } + @Mutation closeMultipleTab({ pathList, nameList }: { pathList: string[]; nameList: string[] }): void { this.tabsState = toRaw(this.tabsState).filter((item) => !pathList.includes(item.fullPath)); diff --git a/src/types/config.d.ts b/src/types/config.d.ts index 43d465bd0e2..c6f33abe353 100644 --- a/src/types/config.d.ts +++ b/src/types/config.d.ts @@ -8,7 +8,7 @@ export interface MenuSetting { fixed: boolean; collapsed: boolean; collapsedShowTitle: boolean; - hasDrag: boolean; + canDrag: boolean; showSearch: boolean; show: boolean; hidden: boolean; @@ -28,7 +28,7 @@ export interface MultiTabsSetting { show: boolean; // 开启快速操作 showQuick: boolean; - + canDrag: boolean; // 缓存最大数量 max: number; } diff --git a/src/utils/is.ts b/src/utils/is.ts index b46814cf99e..b53e7e89d15 100644 --- a/src/utils/is.ts +++ b/src/utils/is.ts @@ -24,6 +24,10 @@ export function isNull(val: unknown): val is null { return val === null; } +export function isNullAndUnDef(val: unknown): val is null | undefined { + return isUnDef(val) && isNull(val); +} + export function isNumber(val: unknown): val is number { return is(val, 'Number'); } diff --git a/src/views/demo/feat/tabs/index.vue b/src/views/demo/feat/tabs/index.vue index 89cbe9597b4..5f671402b56 100644 --- a/src/views/demo/feat/tabs/index.vue +++ b/src/views/demo/feat/tabs/index.vue @@ -11,28 +11,18 @@ 关闭其他 关闭当前 刷新当前 - 打开图标界面tab