diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..39a9c702 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,22 @@ +name: deploy + +on: + push: + branches: + - main + +jobs: + build-deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - run: npm install + - run: npm run build + + - name: Deploy + uses: peaceiris/actions-gh-pages@v2.5.0 + env: + ACTIONS_DEPLOY_KEY: ${{secrets.ACTIONS_DEPLOY_KEY}} + PUBLISH_BRANCH: gh-pages + PUBLISH_DIR: dist diff --git a/.vscode/launch.json b/.vscode/launch.json index 47834917..384f3bec 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,48 +1,13 @@ { "version": "0.2.0", "configurations": [ - // node环境调试当前激活编辑器ts/js代码 { - "type": "node", + "type": "chrome", "request": "launch", - "name": "file", - "cwd": "${workspaceFolder}", - "program": "${file}", - // .vscode 目录又不认识了??? - "preLaunchTask": "tsc: 监视 - build/tsconfig.json", // cn - // "preLaunchTask": "tsc: watch - build/tsconfig.json", // en - "outFiles": ["${workspaceFolder}/compile/**/*.js"] - // "args": ["--experimental-modules", "--loader", "./loader.mjs"] + "name": "Launch Chrome", + "url": "http://localhost:3100", + "webRoot": "${workspaceFolder}/src", + "sourceMaps": true }, - // 调试开发环境脚本 - { - "type": "node", - "request": "launch", - "name": "dev", - // "stopOnEntry": true, - "cwd": "${workspaceFolder}", - "program": "${workspaceFolder}/node_modules/@vue/cli-service/bin/vue-cli-service.js", - "args": ["serve", "--open"] - }, - // 调试生产环境脚本 - { - "type": "node", - "request": "launch", - "name": "build", - // "stopOnEntry": true, - "cwd": "${workspaceFolder}", - "program": "${workspaceFolder}/node_modules/@vue/cli-service/bin/vue-cli-service.js", - "args": ["build"] - }, - // 调试单元测试脚本 - { - "type": "node", - "request": "launch", - "name": "test:unit", - // "stopOnEntry": true, - "cwd": "${workspaceFolder}", - "program": "${workspaceFolder}/node_modules/@vue/cli-service/bin/vue-cli-service.js", - "args": ["test:unit", "--detectOpenHandles"] - } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index dbd50291..cf8d576f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -163,12 +163,6 @@ "[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "[json]": { - "editor.defaultFormatter": "vscode.json-language-features" - }, - "[jsonc]": { - "editor.defaultFormatter": "vscode.json-language-features" - }, "[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, @@ -198,5 +192,8 @@ "ts" ], "i18n-ally.sourceLanguage": "zh", - "i18n-ally.enabledFrameworks":["vue","react"] + "i18n-ally.enabledFrameworks": [ + "vue", + "react" + ] } \ No newline at end of file diff --git a/CHANGELOG.zh_CN.md b/CHANGELOG.zh_CN.md index 3e2f4468..d430a5ac 100644 --- a/CHANGELOG.zh_CN.md +++ b/CHANGELOG.zh_CN.md @@ -1,14 +1,24 @@ ## Wip +## (破坏性更新) Breaking changes + +- 路由重构, 不再支持以前的格式。改为支持 vue-router 最初的默认结构,具体格式可以参考示例更改。实现多级路由缓存,不再将路由转化为 2 级。 +- 重构面包屑,使用 antd 的面包屑组件。之前的组件已删除 + ### ✨ Features - 还原 antdv 默认 loading,重构 `Loading` 组件,增加`useLoading`和`v-loading`指令。并增加示例 - i18n 支持 vscode `i18n-ally`插件 +- 新增多级路由缓存示例 ### 🎫 Chores - 首屏 loading 修改 +### 🐛 Bug Fixes + +-修复表格 i18n 错误 + ## 2.0.0-rc.12 (2020-11-30) ## (破坏性更新) Breaking changes diff --git a/build/vite/plugin/transform/dynamic-import/index.ts b/build/vite/plugin/transform/dynamic-import/index.ts index fbe20b9b..eb60ecca 100644 --- a/build/vite/plugin/transform/dynamic-import/index.ts +++ b/build/vite/plugin/transform/dynamic-import/index.ts @@ -17,8 +17,8 @@ const dynamicImportTransform = function (enableDynamicImport: boolean): Transfor test({ path }) { // Only convert the file return ( - path.includes('/src/utils/helper/dynamicImport.ts') || - path.includes(`\\src\\utils\\helper\\dynamicImport.ts`) + path.includes('/src/router/helper/dynamicImport.ts') || + path.includes(`\\src\\router\\helper\\dynamicImport.ts`) ); }, transform({ code }) { diff --git a/mock/sys/menu.ts b/mock/sys/menu.ts index e80ede76..fc6d7c1e 100644 --- a/mock/sys/menu.ts +++ b/mock/sys/menu.ts @@ -1,33 +1,23 @@ import { resultSuccess } from '../_util'; import { MockMethod } from 'vite-plugin-mock'; +// single const dashboardRoute = { - path: '/dashboard', - name: 'Dashboard', - component: 'PAGE_LAYOUT', - redirect: '/dashboard/welcome', + path: '/home', + name: 'Home', + component: '/dashboard/welcome/index', meta: { + title: 'routes.dashboard.welcome', + affix: true, icon: 'ant-design:home-outlined', - title: 'Dashboard', }, - children: [ - { - path: '/welcome', - name: 'Welcome', - component: '/dashboard/welcome/index', - meta: { - title: '欢迎页', - affix: true, - }, - }, - ], }; const frontRoute = { - path: '/front', + path: 'front', name: 'PermissionFrontDemo', meta: { - title: '基于前端权限', + title: 'routes.demo.permission.front', }, children: [ { @@ -35,7 +25,7 @@ const frontRoute = { name: 'FrontPageAuth', component: '/demo/permission/front/index', meta: { - title: '页面权限', + title: 'routes.demo.permission.frontPage', }, }, { @@ -43,7 +33,7 @@ const frontRoute = { name: 'FrontBtnAuth', component: '/demo/permission/front/Btn', meta: { - title: '按钮权限', + title: 'routes.demo.permission.frontBtn', }, }, { @@ -51,7 +41,7 @@ const frontRoute = { name: 'FrontAuthPageA', component: '/demo/permission/front/AuthPageA', meta: { - title: '权限测试页A', + title: 'routes.demo.permission.frontTestA', }, }, { @@ -59,24 +49,25 @@ const frontRoute = { name: 'FrontAuthPageB', component: '/demo/permission/front/AuthPageB', meta: { - title: '权限测试页B', + title: 'routes.demo.permission.frontTestB', }, }, ], }; const backRoute = { - path: '/back', + path: 'back', name: 'PermissionBackDemo', meta: { - title: '基于后台权限', + title: 'routes.demo.permission.back', }, + children: [ { path: 'page', name: 'BackAuthPage', component: '/demo/permission/back/index', meta: { - title: '页面权限', + title: 'routes.demo.permission.backPage', }, }, { @@ -84,7 +75,7 @@ const backRoute = { name: 'BackAuthBtn', component: '/demo/permission/back/Btn', meta: { - title: '按钮权限', + title: 'routes.demo.permission.backBtn', }, }, ], @@ -92,11 +83,11 @@ const backRoute = { const authRoute = { path: '/permission', name: 'Permission', - component: 'PAGE_LAYOUT', + component: 'LAYOUT', redirect: '/permission/front/page', meta: { - icon: 'ant-design:home-outlined', - title: '权限管理', + icon: 'carbon:user-role', + title: 'routes.demo.permission.permission', }, children: [frontRoute, backRoute], }; @@ -104,14 +95,70 @@ const authRoute = { const authRoute1 = { path: '/permission', name: 'Permission', - component: 'PAGE_LAYOUT', + component: 'LAYOUT', redirect: '/permission/front/page', meta: { - icon: 'ant-design:home-outlined', - title: '权限管理', + icon: 'carbon:user-role', + title: 'routes.demo.permission.permission', }, children: [backRoute], }; + +const levelRoute = { + path: '/level', + name: 'Level', + component: 'LAYOUT', + redirect: '/level/menu1/menu1-1', + meta: { + icon: 'carbon:user-role', + title: 'routes.demo.level.level', + }, + + children: [ + { + path: 'menu1', + name: 'Menu1Demo', + meta: { + title: 'Menu1', + }, + children: [ + { + path: 'menu1-1', + name: 'Menu11Demo', + meta: { + title: 'Menu1-1', + }, + children: [ + { + path: 'menu1-1-1', + name: 'Menu111Demo', + component: '/demo/level/Menu111', + meta: { + title: 'Menu111', + }, + }, + ], + }, + { + path: 'menu1-2', + name: 'Menu12Demo', + component: '/demo/level/Menu12', + meta: { + title: 'Menu1-2', + }, + }, + ], + }, + { + path: 'menu2', + name: 'Menu2Demo', + component: '/demo/level/Menu2', + meta: { + title: 'Menu2', + }, + }, + ], +}; export default [ { url: '/api/getMenuListById', @@ -120,10 +167,10 @@ export default [ response: ({ query }) => { const { id } = query; if (!id || id === '1') { - return resultSuccess([dashboardRoute, authRoute]); + return resultSuccess([dashboardRoute, authRoute, levelRoute]); } if (id === '2') { - return resultSuccess([dashboardRoute, authRoute1]); + return resultSuccess([dashboardRoute, authRoute1, levelRoute]); } }, }, diff --git a/package.json b/package.json index 17762210..6c86df91 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "qrcode": "^1.4.4", "sortablejs": "^1.12.0", "vditor": "^3.7.0", - "vue": "^3.0.3", + "vue": "^3.0.4", "vue-i18n": "^9.0.0-beta.8", "vue-router": "^4.0.0-rc.6", "vue-types": "^3.0.1", @@ -47,7 +47,7 @@ "devDependencies": { "@commitlint/cli": "^11.0.0", "@commitlint/config-conventional": "^11.0.0", - "@iconify/json": "^1.1.266", + "@iconify/json": "^1.1.267", "@ls-lint/ls-lint": "^1.9.2", "@purge-icons/generated": "^0.4.1", "@types/echarts": "^4.9.2", @@ -60,25 +60,25 @@ "@types/qrcode": "^1.3.5", "@types/rollup-plugin-visualizer": "^2.6.0", "@types/sortablejs": "^1.10.6", - "@types/yargs": "^15.0.10", + "@types/yargs": "^15.0.11", "@types/zxcvbn": "^4.4.0", "@typescript-eslint/eslint-plugin": "^4.9.0", "@typescript-eslint/parser": "^4.9.0", - "@vue/compiler-sfc": "^3.0.3", + "@vue/compiler-sfc": "^3.0.4", "@vuedx/typecheck": "^0.2.4-0", "@vuedx/typescript-plugin-vue": "^0.2.4-0", "autoprefixer": "^9.8.6", "commitizen": "^4.2.2", "conventional-changelog-cli": "^2.1.1", "conventional-changelog-custom-config": "^0.3.1", - "cross-env": "^7.0.2", + "cross-env": "^7.0.3", "dot-prop": "^6.0.1", "dotenv": "^8.2.0", "eslint": "^7.14.0", "eslint-config-prettier": "^6.15.0", "eslint-plugin-prettier": "^3.1.4", "eslint-plugin-vue": "^7.1.0", - "esno": "^0.2.4", + "esno": "^0.3.0", "fs-extra": "^9.0.1", "globrex": "^0.1.2", "husky": "^4.3.0", diff --git a/src/components/Breadcrumb/index.ts b/src/components/Breadcrumb/index.ts deleted file mode 100644 index 7069a145..00000000 --- a/src/components/Breadcrumb/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import BreadcrumbLib from './src/Breadcrumb.vue'; -import BreadcrumbItemLib from './src/BreadcrumbItem.vue'; -import { withInstall } from '../util'; - -export const Breadcrumb = withInstall(BreadcrumbLib); -export const BreadcrumbItem = withInstall(BreadcrumbItemLib); diff --git a/src/components/Breadcrumb/src/Breadcrumb.vue b/src/components/Breadcrumb/src/Breadcrumb.vue deleted file mode 100644 index ed25a346..00000000 --- a/src/components/Breadcrumb/src/Breadcrumb.vue +++ /dev/null @@ -1,96 +0,0 @@ - - - - diff --git a/src/components/Breadcrumb/src/BreadcrumbItem.vue b/src/components/Breadcrumb/src/BreadcrumbItem.vue deleted file mode 100644 index 60f3cc92..00000000 --- a/src/components/Breadcrumb/src/BreadcrumbItem.vue +++ /dev/null @@ -1,57 +0,0 @@ - - - diff --git a/src/components/Menu/src/BasicMenu.tsx b/src/components/Menu/src/BasicMenu.tsx index eeb8e76d..dd3283a1 100644 --- a/src/components/Menu/src/BasicMenu.tsx +++ b/src/components/Menu/src/BasicMenu.tsx @@ -36,6 +36,7 @@ import { getCurrentParentPath } from '/@/router/menus'; import { basicProps } from './props'; import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { REDIRECT_NAME } from '/@/router/constant'; export default defineComponent({ name: 'BasicMenu', props: basicProps, @@ -120,7 +121,7 @@ export default defineComponent({ watch( () => currentRoute.value.name, (name: string) => { - if (name === 'Redirect') return; + if (name === REDIRECT_NAME) return; handleMenuChange(); props.isHorizontal && appStore.getProjectConfig.menuSetting.split && getParentPath(); } diff --git a/src/components/Menu/src/hooks/useOpenKeys.ts b/src/components/Menu/src/hooks/useOpenKeys.ts index 272ab48f..cc78cc60 100644 --- a/src/components/Menu/src/hooks/useOpenKeys.ts +++ b/src/components/Menu/src/hooks/useOpenKeys.ts @@ -4,7 +4,7 @@ import type { MenuState } from '../types'; import type { Ref } from 'vue'; import { unref } from 'vue'; -import { getAllParentPath } from '/@/utils/helper/menuHelper'; +import { getAllParentPath } from '/@/router/helper/menuHelper'; import { es6Unique } from '/@/utils'; import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; diff --git a/src/components/Menu/src/hooks/useSearchInput.ts b/src/components/Menu/src/hooks/useSearchInput.ts index 837a6d02..461130de 100644 --- a/src/components/Menu/src/hooks/useSearchInput.ts +++ b/src/components/Menu/src/hooks/useSearchInput.ts @@ -5,7 +5,7 @@ import type { Ref } from 'vue'; import { isString } from '/@/utils/is'; import { unref } from 'vue'; import { es6Unique } from '/@/utils'; -import { getAllParentPath } from '/@/utils/helper/menuHelper'; +import { getAllParentPath } from '/@/router/helper/menuHelper'; interface UseSearchInputOptions { menuState: MenuState; diff --git a/src/components/Table/src/components/TableSetting.vue b/src/components/Table/src/components/TableSetting.vue index 36a9a170..03c443a9 100644 --- a/src/components/Table/src/components/TableSetting.vue +++ b/src/components/Table/src/components/TableSetting.vue @@ -33,7 +33,7 @@ - {{ t('settingColumnShow') }} + {{ t('component.table.settingColumnShow') }} - {{ t('settingReset') }} + + {{ t('component.table.settingReset') }} @@ -69,7 +71,7 @@ diff --git a/src/components/registerGlobComp.ts b/src/components/registerGlobComp.ts index 5e95823e..53868ae5 100644 --- a/src/components/registerGlobComp.ts +++ b/src/components/registerGlobComp.ts @@ -33,6 +33,7 @@ import { Empty, Avatar, Menu, + Breadcrumb, } from 'ant-design-vue'; import { getApp } from '/@/setup/App'; @@ -55,6 +56,7 @@ export function registerGlobComp() { getApp() .use(Select) .use(Alert) + .use(Breadcrumb) .use(Checkbox) .use(DatePicker) .use(Radio) diff --git a/src/design/transition/breadcrumb.less b/src/design/transition/breadcrumb.less deleted file mode 100644 index 5b75b1f2..00000000 --- a/src/design/transition/breadcrumb.less +++ /dev/null @@ -1,18 +0,0 @@ -.breadcrumb-enter-active, -.breadcrumb-leave-active { - transition: all 0.24s; -} - -.breadcrumb-enter-from, -.breadcrumb-leave-active { - opacity: 0; - transform: translateX(16px); -} - -.breadcrumb-move { - transition: all 0.38s; -} - -.breadcrumb-leave-active { - position: absolute; -} diff --git a/src/design/transition/index.less b/src/design/transition/index.less index e7da9713..1d44b148 100644 --- a/src/design/transition/index.less +++ b/src/design/transition/index.less @@ -4,4 +4,3 @@ @import './slide.less'; @import './scroll.less'; @import './zoom.less'; -@import './breadcrumb.less'; diff --git a/src/enums/pageEnum.ts b/src/enums/pageEnum.ts index ef74eba0..9155ee72 100644 --- a/src/enums/pageEnum.ts +++ b/src/enums/pageEnum.ts @@ -2,7 +2,7 @@ export enum PageEnum { // basic login path BASE_LOGIN = '/login', // basic home path - BASE_HOME = '/dashboard', + BASE_HOME = '/home', // error page path ERROR_PAGE = '/exception', // error log page path diff --git a/src/hooks/web/usePage.ts b/src/hooks/web/usePage.ts index d6661886..1b3df117 100644 --- a/src/hooks/web/usePage.ts +++ b/src/hooks/web/usePage.ts @@ -1,11 +1,12 @@ import { appStore } from '/@/store/modules/app'; import type { RouteLocationRaw } from 'vue-router'; -import { useRouter } from 'vue-router'; import { PageEnum } from '/@/enums/pageEnum'; import { isString } from '/@/utils/is'; import { unref } from 'vue'; +import router from '/@/router'; + export type RouteLocationRawEx = Omit & { path: PageEnum }; function handleError(e: Error) { @@ -18,7 +19,7 @@ function handleError(e: Error) { // page switch export function useGo() { - const { push, replace } = useRouter(); + const { push, replace } = router; function go(opt: PageEnum | RouteLocationRawEx | string = PageEnum.BASE_HOME, isReplace = false) { if (!opt) return; if (isString(opt)) { @@ -35,7 +36,7 @@ export function useGo() { * @description: redo current page */ export const useRedo = () => { - const { push, currentRoute } = useRouter(); + const { push, currentRoute } = router; const { query, params } = currentRoute.value; function redo() { push({ diff --git a/src/hooks/web/usePermission.ts b/src/hooks/web/usePermission.ts index eb0a387e..c35409be 100644 --- a/src/hooks/web/usePermission.ts +++ b/src/hooks/web/usePermission.ts @@ -7,13 +7,14 @@ import { userStore } from '/@/store/modules/user'; import { useTabs } from './useTabs'; import router, { resetRouter } from '/@/router'; -import { RootRoute } from '/@/router/routes'; +// import { RootRoute } from '/@/router/routes'; import { PermissionModeEnum } from '/@/enums/appEnum'; import { RoleEnum } from '/@/enums/roleEnum'; import { intersection } from 'lodash-es'; import { isArray } from '/@/utils/is'; +import { tabStore } from '/@/store/modules/tab'; // User permissions related operations export function usePermission() { @@ -27,8 +28,7 @@ export function usePermission() { ? PermissionModeEnum.ROLE : PermissionModeEnum.BACK, }); - resume(); - // location.reload(); + location.reload(); } /** @@ -36,18 +36,15 @@ export function usePermission() { * @param id */ async function resume(id?: string | number) { + tabStore.commitClearCache(); resetRouter(); const routes = await permissionStore.buildRoutesAction(id); routes.forEach((route) => { - router.addRoute(RootRoute.name!, route as RouteRecordRaw); + router.addRoute(route as RouteRecordRaw); }); permissionStore.commitLastBuildMenuTimeState(); - const { - // closeAll, - closeOther, - } = useTabs(); - // closeAll(); - closeOther(); + const { closeAll } = useTabs(); + closeAll(); } /** diff --git a/src/hooks/web/useTabs.ts b/src/hooks/web/useTabs.ts index 775109e4..b85659cf 100644 --- a/src/hooks/web/useTabs.ts +++ b/src/hooks/web/useTabs.ts @@ -1,72 +1,21 @@ -import { TabItem, tabStore } from '/@/store/modules/tab'; +import { tabStore } from '/@/store/modules/tab'; import { appStore } from '/@/store/modules/app'; -type RouteFn = (tabItem: TabItem) => void; - -interface TabFn { - refreshPageFn: RouteFn; - closeAllFn: Fn; - closeLeftFn: RouteFn; - closeRightFn: RouteFn; - closeOtherFn: RouteFn; - closeCurrentFn: RouteFn; -} - -let refreshPage: RouteFn; -let closeAll: Fn; -let closeLeft: RouteFn; -let closeRight: RouteFn; -let closeOther: RouteFn; -let closeCurrent: RouteFn; - -export let isInitUseTab = false; - export function useTabs() { - function initTabFn({ - refreshPageFn, - closeAllFn, - closeLeftFn, - closeRightFn, - closeOtherFn, - closeCurrentFn, - }: TabFn) { - if (isInitUseTab) return; - - refreshPageFn && (refreshPage = refreshPageFn); - closeAllFn && (closeAll = closeAllFn); - closeLeftFn && (closeLeft = closeLeftFn); - closeRightFn && (closeRight = closeRightFn); - closeOtherFn && (closeOther = closeOtherFn); - closeCurrentFn && (closeCurrent = closeCurrentFn); - isInitUseTab = true; - } - - function resetCache() { - const def = undefined as any; - refreshPage = def; - closeAll = def; - closeLeft = def; - closeRight = def; - closeOther = def; - closeCurrent = def; - } - function canIUseFn(): boolean { const { multiTabsSetting: { show } = {} } = appStore.getProjectConfig; if (!show) { - throw new Error('当前未开启多标签页,请在设置中打开!'); + throw new Error('The multi-tab page is currently not open, please open it in the settings!'); } return !!show; } return { - initTabFn, - refreshPage: () => canIUseFn() && refreshPage(tabStore.getCurrentTab), - closeAll: () => canIUseFn() && closeAll(), - closeLeft: () => canIUseFn() && closeLeft(tabStore.getCurrentTab), - closeRight: () => canIUseFn() && closeRight(tabStore.getCurrentTab), - closeOther: () => canIUseFn() && closeOther(tabStore.getCurrentTab), - closeCurrent: () => canIUseFn() && closeCurrent(tabStore.getCurrentTab), - resetCache: () => canIUseFn() && resetCache(), + refreshPage: () => canIUseFn() && tabStore.commitRedoPage(), + closeAll: () => canIUseFn() && tabStore.closeAllTabAction(), + closeLeft: () => canIUseFn() && tabStore.closeLeftTabAction(tabStore.getCurrentTab), + closeRight: () => canIUseFn() && tabStore.closeRightTabAction(tabStore.getCurrentTab), + closeOther: () => canIUseFn() && tabStore.closeOtherTabAction(tabStore.getCurrentTab), + closeCurrent: () => canIUseFn() && tabStore.closeTabAction(tabStore.getCurrentTab), }; } diff --git a/src/layouts/default/content/index.tsx b/src/layouts/default/content/index.tsx index 464e58f5..8e69d0a0 100644 --- a/src/layouts/default/content/index.tsx +++ b/src/layouts/default/content/index.tsx @@ -3,11 +3,9 @@ import './index.less'; import { defineComponent, unref } from 'vue'; import { Loading } from '/@/components/Loading'; -import { RouterView } from 'vue-router'; - import { useRootSetting } from '/@/hooks/setting/useRootSetting'; import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'; - +import PageLayout from '/@/layouts/page/index.vue'; export default defineComponent({ name: 'LayoutContent', setup() { @@ -20,7 +18,7 @@ export default defineComponent({ {unref(getOpenPageLoading) && ( )} - + ); }; diff --git a/src/layouts/default/header/LayoutBreadcrumb.tsx b/src/layouts/default/header/LayoutBreadcrumb.tsx deleted file mode 100644 index 4b0b4769..00000000 --- a/src/layouts/default/header/LayoutBreadcrumb.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import type { AppRouteRecordRaw } from '/@/router/types'; -import type { RouteLocationMatched } from 'vue-router'; -import type { PropType } from 'vue'; - -import { defineComponent, TransitionGroup, unref, watch, ref } from 'vue'; -import Icon from '/@/components/Icon'; - -import { Breadcrumb, BreadcrumbItem } from '/@/components/Breadcrumb'; - -import { useRouter } from 'vue-router'; - -import { isBoolean } from '/@/utils/is'; -import { compile } from 'path-to-regexp'; - -import router from '/@/router'; - -import { PageEnum } from '/@/enums/pageEnum'; -import { useI18n } from '/@/hooks/web/useI18n'; - -export default defineComponent({ - name: 'BasicBreadcrumb', - props: { - showIcon: { - type: Boolean as PropType, - default: false, - }, - }, - setup(props) { - const itemList = ref([]); - - const { currentRoute, push } = useRouter(); - const { t } = useI18n(); - watch( - () => currentRoute.value, - () => { - if (unref(currentRoute).name === 'Redirect') return; - getBreadcrumb(); - }, - { immediate: true } - ); - - function getBreadcrumb() { - const { matched } = unref(currentRoute); - const matchedList = matched.filter((item) => item.meta && item.meta.title).slice(1); - const firstItem = matchedList[0]; - const ret = getHomeRoute(firstItem); - if (!isBoolean(ret)) { - matchedList.unshift(ret); - } - itemList.value = ((matchedList as any) as AppRouteRecordRaw[]).filter( - (item) => item.meta && item.meta.title && !item.meta.hideBreadcrumb - ); - } - - function getHomeRoute(firstItem: RouteLocationMatched) { - if (!firstItem || !firstItem.name) return false; - const routes = router.getRoutes(); - const homeRoute = routes.find((item) => item.path === PageEnum.BASE_HOME); - if (!homeRoute) return false; - if (homeRoute.name === firstItem.name) return false; - return homeRoute; - } - - function pathCompile(path: string) { - const { params } = unref(currentRoute); - const toPath = compile(path); - return toPath(params); - } - - function handleItemClick(item: AppRouteRecordRaw) { - const { redirect, path, meta } = item; - if (meta.disabledRedirect) return; - if (redirect) { - push(redirect as string); - return; - } - return push(pathCompile(path)); - } - - function renderItemContent(item: AppRouteRecordRaw) { - return ( - <> - {props.showIcon && item.meta.icon && item.meta.icon.trim() !== '' && ( - - )} - {t(item.meta.title)} - - ); - } - - function renderBreadcrumbItemList() { - return unref(itemList).map((item) => { - const isLink = - (!!item.redirect && !item.meta.disabledRedirect) || - !item.children || - item.children.length === 0; - - return ( - - {() => renderItemContent(item as AppRouteRecordRaw)} - - ); - }); - } - - function renderBreadcrumbDefault() { - return ( - {() => renderBreadcrumbItemList()} - ); - } - - return () => ( - - {() => renderBreadcrumbDefault()} - - ); - }, -}); diff --git a/src/layouts/default/header/LayoutBreadcrumb.vue b/src/layouts/default/header/LayoutBreadcrumb.vue new file mode 100644 index 00000000..d5d3f5cd --- /dev/null +++ b/src/layouts/default/header/LayoutBreadcrumb.vue @@ -0,0 +1,79 @@ + + diff --git a/src/layouts/default/header/LayoutHeader.tsx b/src/layouts/default/header/LayoutHeader.tsx index 593f68fa..4392342b 100644 --- a/src/layouts/default/header/LayoutHeader.tsx +++ b/src/layouts/default/header/LayoutHeader.tsx @@ -9,7 +9,7 @@ import { Layout, Tooltip, Badge } from 'ant-design-vue'; import { AppLogo } from '/@/components/Application'; import UserDropdown from './UserDropdown'; import LayoutMenu from '../menu'; -import LayoutBreadcrumb from './LayoutBreadcrumb'; +import LayoutBreadcrumb from './LayoutBreadcrumb.vue'; import LockAction from '../lock/LockAction'; import LayoutTrigger from '../LayoutTrigger'; import NoticeAction from './notice/NoticeActionItem.vue'; diff --git a/src/layouts/default/header/LayoutMultipleHeader.less b/src/layouts/default/header/LayoutMultipleHeader.less index 473695da..877e42f1 100644 --- a/src/layouts/default/header/LayoutMultipleHeader.less +++ b/src/layouts/default/header/LayoutMultipleHeader.less @@ -1,5 +1,6 @@ .multiple-tab-header { flex: 0 0 auto; + margin-left: -1px; &.fixed { position: fixed; diff --git a/src/layouts/default/header/index.less b/src/layouts/default/header/index.less index 84bb1d3d..5c52518c 100644 --- a/src/layouts/default/header/index.less +++ b/src/layouts/default/header/index.less @@ -21,11 +21,15 @@ &__left { display: flex; + height: 100%; align-items: center; .layout-trigger { + display: flex; + height: 100%; padding: 1px 10px 0 16px; cursor: pointer; + align-items: center; .anticon { font-size: 17px; @@ -49,12 +53,22 @@ } .layout-breadcrumb { + display: flex; padding: 0 8px; + align-items: center; + + .ant-breadcrumb-link { + .anticon { + margin-right: 4px; + margin-bottom: 2px; + } + } } } &__content { display: flex; + height: 100%; flex-grow: 1; align-items: center; } @@ -72,6 +86,24 @@ } } + .layout-breadcrumb { + .ant-breadcrumb-link { + color: @breadcrumb-item-normal-color; + + a { + color: @text-color-base; + + &:hover { + color: @primary-color; + } + } + } + + .ant-breadcrumb-separator { + color: @breadcrumb-item-normal-color; + } + } + .layout-header__logo { height: @header-height; color: @text-color-base; @@ -152,20 +184,22 @@ } } - .breadcrumb { - &__item:last-child .breadcrumb__inner, - &__item:last-child &__inner a, - &__item:last-child &__inner a:hover, - &__item:last-child &__inner:hover { - font-weight: 400; + .layout-breadcrumb { + .ant-breadcrumb-link { color: rgba(255, 255, 255, 0.6); - cursor: text; + + a { + color: rgba(255, 255, 255, 0.8); + + &:hover { + color: @white; + } + } } - &__inner, - &__inner.is-link, - &__separator { - color: @white; + .ant-breadcrumb-separator, + .anticon { + color: rgba(255, 255, 255, 0.8); } } } diff --git a/src/layouts/default/multitabs/TabContent.tsx b/src/layouts/default/multitabs/TabContent.tsx index a524ca38..ae5ffcb8 100644 --- a/src/layouts/default/multitabs/TabContent.tsx +++ b/src/layouts/default/multitabs/TabContent.tsx @@ -1,20 +1,19 @@ import type { PropType } from 'vue'; - -import { defineComponent, unref, computed, FunctionalComponent } from 'vue'; - -import { TabItem, tabStore } from '/@/store/modules/tab'; -import { getScaleAction, TabContentProps } from './data'; - import { Dropdown } from '/@/components/Dropdown/index'; + +import { defineComponent, unref, FunctionalComponent } from 'vue'; + +import { TabContentProps } from './types'; + import { RightOutlined } from '@ant-design/icons-vue'; -import { TabContentEnum } from './data'; +import { TabContentEnum } from './types'; + import { useTabDropdown } from './useTabDropdown'; -import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; -import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; -import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; import { useI18n } from '/@/hooks/web/useI18n'; +import { RouteLocationNormalized } from 'vue-router'; + const { t: titleT } = useI18n(); const ExtraContent: FunctionalComponent = () => { @@ -25,21 +24,13 @@ const ExtraContent: FunctionalComponent = () => { ); }; -const TabContent: FunctionalComponent<{ tabItem: TabItem }> = (props) => { +const TabContent: FunctionalComponent<{ tabItem: RouteLocationNormalized; handler: Fn }> = ( + 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 && titleT(meta.title)}
); @@ -49,7 +40,7 @@ export default defineComponent({ name: 'TabContent', props: { tabItem: { - type: Object as PropType, + type: Object as PropType, default: null, }, @@ -59,36 +50,27 @@ export default defineComponent({ }, }, setup(props) { - const { t } = useI18n(); - const { getShowMenu } = useMenuSetting(); - const { getShowHeader } = useHeaderSetting(); - const { getShowQuick } = useMultipleTabSetting(); - - const getIsScale = computed(() => { - return !unref(getShowMenu) && !unref(getShowHeader); - }); - - const getIsTab = computed(() => { - return !unref(getShowQuick) ? true : props.type === TabContentEnum.TAB_TYPE; - }); - - const { getDropMenuList, handleMenuEvent } = useTabDropdown(props as TabContentProps); + const { + getDropMenuList, + handleMenuEvent, + handleContextMenu, + getTrigger, + isTabs, + } = useTabDropdown(props as TabContentProps); return () => { - const scaleAction = getScaleAction( - unref(getIsScale) ? t('layout.multipleTab.putAway') : t('layout.multipleTab.unfold'), - unref(getIsScale) - ); - const dropMenuList = unref(getDropMenuList) || []; - - const isTab = unref(getIsTab); return ( - {() => (isTab ? : )} + {() => { + if (!unref(isTabs)) { + return ; + } + return ; + }} ); }; diff --git a/src/layouts/default/multitabs/data.ts b/src/layouts/default/multitabs/data.ts deleted file mode 100644 index 22bc0d6a..00000000 --- a/src/layouts/default/multitabs/data.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { DropMenu } from '/@/components/Dropdown/index'; -import { AppRouteRecordRaw } from '/@/router/types'; -import type { TabItem } from '/@/store/modules/tab'; - -import { useI18n } from '/@/hooks/web/useI18n'; - -const { t } = useI18n(); - -export enum TabContentEnum { - TAB_TYPE, - EXTRA_TYPE, -} - -export interface TabContentProps { - tabItem: TabItem | AppRouteRecordRaw; - type?: TabContentEnum; - trigger?: Array<'click' | 'hover' | 'contextmenu'>; -} - -/** - * @description: 右键:下拉菜单文字 - */ -export enum MenuEventEnum { - // 刷新 - REFRESH_PAGE, - // 关闭当前 - CLOSE_CURRENT, - // 关闭左侧 - CLOSE_LEFT, - // 关闭右侧 - CLOSE_RIGHT, - // 关闭其他 - CLOSE_OTHER, - // 关闭所有 - CLOSE_ALL, - // 放大 - SCALE, -} - -export function getActions() { - const REFRESH_PAGE: DropMenu = { - icon: 'ant-design:reload-outlined', - event: MenuEventEnum.REFRESH_PAGE, - text: t('layout.multipleTab.redo'), - disabled: false, - }; - const CLOSE_CURRENT: DropMenu = { - icon: 'ant-design:close-outlined', - event: MenuEventEnum.CLOSE_CURRENT, - text: t('layout.multipleTab.close'), - disabled: false, - divider: true, - }; - const CLOSE_LEFT: DropMenu = { - icon: 'ant-design:pic-left-outlined', - event: MenuEventEnum.CLOSE_LEFT, - text: t('layout.multipleTab.closeLeft'), - disabled: false, - divider: false, - }; - const CLOSE_RIGHT: DropMenu = { - icon: 'ant-design:pic-right-outlined', - event: MenuEventEnum.CLOSE_RIGHT, - text: t('layout.multipleTab.closeRight'), - disabled: false, - divider: true, - }; - const CLOSE_OTHER: DropMenu = { - icon: 'ant-design:pic-center-outlined', - event: MenuEventEnum.CLOSE_OTHER, - text: t('layout.multipleTab.closeOther'), - disabled: false, - }; - const CLOSE_ALL: DropMenu = { - icon: 'ant-design:line-outlined', - event: MenuEventEnum.CLOSE_ALL, - text: t('layout.multipleTab.closeAll'), - disabled: false, - }; - return [REFRESH_PAGE, CLOSE_CURRENT, CLOSE_LEFT, CLOSE_RIGHT, CLOSE_OTHER, CLOSE_ALL]; -} - -export function getScaleAction(text: string, isZoom = false) { - return { - icon: isZoom ? 'codicon:screen-normal' : 'codicon:screen-full', - event: MenuEventEnum.SCALE, - text: text, - disabled: false, - }; -} diff --git a/src/layouts/default/multitabs/index.tsx b/src/layouts/default/multitabs/index.tsx index 2b5cbe51..0fddad6b 100644 --- a/src/layouts/default/multitabs/index.tsx +++ b/src/layouts/default/multitabs/index.tsx @@ -1,12 +1,8 @@ import './index.less'; -import type { TabContentProps } from './data'; -import type { TabItem } from '/@/store/modules/tab'; -import type { AppRouteRecordRaw } from '/@/router/types'; - -import { defineComponent, watch, computed, unref, ref, onMounted, nextTick } from 'vue'; -import Sortable from 'sortablejs'; +import type { TabContentProps } from './types'; +import { defineComponent, watch, computed, unref, ref } from 'vue'; import { useRouter } from 'vue-router'; import { Tabs } from 'ant-design-vue'; @@ -14,15 +10,12 @@ import TabContent from './TabContent'; import { useGo } from '/@/hooks/web/usePage'; -import { TabContentEnum } from './data'; +import { TabContentEnum } from './types'; import { tabStore } from '/@/store/modules/tab'; import { userStore } from '/@/store/modules/user'; -import { closeTab } from './useTabDropdown'; -import { initAffixTabs } from './useMultipleTabs'; -import { isNullAndUnDef } from '/@/utils/is'; -import { useProjectSetting } from '/@/hooks/setting'; +import { initAffixTabs, useTabsDrag } from './useMultipleTabs'; export default defineComponent({ name: 'MultipleTabs', @@ -31,28 +24,25 @@ export default defineComponent({ const affixTextList = initAffixTabs(); - const go = useGo(); + useTabsDrag(affixTextList); - const { multiTabsSetting } = useProjectSetting(); + const go = useGo(); const { currentRoute } = useRouter(); const getTabsState = computed(() => tabStore.getTabsState); - // If you monitor routing changes, tab switching will be stuck. So setting this method watch( - () => tabStore.getLastChangeRouteState, + () => tabStore.getLastChangeRouteState?.path, () => { const lastChangeRoute = unref(tabStore.getLastChangeRouteState); - if (!lastChangeRoute || !userStore.getTokenState) return; - - const { path, fullPath } = lastChangeRoute as AppRouteRecordRaw; + const { path, fullPath } = lastChangeRoute; const p = fullPath || path; if (activeKeyRef.value !== p) { activeKeyRef.value = p; } - tabStore.commitAddTab(lastChangeRoute); + tabStore.addTabAction(lastChangeRoute); }, { immediate: true, @@ -67,22 +57,19 @@ export default defineComponent({ // Close the current tab function handleEdit(targetKey: string) { // Added operation to hide, currently only use delete operation - const index = unref(getTabsState).findIndex( - (item) => (item.fullPath || item.path) === targetKey - ); - index !== -1 && closeTab(unref(getTabsState)[index]); + tabStore.closeTabByKeyAction(targetKey); } function renderQuick() { const tabContentProps: TabContentProps = { - tabItem: (currentRoute as unknown) as AppRouteRecordRaw, + tabItem: currentRoute.value, type: TabContentEnum.EXTRA_TYPE, }; - return ; + return ; } function renderTabs() { - return unref(getTabsState).map((item: TabItem) => { + return unref(getTabsState).map((item) => { const key = item.query ? item.fullPath : item.path; const closable = !(item && item.meta && item.meta.affix); @@ -97,40 +84,6 @@ export default defineComponent({ }); } - 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(), diff --git a/src/layouts/default/multitabs/types.ts b/src/layouts/default/multitabs/types.ts new file mode 100644 index 00000000..c8b32023 --- /dev/null +++ b/src/layouts/default/multitabs/types.ts @@ -0,0 +1,35 @@ +import type { DropMenu } from '/@/components/Dropdown/index'; +import type { RouteLocationNormalized } from 'vue-router'; + +export enum TabContentEnum { + TAB_TYPE, + EXTRA_TYPE, +} + +export type { DropMenu }; + +export interface TabContentProps { + tabItem: RouteLocationNormalized; + type?: TabContentEnum; + trigger?: ('click' | 'hover' | 'contextmenu')[]; +} + +/** + * @description: 右键:下拉菜单文字 + */ +export enum MenuEventEnum { + // 刷新 + REFRESH_PAGE, + // 关闭当前 + CLOSE_CURRENT, + // 关闭左侧 + CLOSE_LEFT, + // 关闭右侧 + CLOSE_RIGHT, + // 关闭其他 + CLOSE_OTHER, + // 关闭所有 + CLOSE_ALL, + // 放大 + SCALE, +} diff --git a/src/layouts/default/multitabs/useMultipleTabs.ts b/src/layouts/default/multitabs/useMultipleTabs.ts index 69c4e914..b5a7d485 100644 --- a/src/layouts/default/multitabs/useMultipleTabs.ts +++ b/src/layouts/default/multitabs/useMultipleTabs.ts @@ -1,19 +1,22 @@ -import { toRaw, ref } from 'vue'; +import Sortable from 'sortablejs'; +import { toRaw, ref, nextTick, onMounted } from 'vue'; +import { RouteLocationNormalized } from 'vue-router'; +import { useProjectSetting } from '/@/hooks/setting'; import router from '/@/router'; -import { AppRouteRecordRaw } from '/@/router/types'; -import { TabItem, tabStore } from '/@/store/modules/tab'; +import { tabStore } from '/@/store/modules/tab'; +import { isNullAndUnDef } from '/@/utils/is'; -export function initAffixTabs() { - const affixList = ref([]); +export function initAffixTabs(): string[] { + const affixList = ref([]); /** * @description: Filter all fixed routes */ - function filterAffixTabs(routes: AppRouteRecordRaw[]) { - const tabs: TabItem[] = []; + function filterAffixTabs(routes: RouteLocationNormalized[]) { + const tabs: RouteLocationNormalized[] = []; routes && routes.forEach((route) => { if (route.meta && route.meta.affix) { - tabs.push(toRaw(route) as TabItem); + tabs.push(toRaw(route)); } }); return tabs; @@ -23,10 +26,14 @@ export function initAffixTabs() { * @description: Set fixed tabs */ function addAffixTabs(): void { - const affixTabs = filterAffixTabs((router.getRoutes() as unknown) as AppRouteRecordRaw[]); + const affixTabs = filterAffixTabs((router.getRoutes() as unknown) as RouteLocationNormalized[]); affixList.value = affixTabs; for (const tab of affixTabs) { - tabStore.commitAddTab(tab); + tabStore.addTabAction(({ + meta: tab.meta, + name: tab.name, + path: tab.path, + } as unknown) as RouteLocationNormalized); } } @@ -37,3 +44,41 @@ export function initAffixTabs() { } return affixList.value.map((item) => item.meta?.title).filter(Boolean); } + +export function useTabsDrag(affixTextList: string[]) { + const { multiTabsSetting } = useProjectSetting(); + + 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(); + }); +} diff --git a/src/layouts/default/multitabs/useTabDropdown.ts b/src/layouts/default/multitabs/useTabDropdown.ts index 9ba348cc..0e521e22 100644 --- a/src/layouts/default/multitabs/useTabDropdown.ts +++ b/src/layouts/default/multitabs/useTabDropdown.ts @@ -1,168 +1,148 @@ -import type { AppRouteRecordRaw } from '/@/router/types'; -import type { TabContentProps } from './data'; -import type { Ref } from 'vue'; -import type { TabItem } from '/@/store/modules/tab'; +import type { TabContentProps } from './types'; import type { DropMenu } from '/@/components/Dropdown'; -import { computed, unref } from 'vue'; -import { TabContentEnum, MenuEventEnum, getActions } from './data'; +import { computed, unref, reactive } from 'vue'; +import { TabContentEnum, MenuEventEnum } from './types'; import { tabStore } from '/@/store/modules/tab'; -import { appStore } from '/@/store/modules/app'; -import { PageEnum } from '/@/enums/pageEnum'; -import { useGo, useRedo } from '/@/hooks/web/usePage'; import router from '/@/router'; -import { useTabs, isInitUseTab } from '/@/hooks/web/useTabs'; -import { RouteLocationRaw } from 'vue-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'; +import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; -const { initTabFn } = useTabs(); +const { t } = useI18n(); export function useTabDropdown(tabContentProps: TabContentProps) { - const { currentRoute } = router; - const redo = useRedo(); - const go = useGo(); - - const isTabsRef = computed(() => tabContentProps.type === TabContentEnum.TAB_TYPE); - const getCurrentTab: Ref = computed(() => { - return unref(isTabsRef) - ? tabContentProps.tabItem - : ((unref(currentRoute) as any) as AppRouteRecordRaw); + const state = reactive({ + current: null as Nullable, + currentIndex: 0, }); - // Current tab list - const getTabsState = computed(() => tabStore.getTabsState); + const { currentRoute } = router; + + const { getShowMenu, setMenuSetting } = useMenuSetting(); + const { getShowHeader, setHeaderSetting } = useHeaderSetting(); + const { getShowQuick } = useMultipleTabSetting(); + + const isTabs = computed(() => + !unref(getShowQuick) ? true : tabContentProps.type === TabContentEnum.TAB_TYPE + ); + + const getCurrentTab = computed( + (): RouteLocationNormalized => { + return unref(isTabs) ? tabContentProps.tabItem : unref(currentRoute); + } + ); + + const getIsScale = computed(() => { + return !unref(getShowMenu) && !unref(getShowHeader); + }); /** * @description: drop-down list */ const getDropMenuList = computed(() => { - const dropMenuList = getActions(); - // Reset to initial state - for (const item of dropMenuList) { - item.disabled = false; - } - - // No tab - if (!unref(getTabsState) || unref(getTabsState).length <= 0) { - return dropMenuList; - } else if (unref(getTabsState).length === 1) { - // Only one tab - for (const item of dropMenuList) { - if (item.event !== MenuEventEnum.REFRESH_PAGE) { - item.disabled = true; - } - } - return dropMenuList; - } if (!unref(getCurrentTab)) return; - const { meta, path } = unref(getCurrentTab); + const { meta } = unref(getCurrentTab); + const { path } = unref(currentRoute); // Refresh button - const curItem = tabStore.getCurrentContextMenuState; - const index = tabStore.getCurrentContextMenuIndexState; + const curItem = state.current; + const index = state.currentIndex; const refreshDisabled = curItem ? curItem.path !== path : true; // Close left const closeLeftDisabled = index === 0; + const disabled = tabStore.getTabsState.length === 1; + // Close right - const closeRightDisabled = index === unref(getTabsState).length - 1; - // Currently fixed tab - // TODO PERf - dropMenuList[0].disabled = unref(isTabsRef) ? refreshDisabled : false; - if (meta && meta.affix) { - dropMenuList[1].disabled = true; + const closeRightDisabled = + index === tabStore.getTabsState.length - 1 && tabStore.getLastDragEndIndexState >= 0; + const dropMenuList: DropMenu[] = [ + { + icon: 'ant-design:reload-outlined', + event: MenuEventEnum.REFRESH_PAGE, + text: t('layout.multipleTab.redo'), + disabled: refreshDisabled, + }, + { + icon: 'ant-design:close-outlined', + event: MenuEventEnum.CLOSE_CURRENT, + text: t('layout.multipleTab.close'), + disabled: meta?.affix || disabled, + divider: true, + }, + { + icon: 'ant-design:pic-left-outlined', + event: MenuEventEnum.CLOSE_LEFT, + text: t('layout.multipleTab.closeLeft'), + disabled: closeLeftDisabled, + divider: false, + }, + { + icon: 'ant-design:pic-right-outlined', + event: MenuEventEnum.CLOSE_RIGHT, + text: t('layout.multipleTab.closeRight'), + disabled: closeRightDisabled, + divider: true, + }, + { + icon: 'ant-design:pic-center-outlined', + event: MenuEventEnum.CLOSE_OTHER, + text: t('layout.multipleTab.closeOther'), + disabled: disabled, + }, + { + icon: 'ant-design:line-outlined', + event: MenuEventEnum.CLOSE_ALL, + text: t('layout.multipleTab.closeAll'), + disabled: disabled, + }, + ]; + + 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, + }); } - dropMenuList[2].disabled = closeLeftDisabled; - dropMenuList[3].disabled = closeRightDisabled; return dropMenuList; }); - /** - * @description: Jump to page when closing all pages - */ - function gotoPage() { - const len = unref(getTabsState).length; - const { path } = unref(currentRoute); + const getTrigger = computed(() => { + return unref(isTabs) ? ['contextmenu'] : ['click']; + }); - let toPath: PageEnum | string = PageEnum.BASE_HOME; - - if (len > 0) { - const page = unref(getTabsState)[len - 1]; - const p = page.fullPath || page.path; - if (p) { - 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; - // 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); - } - } - function refreshPage(tabItem?: TabItem) { - try { - tabStore.commitCloseTabKeepAlive(tabItem || unref(getCurrentTab)); - } 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 handleContextMenu(tabItem: RouteLocationNormalized) { + return (e: Event) => { + if (!tabItem) return; + e?.preventDefault(); + const index = tabStore.getTabsState.findIndex((tab) => tab.path === tabItem.path); + state.current = tabItem; + state.currentIndex = index; + }; } function scaleScreen() { - const { - headerSetting: { show: showHeader }, - menuSetting: { show: showMenu }, - } = appStore.getProjectConfig; - const isScale = !showHeader && !showMenu; - appStore.commitProjectConfigState({ - headerSetting: { show: isScale }, - menuSetting: { show: isScale }, + const isScale = !unref(getShowMenu) && !unref(getShowHeader); + setMenuSetting({ + show: isScale, }); - } - - if (!isInitUseTab) { - initTabFn({ - refreshPageFn: refreshPage, - closeAllFn: closeAll, - closeCurrentFn: closeCurrent, - closeLeftFn: closeLeft, - closeOtherFn: closeOther, - closeRightFn: closeRight, + setHeaderSetting({ + show: isScale, }); } // Handle right click event function handleMenuEvent(menu: DropMenu): void { + const { refreshPage, closeAll, closeCurrent, closeLeft, closeOther, closeRight } = useTabs(); const { event } = menu; - switch (event) { case MenuEventEnum.SCALE: scaleScreen(); @@ -193,51 +173,5 @@ export function useTabDropdown(tabContentProps: TabContentProps) { 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; - // Current tab list - const getTabsState = computed(() => tabStore.getTabsState); - - const { path } = unref(currentRoute); - if (path !== closedTab.path) { - // Closed is not the activation tab - tabStore.commitCloseTab(closedTab); - return; - } - - // Closed is activated atb - let toObj: RouteLocationRaw = {}; - - const index = unref(getTabsState).findIndex((item) => item.path === path); - - // If the current is the leftmost tab - if (index === 0) { - // 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 { - // Jump to the right tab - const page = unref(getTabsState)[index + 1]; - toObj = getObj(page); - } - } else { - // Close the current tab - const page = unref(getTabsState)[index - 1]; - toObj = getObj(page); - } - const route = (unref(currentRoute) as unknown) as AppRouteRecordRaw; - tabStore.commitCloseTab(route); - replace(toObj); + return { getDropMenuList, handleMenuEvent, handleContextMenu, getTrigger, isTabs }; } diff --git a/src/layouts/iframe/index.vue b/src/layouts/iframe/index.vue index 0dc90496..7f7fe6b5 100644 --- a/src/layouts/iframe/index.vue +++ b/src/layouts/iframe/index.vue @@ -1,7 +1,7 @@