From 4baf90a5c87493939830129efaa146624faabbcc Mon Sep 17 00:00:00 2001 From: vben Date: Tue, 10 Nov 2020 21:58:19 +0800 Subject: [PATCH] perf: optimize tab switching speed --- CHANGELOG.zh_CN.md | 1 + src/components/Menu/src/index.less | 15 +-- src/design/color.less | 7 +- src/layouts/default/index.less | 2 +- src/layouts/default/multitabs/index.tsx | 46 ++++----- src/router/guard/index.ts | 15 +++ src/store/modules/menu.ts | 6 -- src/store/modules/tab.ts | 17 +++- src/utils/color.ts | 130 ++++++++++++++++++++++++ 9 files changed, 192 insertions(+), 47 deletions(-) create mode 100644 src/utils/color.ts diff --git a/CHANGELOG.zh_CN.md b/CHANGELOG.zh_CN.md index 93fc03ca..339df473 100644 --- a/CHANGELOG.zh_CN.md +++ b/CHANGELOG.zh_CN.md @@ -7,6 +7,7 @@ ### ⚡ Performance Improvements - 优化 settingDrawer 代码 +- 优化多标签页切换速度 ### 🐛 Bug Fixes diff --git a/src/components/Menu/src/index.less b/src/components/Menu/src/index.less index fb8c95a7..a75b0a2c 100644 --- a/src/components/Menu/src/index.less +++ b/src/components/Menu/src/index.less @@ -206,7 +206,7 @@ // 层级样式 &.ant-menu-dark:not(.basic-menu__sidebar-hor) { overflow-x: hidden; - background: @first-menu-item-dark-bg-color; + background: @menu-item-dark-bg-color; .active-menu-style(); .ant-menu-item.ant-menu-item-selected.basic-menu-menu-item__level1, @@ -215,20 +215,21 @@ } .basic-menu-item__level1 { - background-color: @first-menu-item-dark-bg-color; + background-color: @menu-item-dark-bg-color; > .ant-menu-sub > li { - background-color: @sub-menu-item-dark-bg-color; + background-color: lighten(@menu-item-dark-bg-color, 6%); } } .basic-menu-item__level2:not(.ant-menu-item-selected), .ant-menu-sub { - background-color: @sub-menu-item-dark-bg-color; + background-color: lighten(@menu-item-dark-bg-color, 6%); + // background-color: @sub-menu-item-dark-bg-color; } .basic-menu-item__level3:not(.ant-menu-item-selected) { - background-color: @children-menu-item-dark-bg-color; + background-color: lighten(@menu-item-dark-bg-color, 10%); } .ant-menu-submenu-title { @@ -241,7 +242,7 @@ &.ant-menu-inline-collapsed { .ant-menu-submenu-selected, .ant-menu-item-selected { - background: darken(@first-menu-item-dark-bg-color, 6%) !important; + background: darken(@menu-item-dark-bg-color, 6%) !important; } } } @@ -310,7 +311,7 @@ .ant-menu-dark { &.ant-menu-submenu-popup { > ul { - background: @first-menu-item-dark-bg-color; + background: @menu-item-dark-bg-color; } .active-menu-style(); diff --git a/src/design/color.less b/src/design/color.less index 8c023b2c..7b849178 100644 --- a/src/design/color.less +++ b/src/design/color.less @@ -64,12 +64,7 @@ // ================================= // let -menu -@first-menu-item-dark-bg-color: #273352; - -// Level 2 menu dark background color -@sub-menu-item-dark-bg-color: #314268; -// Level 3 menu dark background color -@children-menu-item-dark-bg-color: #4f6088; +@menu-item-dark-bg-color: #273352; // top-menu @top-menu-active-bg-color: #273352; diff --git a/src/layouts/default/index.less b/src/layouts/default/index.less index ea2a70cf..831411c1 100644 --- a/src/layouts/default/index.less +++ b/src/layouts/default/index.less @@ -41,7 +41,7 @@ background-size: 100% 100%; &.ant-layout-sider-dark { - background: @first-menu-item-dark-bg-color; + background: @menu-item-dark-bg-color; } &:not(.ant-layout-sider-dark) { diff --git a/src/layouts/default/multitabs/index.tsx b/src/layouts/default/multitabs/index.tsx index c280afba..5aca4157 100644 --- a/src/layouts/default/multitabs/index.tsx +++ b/src/layouts/default/multitabs/index.tsx @@ -2,15 +2,10 @@ import type { TabContentProps } from './tab.data'; import type { TabItem } from '/@/store/modules/tab'; import type { AppRouteRecordRaw } from '/@/router/types'; -import { - defineComponent, - watch, - computed, - // ref, - unref, - // onMounted, - toRaw, -} from 'vue'; +import { defineComponent, watch, computed, unref, toRaw } from 'vue'; +import { useRouter } from 'vue-router'; +import router from '/@/router'; + import { Tabs } from 'ant-design-vue'; import TabContent from './TabContent'; @@ -18,16 +13,13 @@ import { useGo } from '/@/hooks/web/usePage'; import { TabContentEnum } from './tab.data'; -import { useRouter } from 'vue-router'; - import { tabStore } from '/@/store/modules/tab'; +import { userStore } from '/@/store/modules/user'; + import { closeTab } from './useTabDropdown'; -import router from '/@/router'; import { useTabs } from '/@/hooks/web/useTabs'; -// import { PageEnum } from '/@/enums/pageEnum'; import './index.less'; -import { userStore } from '/@/store/modules/user'; export default defineComponent({ name: 'MultiTabs', setup() { @@ -41,20 +33,24 @@ export default defineComponent({ return tabStore.getTabsState; }); - if (!isAddAffix) { - addAffixTabs(); - isAddAffix = true; - } - + // If you monitor routing changes, tab switching will be stuck. So use this method watch( - () => unref(currentRoute).path, + () => tabStore.getLastChangeRouteState, () => { - if (!userStore.getTokenState) return; - const { path: rPath, fullPath } = unref(currentRoute); - if (activeKeyRef.value !== (fullPath || rPath)) { - activeKeyRef.value = fullPath || rPath; + if (!isAddAffix) { + addAffixTabs(); + isAddAffix = true; + } + + const lastChangeRoute = unref(tabStore.getLastChangeRouteState); + + if (!lastChangeRoute || !userStore.getTokenState) return; + + const { path, fullPath } = lastChangeRoute; + if (activeKeyRef.value !== (fullPath || path)) { + activeKeyRef.value = fullPath || path; } - tabStore.commitAddTab((unref(currentRoute) as unknown) as AppRouteRecordRaw); + tabStore.commitAddTab((lastChangeRoute as unknown) as AppRouteRecordRaw); }, { immediate: true, diff --git a/src/router/guard/index.ts b/src/router/guard/index.ts index e9272521..d5b11724 100644 --- a/src/router/guard/index.ts +++ b/src/router/guard/index.ts @@ -12,6 +12,8 @@ import { getIsOpenTab, setCurrentTo } from '/@/utils/helper/routeHelper'; import { setTitle } from '/@/utils/browser'; import { AxiosCanceler } from '/@/utils/http/axios/axiosCancel'; +import { tabStore } from '/@/store/modules/tab'; + const { projectSetting, globSetting } = useSetting(); export function createGuard(router: Router) { const { openNProgress, closeMessageOnSwitch, removeAllHttpPending } = projectSetting; @@ -20,8 +22,21 @@ export function createGuard(router: Router) { axiosCanceler = new AxiosCanceler(); } router.beforeEach(async (to) => { + // Determine whether the tab has been opened const isOpen = getIsOpenTab(to.fullPath); to.meta.inTab = isOpen; + + // Notify routing changes + const { fullPath, path, query, params, name, meta } = to; + tabStore.commitLastChangeRouteState({ + fullPath, + path, + query, + params, + name, + meta, + } as any); + try { if (closeMessageOnSwitch) { Modal.destroyAll(); diff --git a/src/store/modules/menu.ts b/src/store/modules/menu.ts index 5694d1b6..4586f73d 100644 --- a/src/store/modules/menu.ts +++ b/src/store/modules/menu.ts @@ -8,12 +8,6 @@ const NAME = 'menu'; hotModuleUnregisterModule(NAME); @Module({ namespaced: true, name: NAME, dynamic: true, store }) class Menu extends VuexModule { - // // 默认展开 - // private collapsedState: boolean = appStore.getProjectConfig.menuSetting.collapsed; - - // // 菜单宽度 - // private menuWidthState: number = appStore.getProjectConfig.menuSetting.menuWidth; - // 是否开始拖拽 private dragStartState = false; diff --git a/src/store/modules/tab.ts b/src/store/modules/tab.ts index 8b716971..ad2e1ad8 100644 --- a/src/store/modules/tab.ts +++ b/src/store/modules/tab.ts @@ -7,6 +7,7 @@ import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper'; import { PageEnum } from '/@/enums/pageEnum'; import { appStore } from '/@/store/modules/app'; +import { userStore } from './user'; import store from '/@/store'; import router from '/@/router'; @@ -43,10 +44,17 @@ class Tab extends VuexModule { currentContextMenuState: TabItem | null = null; + // Last route change + lastChangeRouteState: AppRouteRecordRaw | null = null; + get getTabsState() { return this.tabsState; } + get getLastChangeRouteState() { + return this.lastChangeRouteState; + } + get getCurrentContextMenuIndexState() { return this.currentContextMenuIndexState; } @@ -64,6 +72,12 @@ class Tab extends VuexModule { return this.tabsState.find((item) => item.path === route.path)!; } + @Mutation + commitLastChangeRouteState(route: AppRouteRecordRaw): void { + if (!userStore.getTokenState) return; + this.lastChangeRouteState = route; + } + @Mutation commitClearCache(): void { this.keepAliveTabsState = []; @@ -86,7 +100,7 @@ class Tab extends VuexModule { commitAddTab(route: AppRouteRecordRaw | TabItem): void { const { path, name, meta, fullPath, params, query } = route as TabItem; // 404 页面不需要添加tab - if (path === PageEnum.ERROR_PAGE) { + if (path === PageEnum.ERROR_PAGE || !name) { return; } else if ([REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string)) { return; @@ -107,7 +121,6 @@ class Tab extends VuexModule { this.tabsState.splice(updateIndex, 1, curTab); return; } - this.tabsState.push({ path, fullPath, name, meta, params, query }); if (unref(getOpenKeepAliveRef) && name) { const noKeepAlive = meta && meta.ignoreKeepAlive; diff --git a/src/utils/color.ts b/src/utils/color.ts new file mode 100644 index 00000000..a720f185 --- /dev/null +++ b/src/utils/color.ts @@ -0,0 +1,130 @@ +/** + * 判断是否 十六进制颜色值. + * 输入形式可为 #fff000 #f00 + * + * @param String color 十六进制颜色值 + * @return Boolean + */ +export const isHexColor = function (color: string) { + const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/; + return reg.test(color); +}; + +/** + * RGB 颜色值转换为 十六进制颜色值. + * r, g, 和 b 需要在 [0, 255] 范围内 + * + * @param Number r 红色色值 + * @param Number g 绿色色值 + * @param Number b 蓝色色值 + * @return String 类似#ff00ff + */ +export const rgbToHex = function (r: number, g: number, b: number) { + // tslint:disable-next-line:no-bitwise + const hex = ((r << 16) | (g << 8) | b).toString(16); + return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex; +}; + +/** + * Transform a HEX color to its RGB representation + * @param {string} hex The color to transform + * @returns The RGB representation of the passed color + */ +export const hexToRGB = function (hex: string) { + return ( + parseInt(hex.substring(0, 2), 16) + + ',' + + parseInt(hex.substring(2, 4), 16) + + ',' + + parseInt(hex.substring(4, 6), 16) + ); +}; + +/** + * Darkens a HEX color given the passed percentage + * @param {string} color The color to process + * @param {number} amount The amount to change the color by + * @returns {string} The HEX representation of the processed color + */ +export const darken = (color: string, amount: number) => { + color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color; + amount = Math.trunc((255 * amount) / 100); + return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight( + color.substring(2, 4), + amount + )}${subtractLight(color.substring(4, 6), amount)}`; +}; + +/** + * Lightens a 6 char HEX color according to the passed percentage + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed color represented as HEX + */ +export const lighten = (color: string, amount: number) => { + color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color; + amount = Math.trunc((255 * amount) / 100); + return `#${addLight(color.substring(0, 2), amount)}${addLight( + color.substring(2, 4), + amount + )}${addLight(color.substring(4, 6), amount)}`; +}; + +/* Suma el porcentaje indicado a un color (RR, GG o BB) hexadecimal para aclararlo */ +/** + * Sums the passed percentage to the R, G or B of a HEX color + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed part of the color + */ +const addLight = (color: string, amount: number) => { + const cc = parseInt(color, 16) + amount; + const c = cc > 255 ? 255 : cc; + return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`; +}; + +/** + * Calculates luminance of an rgb color + * @param {number} r red + * @param {number} g green + * @param {number} b blue + */ +const luminanace = (r: stri, g: number, b: number) => { + const a = [r, g, b].map((v) => { + v /= 255; + return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); + }); + return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; +}; + +/** + * Calculates contrast between two rgb colors + * @param {string} rgb1 rgb color 1 + * @param {string} rgb2 rgb color 2 + */ +const contrast = (rgb1: string[], rgb2: number[]) => + (luminanace(rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) / + (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05); + +/** + * Determines what the best text color is (black or white) based con the contrast with the background + * @param hexColor - Last selected color by the user + */ +export const calculateBestTextColor = (hexColor: string) => { + const rgbColor = hexToRGB(hexColor.substring(1)); + const contrastWithBlack = contrast(rgbColor.split(','), [0, 0, 0]); + + return contrastWithBlack >= 12 ? '#000000' : '#FFFFFF'; +}; + +/** + * Subtracts the indicated percentage to the R, G or B of a HEX color + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed part of the color + */ +const subtractLight = (color: string, amount: number) => { + const cc = parseInt(color, 16) - amount; + const c = cc < 0 ? 0 : cc; + return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`; +}; -- GitLab