From b36a62b150476a2958fa19d87c9f6381a57a0792 Mon Sep 17 00:00:00 2001 From: Soybean Date: Thu, 6 Jan 2022 02:00:42 +0800 Subject: [PATCH] =?UTF-8?q?refactor(projects):=20=E5=8D=95=E7=8B=AC?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E9=80=BB=E8=BE=91=E9=87=8D=E6=9E=84=E3=80=81?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E8=BD=AC=E6=8D=A2=E5=87=BD=E6=95=B0=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mock/api/route.ts | 8 +- package.json | 4 +- pnpm-lock.yaml | 76 +- src/components/index.ts | 4 +- src/components/svg/SvgEmptyData.vue | 1447 +++++++++++++++++ src/components/svg/SvgNetworkError.vue | 408 +++++ src/components/svg/SvgNoPermission.vue | 897 ++++++++++ src/components/svg/SvgNotFound.vue | 504 ++++++ src/components/svg/SvgServiceError.vue | 1337 +++++++++++++++ src/components/svg/index.ts | 7 + src/composables/common/index.ts | 1 - src/composables/common/route.ts | 32 - src/composables/common/router.ts | 28 +- src/config/common/regexp.ts | 4 + src/interface/index.ts | 1 + src/interface/system.ts | 7 + src/layouts/Layout/index.vue | 8 +- src/main.ts | 7 +- src/router/guard/permission.ts | 21 +- src/router/index.ts | 4 +- src/router/routes/constant.ts | 24 +- src/router/routes/index.ts | 13 + src/service/api/auth.ts | 6 +- src/service/middleware/auth.ts | 10 - src/service/middleware/index.ts | 2 +- src/store/modules/auth/index.ts | 5 +- src/store/modules/route/index.ts | 55 +- src/typings/common/route.d.ts | 72 +- src/utils/common/icon.ts | 19 + src/utils/common/index.ts | 1 + src/utils/router/component.ts | 4 +- src/utils/router/helpers.ts | 137 +- src/utils/router/index.ts | 1 + src/utils/router/menu.ts | 51 + src/views/system/exception/403.vue | 6 - src/views/system/exception/404.vue | 6 - src/views/system/exception/500.vue | 6 - .../exception/components/ExceptionBase.vue | 42 + .../system/exception/components/index.ts | 3 + .../system/exception/no-permission/index.vue | 8 + .../system/exception/not-found/index.vue | 8 + .../system/exception/service-error/index.vue | 8 + src/views/system/index.ts | 6 +- .../login/components/CodeLogin/index.vue | 2 +- .../login/components/Register/index.vue | 2 +- 45 files changed, 4974 insertions(+), 328 deletions(-) create mode 100644 src/components/svg/SvgEmptyData.vue create mode 100644 src/components/svg/SvgNetworkError.vue create mode 100644 src/components/svg/SvgNoPermission.vue create mode 100644 src/components/svg/SvgNotFound.vue create mode 100644 src/components/svg/SvgServiceError.vue create mode 100644 src/components/svg/index.ts delete mode 100644 src/composables/common/route.ts create mode 100644 src/interface/system.ts delete mode 100644 src/service/middleware/auth.ts create mode 100644 src/utils/common/icon.ts create mode 100644 src/utils/router/menu.ts delete mode 100644 src/views/system/exception/403.vue delete mode 100644 src/views/system/exception/404.vue delete mode 100644 src/views/system/exception/500.vue create mode 100644 src/views/system/exception/components/ExceptionBase.vue create mode 100644 src/views/system/exception/components/index.ts create mode 100644 src/views/system/exception/no-permission/index.vue create mode 100644 src/views/system/exception/not-found/index.vue create mode 100644 src/views/system/exception/service-error/index.vue diff --git a/mock/api/route.ts b/mock/api/route.ts index 66dec8a29..efedcfc8b 100644 --- a/mock/api/route.ts +++ b/mock/api/route.ts @@ -19,7 +19,7 @@ const routes: AuthRoute.Route[] = [ path: '/dashboard/workbench', component: 'self', meta: { - title: '分析页', + title: '工作台', permissions: ['super', 'admin'] } } @@ -33,12 +33,12 @@ const routes: AuthRoute.Route[] = [ { name: 'about', path: '/about', - component: 'layout', + component: 'self', meta: { title: '关于', + singleLayout: 'layout', permissions: ['super', 'admin', 'test'], - icon: 'fluent:book-information-24-regular', - single: true + icon: 'fluent:book-information-24-regular' } }, { diff --git a/package.json b/package.json index 3d4dd80d7..ebe9c7041 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ } }, "dependencies": { - "@vueuse/core": "^7.5.1", + "@vueuse/core": "^7.5.3", "axios": "^0.24.0", "colord": "^2.9.2", "crypto-js": "^4.1.1", @@ -40,7 +40,7 @@ "@iconify/json": "^1.1.452", "@iconify/vue": "^3.1.1", "@types/crypto-js": "^4.1.0", - "@types/node": "^17.0.6", + "@types/node": "^17.0.8", "@types/qs": "^6.9.7", "@typescript-eslint/eslint-plugin": "^5.8.1", "@typescript-eslint/parser": "^5.9.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13a7f0ab3..351d5685c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,14 +6,14 @@ specifiers: '@iconify/json': ^1.1.452 '@iconify/vue': ^3.1.1 '@types/crypto-js': ^4.1.0 - '@types/node': ^17.0.6 + '@types/node': ^17.0.8 '@types/qs': ^6.9.7 '@typescript-eslint/eslint-plugin': ^5.8.1 '@typescript-eslint/parser': ^5.9.0 '@vitejs/plugin-vue': ^2.0.1 '@vue/eslint-config-prettier': ^7.0.0 '@vue/eslint-config-typescript': ^10.0.0 - '@vueuse/core': ^7.5.1 + '@vueuse/core': ^7.5.3 axios: ^0.24.0 colord: ^2.9.2 commitizen: ^4.2.4 @@ -55,7 +55,7 @@ specifiers: windicss: ^3.4.2 dependencies: - '@vueuse/core': registry.npmmirror.com/@vueuse/core/7.5.1_vue@3.2.26 + '@vueuse/core': registry.npmmirror.com/@vueuse/core/7.5.3_vue@3.2.26 axios: registry.npmmirror.com/axios/0.24.0 colord: registry.npmmirror.com/colord/2.9.2 crypto-js: registry.npmmirror.com/crypto-js/4.1.1 @@ -69,21 +69,21 @@ dependencies: vue-router: registry.npmmirror.com/vue-router/4.0.12_vue@3.2.26 devDependencies: - '@commitlint/cli': registry.npmmirror.com/@commitlint/cli/16.0.1_@types+node@17.0.6 + '@commitlint/cli': registry.npmmirror.com/@commitlint/cli/16.0.1_@types+node@17.0.8 '@commitlint/config-conventional': registry.npmmirror.com/@commitlint/config-conventional/16.0.0 '@iconify/json': registry.npmmirror.com/@iconify/json/1.1.452 '@iconify/vue': registry.npmmirror.com/@iconify/vue/3.1.1_vue@3.2.26 '@types/crypto-js': registry.npmmirror.com/@types/crypto-js/4.1.0 - '@types/node': registry.npmmirror.com/@types/node/17.0.6 + '@types/node': registry.npmmirror.com/@types/node/17.0.8 '@types/qs': registry.npmmirror.com/@types/qs/6.9.7 '@typescript-eslint/eslint-plugin': registry.npmmirror.com/@typescript-eslint/eslint-plugin/5.8.1_bd2fd93dbcc607ad2f21b784bccfe0c8 '@typescript-eslint/parser': registry.npmmirror.com/@typescript-eslint/parser/5.9.0_eslint@8.6.0+typescript@4.5.4 '@vitejs/plugin-vue': registry.npmmirror.com/@vitejs/plugin-vue/2.0.1_vite@2.7.10+vue@3.2.26 '@vue/eslint-config-prettier': registry.npmmirror.com/@vue/eslint-config-prettier/7.0.0_eslint@8.6.0+prettier@2.5.1 '@vue/eslint-config-typescript': registry.npmmirror.com/@vue/eslint-config-typescript/10.0.0_57f850728139557a3a27f1248f77f964 - commitizen: registry.npmmirror.com/commitizen/4.2.4_@types+node@17.0.6 + commitizen: registry.npmmirror.com/commitizen/4.2.4_@types+node@17.0.8 cross-env: registry.nlark.com/cross-env/7.0.3 - cz-conventional-changelog: registry.nlark.com/cz-conventional-changelog/3.3.0_@types+node@17.0.6 + cz-conventional-changelog: registry.nlark.com/cz-conventional-changelog/3.3.0_@types+node@17.0.8 cz-customizable: registry.npmmirror.com/cz-customizable/6.3.0 eslint: registry.npmmirror.com/eslint/8.6.0 eslint-config-airbnb-base: registry.npmmirror.com/eslint-config-airbnb-base/15.0.0_b5a36b8c1535387c8dd00eff7ec6b551 @@ -624,7 +624,7 @@ packages: which: registry.nlark.com/which/2.0.2 dev: true - registry.nlark.com/cz-conventional-changelog/3.2.0_@types+node@17.0.6: + registry.nlark.com/cz-conventional-changelog/3.2.0_@types+node@17.0.8: resolution: {integrity: sha1-au8fiS1kETND1+RVUpCJrJ8g5Hc=, registry: http://registry.npm.taobao.org/, tarball: https://registry.nlark.com/cz-conventional-changelog/download/cz-conventional-changelog-3.2.0.tgz} id: registry.nlark.com/cz-conventional-changelog/3.2.0 name: cz-conventional-changelog @@ -638,14 +638,14 @@ packages: longest: registry.nlark.com/longest/2.0.1 word-wrap: registry.nlark.com/word-wrap/1.2.3 optionalDependencies: - '@commitlint/load': registry.npmmirror.com/@commitlint/load/16.0.0_@types+node@17.0.6 + '@commitlint/load': registry.npmmirror.com/@commitlint/load/16.0.0_@types+node@17.0.8 transitivePeerDependencies: - '@swc/core' - '@swc/wasm' - '@types/node' dev: true - registry.nlark.com/cz-conventional-changelog/3.3.0_@types+node@17.0.6: + registry.nlark.com/cz-conventional-changelog/3.3.0_@types+node@17.0.8: resolution: {integrity: sha1-kkaUfJBAQUmz/iz37pGsrTt9ItI=, registry: http://registry.npm.taobao.org/, tarball: https://registry.nlark.com/cz-conventional-changelog/download/cz-conventional-changelog-3.3.0.tgz} id: registry.nlark.com/cz-conventional-changelog/3.3.0 name: cz-conventional-changelog @@ -653,13 +653,13 @@ packages: engines: {node: '>= 10'} dependencies: chalk: registry.npmmirror.com/chalk/2.4.2 - commitizen: registry.npmmirror.com/commitizen/4.2.4_@types+node@17.0.6 + commitizen: registry.npmmirror.com/commitizen/4.2.4_@types+node@17.0.8 conventional-commit-types: registry.nlark.com/conventional-commit-types/3.0.0 lodash.map: registry.nlark.com/lodash.map/4.6.0 longest: registry.nlark.com/longest/2.0.1 word-wrap: registry.nlark.com/word-wrap/1.2.3 optionalDependencies: - '@commitlint/load': registry.npmmirror.com/@commitlint/load/16.0.0_@types+node@17.0.6 + '@commitlint/load': registry.npmmirror.com/@commitlint/load/16.0.0_@types+node@17.0.8 transitivePeerDependencies: - '@swc/core' - '@swc/wasm' @@ -3086,7 +3086,7 @@ packages: to-fast-properties: registry.nlark.com/to-fast-properties/2.0.0 dev: true - registry.npmmirror.com/@commitlint/cli/16.0.1_@types+node@17.0.6: + registry.npmmirror.com/@commitlint/cli/16.0.1_@types+node@17.0.8: resolution: {integrity: sha512-61gGRy65WiVDRsqP0dAR2fAgE3qrTBW3fgz9MySv32y5Ib3ZXXDDq6bGyQqi2dSaPuDYzNCRwwlC7mmQM73T/g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@commitlint/cli/download/@commitlint/cli-16.0.1.tgz} id: registry.npmmirror.com/@commitlint/cli/16.0.1 name: '@commitlint/cli' @@ -3096,7 +3096,7 @@ packages: dependencies: '@commitlint/format': registry.npmmirror.com/@commitlint/format/16.0.0 '@commitlint/lint': registry.npmmirror.com/@commitlint/lint/16.0.0 - '@commitlint/load': registry.npmmirror.com/@commitlint/load/16.0.0_@types+node@17.0.6 + '@commitlint/load': registry.npmmirror.com/@commitlint/load/16.0.0_@types+node@17.0.8 '@commitlint/read': registry.npmmirror.com/@commitlint/read/16.0.0 '@commitlint/types': registry.npmmirror.com/@commitlint/types/16.0.0 lodash: registry.npmmirror.com/lodash/4.17.21 @@ -3177,7 +3177,7 @@ packages: '@commitlint/types': registry.npmmirror.com/@commitlint/types/16.0.0 dev: true - registry.npmmirror.com/@commitlint/load/16.0.0_@types+node@17.0.6: + registry.npmmirror.com/@commitlint/load/16.0.0_@types+node@17.0.8: resolution: {integrity: sha512-7WhrGCkP6K/XfjBBguLkkI2XUdiiIyMGlNsSoSqgRNiD352EiffhFEApMy1/XOU+viwBBm/On0n5p0NC7e9/4A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@commitlint/load/download/@commitlint/load-16.0.0.tgz} id: registry.npmmirror.com/@commitlint/load/16.0.0 name: '@commitlint/load' @@ -3190,7 +3190,7 @@ packages: '@commitlint/types': registry.npmmirror.com/@commitlint/types/16.0.0 chalk: registry.npmmirror.com/chalk/4.1.2 cosmiconfig: registry.nlark.com/cosmiconfig/7.0.1 - cosmiconfig-typescript-loader: registry.npmmirror.com/cosmiconfig-typescript-loader/1.0.2_646584a8d620b4d6f5eb4525e8655565 + cosmiconfig-typescript-loader: registry.npmmirror.com/cosmiconfig-typescript-loader/1.0.2_faf01e1d5a40372a98081522dcafc186 lodash: registry.npmmirror.com/lodash/4.17.21 resolve-from: registry.nlark.com/resolve-from/5.0.0 typescript: registry.npmmirror.com/typescript/4.5.4 @@ -3403,7 +3403,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': registry.npmmirror.com/@types/istanbul-lib-coverage/2.0.4 '@types/istanbul-reports': registry.npmmirror.com/@types/istanbul-reports/3.0.1 - '@types/node': registry.npmmirror.com/@types/node/17.0.6 + '@types/node': registry.npmmirror.com/@types/node/17.0.8 '@types/yargs': registry.npmmirror.com/@types/yargs/16.0.4 chalk: registry.npmmirror.com/chalk/4.1.2 @@ -3522,10 +3522,10 @@ packages: name: '@types/node' version: 14.14.45 - registry.npmmirror.com/@types/node/17.0.6: - resolution: {integrity: sha512-+XBAjfZmmivILUzO0HwBJoYkAyyySSLg5KCGBDFLomJo0sV6szvVLAf4ANZZ0pfWzgEds5KmGLG9D5hfEqOhaA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/node/download/@types/node-17.0.6.tgz} + registry.npmmirror.com/@types/node/17.0.8: + resolution: {integrity: sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/node/download/@types/node-17.0.8.tgz} name: '@types/node' - version: 17.0.6 + version: 17.0.8 registry.npmmirror.com/@types/normalize-package-data/2.4.1: resolution: {integrity: sha1-0zV0eaD9/dWQf+Z+F+CoXJBuEwE=, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/normalize-package-data/download/@types/normalize-package-data-2.4.1.tgz} @@ -3550,7 +3550,7 @@ packages: name: '@types/resolve' version: 1.17.1 dependencies: - '@types/node': registry.npmmirror.com/@types/node/17.0.6 + '@types/node': registry.npmmirror.com/@types/node/17.0.8 dev: true registry.npmmirror.com/@types/throttle-debounce/2.1.0: @@ -3976,11 +3976,11 @@ packages: name: '@vue/shared' version: 3.2.26 - registry.npmmirror.com/@vueuse/core/7.5.1_vue@3.2.26: - resolution: {integrity: sha512-GczfdTWqH483zkUHdDYiLm0Tn51OtsQXYc+eBKKIeolh0mgn682KbSYmkrjNytaF7qGc74YxMDAYjkPBW6V2Pg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vueuse/core/download/@vueuse/core-7.5.1.tgz} - id: registry.npmmirror.com/@vueuse/core/7.5.1 + registry.npmmirror.com/@vueuse/core/7.5.3_vue@3.2.26: + resolution: {integrity: sha512-D9j5ymHFMFRXQqCp0yZJkf/bvBGiz0MrKUa364p+L8dMyd5zyq2K1JmHyvoBd4xbTFRfmQ1h878u6YE5LCkDVQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vueuse/core/download/@vueuse/core-7.5.3.tgz} + id: registry.npmmirror.com/@vueuse/core/7.5.3 name: '@vueuse/core' - version: 7.5.1 + version: 7.5.3 peerDependencies: '@vue/composition-api': ^1.1.0 vue: ^2.6.0 || ^3.2.0 @@ -3990,16 +3990,16 @@ packages: vue: optional: true dependencies: - '@vueuse/shared': registry.npmmirror.com/@vueuse/shared/7.5.1_vue@3.2.26 + '@vueuse/shared': registry.npmmirror.com/@vueuse/shared/7.5.3_vue@3.2.26 vue: registry.npmmirror.com/vue/3.2.26 vue-demi: registry.npmmirror.com/vue-demi/0.12.1_vue@3.2.26 dev: false - registry.npmmirror.com/@vueuse/shared/7.5.1_vue@3.2.26: - resolution: {integrity: sha512-zMQEuYJyTmr5Hj2rYgSbb4H/cSI8mdaa9dUuw20j6rPV+xLV11y7vCyIkxo31uODDr0p77FMlProKzNDiK9rAA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vueuse/shared/download/@vueuse/shared-7.5.1.tgz} - id: registry.npmmirror.com/@vueuse/shared/7.5.1 + registry.npmmirror.com/@vueuse/shared/7.5.3_vue@3.2.26: + resolution: {integrity: sha512-BJ71cxHN5VByW1S58Gl85NFJaQu93F7Vs7K/MuAKsIIuHm9PBbkR5Vxkg9ko9cBdiKVt+FNoo13BhdbA+Vwycg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vueuse/shared/download/@vueuse/shared-7.5.3.tgz} + id: registry.npmmirror.com/@vueuse/shared/7.5.3 name: '@vueuse/shared' - version: 7.5.1 + version: 7.5.3 peerDependencies: '@vue/composition-api': ^1.1.0 vue: ^2.6.0 || ^3.2.0 @@ -4283,7 +4283,7 @@ packages: hasBin: true dependencies: cachedir: registry.nlark.com/cachedir/2.2.0 - cz-conventional-changelog: registry.nlark.com/cz-conventional-changelog/3.2.0_@types+node@17.0.6 + cz-conventional-changelog: registry.nlark.com/cz-conventional-changelog/3.2.0_@types+node@17.0.8 dedent: registry.nlark.com/dedent/0.7.0 detect-indent: registry.nlark.com/detect-indent/6.0.0 find-node-modules: registry.nlark.com/find-node-modules/2.1.2 @@ -4298,7 +4298,7 @@ packages: strip-json-comments: registry.nlark.com/strip-json-comments/3.0.1 dev: true - registry.npmmirror.com/commitizen/4.2.4_@types+node@17.0.6: + registry.npmmirror.com/commitizen/4.2.4_@types+node@17.0.8: resolution: {integrity: sha512-LlZChbDzg3Ir3O2S7jSo/cgWp5/QwylQVr59K4xayVq8S4/RdKzSyJkghAiZZHfhh5t4pxunUoyeg0ml1q/7aw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/commitizen/download/commitizen-4.2.4.tgz} id: registry.npmmirror.com/commitizen/4.2.4 name: commitizen @@ -4307,7 +4307,7 @@ packages: hasBin: true dependencies: cachedir: registry.nlark.com/cachedir/2.2.0 - cz-conventional-changelog: registry.nlark.com/cz-conventional-changelog/3.2.0_@types+node@17.0.6 + cz-conventional-changelog: registry.nlark.com/cz-conventional-changelog/3.2.0_@types+node@17.0.8 dedent: registry.nlark.com/dedent/0.7.0 detect-indent: registry.nlark.com/detect-indent/6.0.0 find-node-modules: registry.nlark.com/find-node-modules/2.1.2 @@ -4358,7 +4358,7 @@ packages: through2: registry.nlark.com/through2/4.0.2 dev: true - registry.npmmirror.com/cosmiconfig-typescript-loader/1.0.2_646584a8d620b4d6f5eb4525e8655565: + registry.npmmirror.com/cosmiconfig-typescript-loader/1.0.2_faf01e1d5a40372a98081522dcafc186: resolution: {integrity: sha512-27ZehvijYqAKVzta5xtZBS3PAliC8CmnWkGXN0vgxAZz7yqxpMjf3aG7flxF5rEiu8FAD7nZZXtOI+xUGn+bVg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cosmiconfig-typescript-loader/download/cosmiconfig-typescript-loader-1.0.2.tgz} id: registry.npmmirror.com/cosmiconfig-typescript-loader/1.0.2 name: cosmiconfig-typescript-loader @@ -4368,9 +4368,9 @@ packages: '@types/node': '*' typescript: '>=3' dependencies: - '@types/node': registry.npmmirror.com/@types/node/17.0.6 + '@types/node': registry.npmmirror.com/@types/node/17.0.8 cosmiconfig: registry.nlark.com/cosmiconfig/7.0.1 - ts-node: registry.npmmirror.com/ts-node/10.4.0_646584a8d620b4d6f5eb4525e8655565 + ts-node: registry.npmmirror.com/ts-node/10.4.0_faf01e1d5a40372a98081522dcafc186 typescript: registry.npmmirror.com/typescript/4.5.4 transitivePeerDependencies: - '@swc/core' @@ -6100,7 +6100,7 @@ packages: version: 0.3.9 dev: false - registry.npmmirror.com/ts-node/10.4.0_646584a8d620b4d6f5eb4525e8655565: + registry.npmmirror.com/ts-node/10.4.0_faf01e1d5a40372a98081522dcafc186: resolution: {integrity: sha1-aA+IlFiF9ObPRQ5/DWIj3UBIlfc=, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ts-node/download/ts-node-10.4.0.tgz?cache=0&sync_timestamp=1636438781562&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fts-node%2Fdownload%2Fts-node-10.4.0.tgz} id: registry.npmmirror.com/ts-node/10.4.0 name: ts-node @@ -6122,7 +6122,7 @@ packages: '@tsconfig/node12': registry.nlark.com/@tsconfig/node12/1.0.9 '@tsconfig/node14': registry.nlark.com/@tsconfig/node14/1.0.1 '@tsconfig/node16': registry.nlark.com/@tsconfig/node16/1.0.2 - '@types/node': registry.npmmirror.com/@types/node/17.0.6 + '@types/node': registry.npmmirror.com/@types/node/17.0.8 acorn: registry.npmmirror.com/acorn/8.7.0 acorn-walk: registry.nlark.com/acorn-walk/8.2.0 arg: registry.npmmirror.com/arg/4.1.3 diff --git a/src/components/index.ts b/src/components/index.ts index 814c5b360..76e372d76 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,4 +1,4 @@ -export * from './common'; -export * from './common'; export * from './custom'; +export * from './svg'; +export * from './common'; export * from './business'; diff --git a/src/components/svg/SvgEmptyData.vue b/src/components/svg/SvgEmptyData.vue new file mode 100644 index 000000000..9cbf8fb1d --- /dev/null +++ b/src/components/svg/SvgEmptyData.vue @@ -0,0 +1,1447 @@ + + + + diff --git a/src/components/svg/SvgNetworkError.vue b/src/components/svg/SvgNetworkError.vue new file mode 100644 index 000000000..b2882c765 --- /dev/null +++ b/src/components/svg/SvgNetworkError.vue @@ -0,0 +1,408 @@ + + + + diff --git a/src/components/svg/SvgNoPermission.vue b/src/components/svg/SvgNoPermission.vue new file mode 100644 index 000000000..854b7760b --- /dev/null +++ b/src/components/svg/SvgNoPermission.vue @@ -0,0 +1,897 @@ + + + + diff --git a/src/components/svg/SvgNotFound.vue b/src/components/svg/SvgNotFound.vue new file mode 100644 index 000000000..53f921c34 --- /dev/null +++ b/src/components/svg/SvgNotFound.vue @@ -0,0 +1,504 @@ + + + + diff --git a/src/components/svg/SvgServiceError.vue b/src/components/svg/SvgServiceError.vue new file mode 100644 index 000000000..f9eee8813 --- /dev/null +++ b/src/components/svg/SvgServiceError.vue @@ -0,0 +1,1337 @@ + + + + diff --git a/src/components/svg/index.ts b/src/components/svg/index.ts new file mode 100644 index 000000000..38ca609f5 --- /dev/null +++ b/src/components/svg/index.ts @@ -0,0 +1,7 @@ +import SvgNoPermission from './SvgNoPermission.vue'; +import SvgNotFound from './SvgNotFound.vue'; +import SvgServiceError from './SvgServiceError.vue'; +import SvgEmptyData from './SvgEmptyData.vue'; +import SvgNetworkError from './SvgNetworkError.vue'; + +export { SvgNoPermission, SvgNotFound, SvgServiceError, SvgEmptyData, SvgNetworkError }; diff --git a/src/composables/common/index.ts b/src/composables/common/index.ts index 7c20b36c7..f2a9ec806 100644 --- a/src/composables/common/index.ts +++ b/src/composables/common/index.ts @@ -1,3 +1,2 @@ export * from './system'; export * from './router'; -export * from './route'; diff --git a/src/composables/common/route.ts b/src/composables/common/route.ts deleted file mode 100644 index 860e9f360..000000000 --- a/src/composables/common/route.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { unref, computed } from 'vue'; -import { useRoute } from 'vue-router'; -import { router } from '@/router'; -import { useRouteStore } from '@/store'; - -/** - * 路由查询参数 - * @param inSetup - 是否在vue页面/组件的setup里面调用,在axios里面无法使用useRouter和useRoute - */ -export function useRouteQuery(inSetup: boolean = true) { - const { getRouteName } = useRouteStore(); - - const route = getRouteInstance(inSetup); - - /** 登录后跳转的地址 */ - const loginRedirect = computed(() => { - let url: string | undefined; - if (route.name === getRouteName('login')) { - url = (route.query?.redirect as string) || ''; - } - return url; - }); - - return { - loginRedirect - }; -} - -function getRouteInstance(inSetup: boolean = true) { - const route = inSetup ? useRoute() : unref(router.currentRoute); - return route; -} diff --git a/src/composables/common/router.ts b/src/composables/common/router.ts index 09b8d4d32..d14680750 100644 --- a/src/composables/common/router.ts +++ b/src/composables/common/router.ts @@ -1,8 +1,6 @@ -import { unref } from 'vue'; -import { useRouter, useRoute } from 'vue-router'; +import { useRouter } from 'vue-router'; import type { RouteLocationRaw } from 'vue-router'; -import { router as globalRouter } from '@/router'; -import { useRouteStore } from '@/store'; +import { router as globalRouter, routeName } from '@/router'; import { LoginModuleKey } from '@/interface'; /** @@ -10,10 +8,8 @@ import { LoginModuleKey } from '@/interface'; * @param inSetup - 是否在vue页面/组件的setup里面调用,在axios里面无法使用useRouter和useRoute */ export function useRouterPush(inSetup: boolean = true) { - const { getRouteName } = useRouteStore(); - const router = inSetup ? useRouter() : globalRouter; - const route = inSetup ? useRoute() : unref(globalRouter.currentRoute); + const route = globalRouter.currentRoute; /** * 路由跳转 @@ -39,7 +35,7 @@ export function useRouterPush(inSetup: boolean = true) { * @param newTab - 在新的浏览器标签打开 */ function toHome(newTab = false) { - routerPush(getRouteName('root'), newTab); + routerPush({ name: routeName('root') }, newTab); } /** @@ -50,10 +46,10 @@ export function useRouterPush(inSetup: boolean = true) { function toLogin(loginModule?: LoginModuleKey, redirectUrl?: string) { const module: LoginModuleKey = loginModule || 'pwd-login'; const routeLocation: RouteLocationRaw = { - name: getRouteName('login'), + name: routeName('login'), params: { module } }; - const redirect = redirectUrl || route.fullPath; + const redirect = redirectUrl || route.value.fullPath; Object.assign(routeLocation, { query: { redirect } }); routerPush(routeLocation); } @@ -63,17 +59,17 @@ export function useRouterPush(inSetup: boolean = true) { * @param module - 切换后的登录模块 */ function toLoginModule(module: LoginModuleKey) { - const { query } = route; - routerPush({ name: getRouteName('login'), params: { module }, query }); + const { query } = route.value; + routerPush({ name: routeName('login'), params: { module }, query }); } /** * 登录成功后跳转重定向的地址 - * @param redirect - 重定向地址 */ - function toLoginRedirect(redirect?: string) { - if (redirect) { - routerPush(redirect); + function toLoginRedirect() { + const { query } = route.value; + if (query?.redirect) { + routerPush(query.redirect as string); } else { toHome(); } diff --git a/src/config/common/regexp.ts b/src/config/common/regexp.ts index 847a5eb9d..b94e15c48 100644 --- a/src/config/common/regexp.ts +++ b/src/config/common/regexp.ts @@ -14,3 +14,7 @@ export const REGEXP_CODE_SIX = /^\d{6}$/; /** 4位数字验证码正则 */ export const REGEXP_CODE_FOUR = /^\d{4}$/; + +/** url链接正则 */ +export const REGEXP_URL = + /(((^https?:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)$/; diff --git a/src/interface/index.ts b/src/interface/index.ts index bdd505d9c..0e406d1c6 100644 --- a/src/interface/index.ts +++ b/src/interface/index.ts @@ -1 +1,2 @@ export * from './enum'; +export * from './system'; diff --git a/src/interface/system.ts b/src/interface/system.ts new file mode 100644 index 000000000..5c3fc30f6 --- /dev/null +++ b/src/interface/system.ts @@ -0,0 +1,7 @@ +import type { MenuOption } from 'naive-ui'; + +/** 菜单项配置 */ +export type GlobalMenuOption = MenuOption & { + routeName: string; + routePath: string; +}; diff --git a/src/layouts/Layout/index.vue b/src/layouts/Layout/index.vue index 3acf77106..aba3fdc18 100644 --- a/src/layouts/Layout/index.vue +++ b/src/layouts/Layout/index.vue @@ -2,11 +2,15 @@
- +
- + diff --git a/src/main.ts b/src/main.ts index 5a8af4fc2..57f109e6d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,10 @@ import AppProvider from './AppProvider.vue'; import App from './App.vue'; async function setupApp() { + // 初始化加载的svg logo + setupInitSvgLogo('#loadingLogo'); + + // 引入静态资源 setupAssets(); // 挂载 appProvider 解决路由守卫,Axios中可使用,LoadingBar,Dialog,Message 等之类组件 @@ -13,9 +17,6 @@ async function setupApp() { setupStore(appProvider); appProvider.mount('#appProvider'); - // 初始化加载的svg logo - setupInitSvgLogo('#loadingLogo'); - const app = createApp(App); setupStore(app); diff --git a/src/router/guard/permission.ts b/src/router/guard/permission.ts index 4eea1734b..ce48e3d72 100644 --- a/src/router/guard/permission.ts +++ b/src/router/guard/permission.ts @@ -1,4 +1,5 @@ import type { Router, RouteLocationNormalized, NavigationGuardNext } from 'vue-router'; +import { routeName } from '@/router'; import { useAuthStore, useRouteStore } from '@/store'; import { exeStrategyActions } from '@/utils'; @@ -11,7 +12,7 @@ export async function handlePagePermission( ) { const auth = useAuthStore(); const route = useRouteStore(); - const { initDynamicRoute, getRouteName } = useRouteStore(); + const { initDynamicRoute } = useRouteStore(); const permissions = to.meta.permissions || []; const needLogin = Boolean(to.meta?.requiresAuth) || Boolean(permissions.length); @@ -21,19 +22,25 @@ export async function handlePagePermission( // 添加动态路由 await initDynamicRoute(router); - if (to.name === getRouteName('redirect-not-found')) { - // 动态路由没有加载导致重定向到了redirect-not-found,等待动态路由加载好了,回到重定向之前的路由 + if (to.name === routeName('not-found-page')) { + // 动态路由没有加载导致被not-found-page路由捕获,等待动态路由加载好了,回到之前的路由 next({ path: to.fullPath, replace: true, query: to.query }); return; } } + // 动态路由已经加载,仍然未找到,重定向到not-found + if (to.name === routeName('not-found-page')) { + next({ name: routeName('not-found'), replace: true }); + return; + } + const actions: Common.StrategyAction[] = [ // 已登录状态跳转登录页,跳转至首页 [ - auth.isLogin && to.name === getRouteName('login'), + auth.isLogin && to.name === routeName('login'), () => { - next({ name: getRouteName('root') }); + next({ name: routeName('root') }); } ], // 不需要登录权限的页面直接通行 @@ -48,7 +55,7 @@ export async function handlePagePermission( !auth.isLogin && needLogin, () => { const redirect = to.fullPath; - next({ name: getRouteName('login'), query: { redirect } }); + next({ name: routeName('login'), query: { redirect } }); } ], // 登录状态进入需要登录权限的页面,有权限直接通行 @@ -62,7 +69,7 @@ export async function handlePagePermission( // 登录状态进入需要登录权限的页面,无权限,重定向到无权限页面 auth.isLogin && needLogin && !hasPermission, () => { - next({ name: getRouteName('no-permission') }); + next({ name: routeName('no-permission') }); } ] ]; diff --git a/src/router/index.ts b/src/router/index.ts index 492a18b98..d33f55afb 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,6 +1,6 @@ import type { App } from 'vue'; import { createRouter, createWebHistory } from 'vue-router'; -import { routes, constantRoutes } from './routes'; +import { routes } from './routes'; import { createRouterGuide } from './guard'; export const router = createRouter({ @@ -15,4 +15,4 @@ export async function setupRouter(app: App) { await router.isReady(); } -export { constantRoutes }; +export * from './routes'; diff --git a/src/router/routes/constant.ts b/src/router/routes/constant.ts index d9ff75c39..c49933f9c 100644 --- a/src/router/routes/constant.ts +++ b/src/router/routes/constant.ts @@ -13,8 +13,8 @@ const constantRoutes: AuthRoute.Route[] = [ }, { name: 'login', - path: `/login/:module(${getLoginModuleRegExp()})?`, - component: 'blank', + path: '/login', + component: 'self', props: route => { const moduleType = (route.params.module as LoginModuleKey) || 'pwd-login'; return { @@ -23,45 +23,45 @@ const constantRoutes: AuthRoute.Route[] = [ }, meta: { title: '登录', - single: true, - singleOriginPath: '/login' + dynamicPath: `/login/:module(${getLoginModuleRegExp()})?`, + singleLayout: 'blank' } }, { name: 'no-permission', path: '/no-permission', - component: 'blank', + component: 'self', meta: { title: '无权限', - single: true + singleLayout: 'blank' } }, { name: 'not-found', path: '/not-found', - component: 'blank', + component: 'self', meta: { title: '未找到', - single: true + singleLayout: 'blank' } }, { name: 'service-error', path: '/service-error', - component: 'blank', + component: 'self', meta: { title: '服务器错误', - single: true + singleLayout: 'blank' } }, // 匹配无效的路径重定向not-found的页面 { - name: 'redirect-not-found', + name: 'not-found-page', path: '/:pathMatch(.*)*', component: 'blank', meta: { title: '未找到', - single: true + singleLayout: 'blank' } } ]; diff --git a/src/router/routes/index.ts b/src/router/routes/index.ts index 3f133e4a0..9327b3b21 100644 --- a/src/router/routes/index.ts +++ b/src/router/routes/index.ts @@ -5,4 +5,17 @@ import constantRoutes from './constant'; /** 所有路由 */ export const routes: RouteRecordRaw[] = constantRoutes.map(item => transformAuthRouteToVueRoute(item)); +/** 路由名称 */ +export const routeName = (key: AuthRoute.RouteKey) => key; + +/** 路由路径 */ +export function routePath(key: Exclude): AuthRoute.RoutePath { + const rootPath: AuthRoute.RoutePath = '/'; + if (key === 'root') return rootPath; + const splitMark: AuthRoute.RouteSplitMark = '_'; + const pathSplitMark = '/'; + const path = key.split(splitMark).join(pathSplitMark); + return (pathSplitMark + path) as AuthRoute.RoutePath; +} + export { constantRoutes }; diff --git a/src/service/api/auth.ts b/src/service/api/auth.ts index 2e6e6f047..a7d7db9b8 100644 --- a/src/service/api/auth.ts +++ b/src/service/api/auth.ts @@ -1,5 +1,4 @@ import { mockRequest } from '../request'; -import { userRoutesMiddleware } from '../middleware'; /** * 获取验证码 @@ -33,7 +32,6 @@ export function fetchUserInfo() { * @param userId - 用户id * @description 后端根据用户id查询到对应的角色类型,并将路由筛选出对应角色的路由数据返回前端 */ -export async function fetchUserRoutes(userId: string = 'soybean') { - const { data } = await mockRequest.post('/getUserRoutes', { userId }); - return userRoutesMiddleware(data); +export function fetchUserRoutes(userId: string = 'soybean') { + return mockRequest.post('/getUserRoutes', { userId }); } diff --git a/src/service/middleware/auth.ts b/src/service/middleware/auth.ts deleted file mode 100644 index 78302dd7b..000000000 --- a/src/service/middleware/auth.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; -import { transformAuthRouteToVueRoute } from '@/utils'; - -export function userRoutesMiddleware(data: ApiRoute.Route | null) { - if (!data) return []; - - const routes: RouteRecordRaw[] = data.routes.map(item => transformAuthRouteToVueRoute(item)); - - return routes; -} diff --git a/src/service/middleware/index.ts b/src/service/middleware/index.ts index 269586ee8..cb0ff5c3b 100644 --- a/src/service/middleware/index.ts +++ b/src/service/middleware/index.ts @@ -1 +1 @@ -export * from './auth'; +export {}; diff --git a/src/store/modules/auth/index.ts b/src/store/modules/auth/index.ts index 31e86beac..99632bb86 100644 --- a/src/store/modules/auth/index.ts +++ b/src/store/modules/auth/index.ts @@ -2,7 +2,7 @@ import { ref, computed, reactive, unref } from 'vue'; import type { Ref, ComputedRef } from 'vue'; import { defineStore } from 'pinia'; import { router as globalRouter } from '@/router'; -import { useRouterPush, useRouteQuery } from '@/composables'; +import { useRouterPush } from '@/composables'; import { useLoading } from '@/hooks'; import { fetchLogin, fetchUserInfo } from '@/service'; import { getUserInfo, getToken, setUserInfo, setToken, setRefreshToken, clearAuthStorage } from '@/utils'; @@ -32,7 +32,6 @@ interface AuthStore { export const useAuthStore = defineStore('auth-store', () => { const { toLogin, toLoginRedirect } = useRouterPush(false); - const { loginRedirect } = useRouteQuery(false); const { loading: loginLoding, startLoading: startLoginLoading, endLoading: endLoginLoading } = useLoading(); const userInfo: Auth.UserInfo = reactive(getUserInfo()); @@ -82,7 +81,7 @@ export const useAuthStore = defineStore('auth-store', () => { handleSetToken(token); handleSetUserInfo(data); // 3. 跳转登录后的地址 - toLoginRedirect(loginRedirect.value); + toLoginRedirect(); // 4.登录成功弹出欢迎提示 window.$notification?.success({ title: '登录成功!', diff --git a/src/store/modules/route/index.ts b/src/store/modules/route/index.ts index f7e2277dc..a3caaab6e 100644 --- a/src/store/modules/route/index.ts +++ b/src/store/modules/route/index.ts @@ -2,10 +2,10 @@ import { ref } from 'vue'; import type { Ref } from 'vue'; import type { Router } from 'vue-router'; import { defineStore } from 'pinia'; -import { constantRoutes } from '@/router'; import { useBoolean } from '@/hooks'; import { fetchUserRoutes } from '@/service'; -import { findAuthRouteByKey } from '@/utils'; +import { transformAuthRouteToMenu, transformAuthRouteToVueRoute } from '@/utils'; +import type { GlobalMenuOption } from '@/interface'; /** 路由状态 */ interface RouteStore { @@ -17,18 +17,6 @@ interface RouteStore { isAddedDynamicRoute: Ref; /** 初始化动态路由 */ initDynamicRoute(router: Router): Promise; - /** - * 获取路由名称 - * @description getRouteName 和 getRoutePath 优先使用 getRouteName - */ - getRouteName(key: AuthRoute.RouteKey): AuthRoute.RouteKey; - /** - * 获取路由路径 - * @description getRouteName 和 getRoutePath 优先使用 getRouteName - */ - getRoutePath(key: AuthRoute.RouteKey): AuthRoute.RoutePath | undefined; - /** 获取路由路径 */ - getRouteTitle(key: AuthRoute.RouteKey): string | undefined; } export const useRouteStore = defineStore('route-store', () => { @@ -37,37 +25,32 @@ export const useRouteStore = defineStore('route-store', () => { routes.value = data; } + const menus = ref([]) as Ref; + function getMenus(data: AuthRoute.Route[]) { + const transform = transformAuthRouteToMenu(data); + menus.value = transform; + } + const { bool: isAddedDynamicRoute, setTrue: setAddedDynamicRoute } = useBoolean(); async function initDynamicRoute(router: Router) { - const routes = await fetchUserRoutes(); - routes.forEach(route => { - router.addRoute(route); - }); - setAddedDynamicRoute(); - } + const { data } = await fetchUserRoutes(); + if (data) { + getMenus(data.routes); - function getRouteName(key: AuthRoute.RouteKey) { - return key; - } - function getRoutePath(key: AuthRoute.RouteKey) { - const allRoutes = [...constantRoutes, ...routes.value]; - const item = findAuthRouteByKey(key, allRoutes); - return item?.path; - } - function getRouteTitle(key: AuthRoute.RouteKey) { - const allRoutes = [...constantRoutes, ...routes.value]; - const item = findAuthRouteByKey(key, allRoutes); - return item?.meta?.title; + const vueRoutes = data.routes.map(route => transformAuthRouteToVueRoute(route)); + vueRoutes.forEach(route => { + router.addRoute(route); + }); + + setAddedDynamicRoute(); + } } const routeStore: RouteStore = { routes, setRoutes, isAddedDynamicRoute, - initDynamicRoute, - getRouteName, - getRoutePath, - getRouteTitle + initDynamicRoute }; return routeStore; diff --git a/src/typings/common/route.d.ts b/src/typings/common/route.d.ts index 40b0d2d09..6d69bb87e 100644 --- a/src/typings/common/route.d.ts +++ b/src/typings/common/route.d.ts @@ -1,14 +1,17 @@ /** 权限路由相关类型 */ declare namespace AuthRoute { + /** 多级路由分割符号 */ + type RouteSplitMark = '_'; + /** 路由的key */ type RouteKey = // 固定的路由 - | 'root' + | 'root' // 根路由 | 'login' | 'not-found' | 'no-permission' | 'service-error' - | 'redirect-not-found' // 重定向not-found + | 'not-found-page' // 捕获无效path的路由 // 自定义路由 | 'dashboard' | 'dashboard_analysis' @@ -18,18 +21,13 @@ declare namespace AuthRoute { | 'multi-menu_first_second' | 'about'; - /** 路由路径 */ - type RoutePath = + /** 路由的path */ + type RoutePath = | '/' - | Exclude, '/root' | '/redirect'> + | Exclude, '/root' | 'not-found-page'> | SingleRouteParentPath - | Key - | '/:path(.*)*' | '/:pathMatch(.*)*'; - /** 多级路由分割符号 */ - type RouteSplitMark = '_'; - /** * 路由的组件 * - layout - 基础布局,具有公共部分的布局 @@ -43,6 +41,10 @@ declare namespace AuthRoute { type RouteMeta = { /** 路由标题(可用来作document.title或者菜单的名称) */ title: string; + /** 路由的动态路径 */ + dynamicPath?: PathToDynamicPath<'/login'>; + /** 作为单独路由的父级路由布局组件 */ + singleLayout?: Extract; /** 需要登录权限 */ requiresAuth?: boolean; /** 哪些类型的用户有权限才能访问的路由 */ @@ -55,23 +57,16 @@ declare namespace AuthRoute { icon?: string; /** 是否在菜单中隐藏 */ hide?: boolean; - /** 是否作为单独的路由(作为菜单时只有自身,没有子菜单) */ - single?: boolean; - /** 作为单独的路由且path为动态path的原始path */ - singleOriginPath?: SingleRoutePath; /** 路由顺序,可用于菜单的排序 */ order?: number; }; - /** 登录路由路径 */ - type LoginPath = `/login/:module(${string})?`; - /** 单个路由的类型结构(后端返回此类型结构的路由) */ - interface Route { + interface Route { /** 路由名称(路由唯一标识) */ name: RouteKey; /** 路由路径 */ - path: RoutePath; + path: RoutePath; /** 路由重定向 */ redirect?: RoutePath; /** @@ -86,30 +81,41 @@ declare namespace AuthRoute { children?: Route[]; /** 路由描述 */ meta: RouteMeta; - /** 属性 */ + /** 路由属性 */ props?: boolean | Record | ((to: any) => Record); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - type GetMultiRouteParentKey = Key extends `${infer Left}_${infer Right}` ? Left : never; - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - type GetSingleRouteKey = Key extends `${infer Left}_${infer Right}` ? never : Key; - - /** 单独一级路由的key (单独路由需要添加一个父路由用于应用布局组件) */ + /** 单独一级路由的key (单独路由需要添加一个父级路由用于应用布局组件) */ type SingleRouteKey = Exclude< GetSingleRouteKey, - GetMultiRouteParentKey | 'root' | 'redirect-not-found' + GetMultiRouteParentKey | 'root' | 'not-found-page' >; - - /** 单独路由需要添加一个父路由用于应用布局组件 */ + /** 单独路由父级路由key */ type SingleRouteParentKey = `${SingleRouteKey}-parent`; + /** 单独路由path */ + type SingleRoutePath = KeyToPath; + + /** 单独路由父级路由path */ + type SingleRouteParentPath = KeyToPath; + /** 路由key转换路由path */ - type KeyToPath = Key extends `${infer Left}_${infer Right}` + type KeyToPath = Key extends `${infer Left}_${infer Right}` ? KeyToPath<`${Left}/${Right}`> : `/${Key}`; - type SingleRoutePath = KeyToPath; - type SingleRouteParentPath = KeyToPath; + /** 路由path转换动态路径 */ + type PathToDynamicPath = + | `${Path}/:module` + | `${Path}/:module(${string})` + | `${Path}/:module(${string})?`; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + type GetSingleRouteKey = Key extends `${infer Left}${RouteSplitMark}${infer Right}` + ? never + : Key; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + type GetMultiRouteParentKey = Key extends `${infer Left}${RouteSplitMark}${infer Right}` + ? Left + : never; } diff --git a/src/utils/common/icon.ts b/src/utils/common/icon.ts new file mode 100644 index 000000000..416a7087c --- /dev/null +++ b/src/utils/common/icon.ts @@ -0,0 +1,19 @@ +import { h } from 'vue'; +import { Icon } from '@iconify/vue'; + +/** + * 动态渲染iconify + * @param icon - 图标名称 + * @param color - 图标颜色 + * @param size - 图标大小 + */ +export function iconifyRender(icon: string, color?: string, size?: number) { + const style: { color?: string; size?: string } = {}; + if (color) { + style.color = color; + } + if (size) { + style.size = `${size}px`; + } + return () => h(Icon, { icon, style }); +} diff --git a/src/utils/common/index.ts b/src/utils/common/index.ts index 9cd5747f6..f2a056ae1 100644 --- a/src/utils/common/index.ts +++ b/src/utils/common/index.ts @@ -2,4 +2,5 @@ export * from './typeof'; export * from './console'; export * from './color'; export * from './number'; +export * from './icon'; export * from './design-pattern'; diff --git a/src/utils/router/component.ts b/src/utils/router/component.ts index 3d4b15e4f..36ff49334 100644 --- a/src/utils/router/component.ts +++ b/src/utils/router/component.ts @@ -30,7 +30,7 @@ export function getViewComponent(routeKey: AuthRoute.RouteKey) { 'dashboard_workbench', 'about', 'multi-menu_first_second', - 'redirect-not-found' + 'not-found-page' ]; const key = keys.includes(routeKey as ViewComponentKey) ? (routeKey as ViewComponentKey) : 'not-found'; @@ -44,7 +44,7 @@ export function getViewComponent(routeKey: AuthRoute.RouteKey) { dashboard_workbench: DashboardWorkbench, about: About, 'multi-menu_first_second': MultiMenuFirstSecond, - 'redirect-not-found': NotFound + 'not-found-page': NotFound }; return () => setViewComponentName(viewComponent[key], key) as Promise; diff --git a/src/utils/router/helpers.ts b/src/utils/router/helpers.ts index f4083ec1a..c97469810 100644 --- a/src/utils/router/helpers.ts +++ b/src/utils/router/helpers.ts @@ -9,15 +9,12 @@ type ComponentAction = { /** 将权限路由类型转换成vue路由类型 */ export function transformAuthRouteToVueRoute(item: AuthRoute.Route) { - const { name, path, meta } = item; - const itemRoute: Partial = { - name, - path, - meta - }; - if (hasRedirect(item)) { - itemRoute.redirect = item.redirect; + const itemRoute = { ...item } as RouteRecordRaw; + + if (hasDynamicPath(item)) { + Object.assign(itemRoute, { path: item.meta.dynamicPath }); } + if (hasComponent(item)) { const action: ComponentAction = { layout() { @@ -29,7 +26,14 @@ export function transformAuthRouteToVueRoute(item: AuthRoute.Route) { itemRoute.meta.blankLayout = true; } }, - multi() {}, + multi() { + // 多级路由一定有子路由 + if (hasChildren(item)) { + Object.assign(itemRoute, { component: Layout }); + } else { + consoleError('多级路由缺少子路由: ', item); + } + }, self() { itemRoute.component = getViewComponent(item.name); } @@ -41,116 +45,59 @@ export function transformAuthRouteToVueRoute(item: AuthRoute.Route) { } } - if (hasProps(item)) { - (itemRoute as any).props = item.props; - } - + // 注意:单独路由没有children if (isSingleRoute(item)) { - if (item.name === 'redirect-not-found') { + if (hasChildren(item)) { + consoleError('单独路由不应该有子路由: ', item); + } + + // 捕获无效路由的需特殊处理 + if (item.name === 'not-found-page') { itemRoute.children = [ { path: '', name: item.name, - component: getViewComponent('redirect-not-found') + component: getViewComponent('not-found-page') } ]; - return itemRoute as RouteRecordRaw; - } - const singleRoute = { - ...itemRoute - }; - Object.assign(singleRoute, { component: getViewComponent(item.name) }); + } else { + const parentPath = `${itemRoute.path}-parent` as AuthRoute.SingleRouteParentPath; - const singlePath = ( - hasSingleOriginPath(item) ? item.meta.singleOriginPath : item.path - ) as AuthRoute.SingleRoutePath; - const parenPath = `${singlePath}-parent` as AuthRoute.SingleRouteParentPath; + if (item.meta.singleLayout === 'blank') { + itemRoute.meta!.blankLayout = true; + } - const parentRoute: Partial = { - path: parenPath, - component: itemRoute.component, - redirect: singlePath, - children: [singleRoute as RouteRecordRaw] - }; - return parentRoute as RouteRecordRaw; + const parentRoute: RouteRecordRaw = { + path: parentPath, + component: Layout, + redirect: item.path, + children: [itemRoute] + }; + + return parentRoute; + } } + if (hasChildren(item)) { - itemRoute.children = item.children!.map(child => transformAuthRouteToVueRoute(child)) as RouteRecordRaw[]; + itemRoute.redirect = item.children![0].path; + itemRoute.children = item.children!.map(child => transformAuthRouteToVueRoute(child)); } - return itemRoute as RouteRecordRaw; + return itemRoute; } function hasComponent(item: AuthRoute.Route) { return Boolean(item.component); } -function hasRedirect(item: AuthRoute.Route) { - return Boolean(item.redirect); -} - function hasChildren(item: AuthRoute.Route) { return Boolean(item.children && item.children.length); } -function hasProps(item: AuthRoute.Route) { - return Boolean(item.props); +function hasDynamicPath(item: AuthRoute.Route) { + return Boolean(item.meta.dynamicPath); } function isSingleRoute(item: AuthRoute.Route) { - return Boolean(item.meta.single); -} - -function hasSingleOriginPath(item: AuthRoute.Route) { - return Boolean(item.meta.singleOriginPath); -} - -/** - * 根据路由key获取AuthRoute数据 - * @param key - 路由key - * @param routes - 路由 - */ -export function findAuthRouteByKey(key: AuthRoute.RouteKey, routes: AuthRoute.Route[]) { - const paths = getRouteKeyPathsByKey(key); - const route = recursiveFindRouteByPaths(paths, routes); - - return route; -} - -/** - * 根据路由key的paths获递归取路由 - * @param paths - 路由key的路径 - * @param routes - 路由 - */ -function recursiveFindRouteByPaths( - paths: AuthRoute.RouteKey[], - routes: AuthRoute.Route[] -): AuthRoute.Route | undefined { - const item = routes.find(route => paths.length && route.name === paths[0]); - - if (item && hasComponent(item)) { - return recursiveFindRouteByPaths(paths.slice(1), item.children!); - } - return item; -} - -/** - * 根据路由key获取从第一级路由到当前路由key的paths - * @param key - 路由key - */ -function getRouteKeyPathsByKey(key: AuthRoute.RouteKey) { - const splitMark: AuthRoute.RouteSplitMark = '_'; - const keys = key.split(splitMark); - const keyPaths: AuthRoute.RouteKey[] = []; - - keys.forEach((itemKey, index) => { - if (index === 0) { - keyPaths.push(itemKey as AuthRoute.RouteKey); - } else { - const concatKey = keyPaths[index - 1] + splitMark + itemKey; - keyPaths.push(concatKey as AuthRoute.RouteKey); - } - }); - - return keyPaths; + return Boolean(item.meta.singleLayout); } diff --git a/src/utils/router/index.ts b/src/utils/router/index.ts index a363a5bc8..e659803c6 100644 --- a/src/utils/router/index.ts +++ b/src/utils/router/index.ts @@ -1,2 +1,3 @@ export * from './helpers'; +export * from './menu'; export * from './regexp'; diff --git a/src/utils/router/menu.ts b/src/utils/router/menu.ts new file mode 100644 index 000000000..07c208ae2 --- /dev/null +++ b/src/utils/router/menu.ts @@ -0,0 +1,51 @@ +import type { GlobalMenuOption } from '@/interface'; +import { iconifyRender } from '../common'; + +/** 路由不转换菜单 */ +function hideInMenu(route: AuthRoute.Route) { + return Boolean(route.meta.hide); +} + +/** 给菜单添加可选属性 */ +function addPartialProps(menuItem: GlobalMenuOption, icon?: string, children?: GlobalMenuOption[]) { + const item = { ...menuItem }; + if (icon) { + Object.assign(item, { icon: iconifyRender(icon) }); + } + if (children) { + Object.assign(item, { children }); + } + return item; +} + +/** + * 将权限路由转换成菜单 + * @param routes - 路由 + */ +export function transformAuthRouteToMenu(routes: AuthRoute.Route[]) { + const globalMenu: GlobalMenuOption[] = []; + routes.forEach(route => { + const { name, path, meta } = route; + const routeName = name as string; + let menuChildren: GlobalMenuOption[] | undefined; + if (route.children) { + menuChildren = transformAuthRouteToMenu(route.children); + } + const menuItem: GlobalMenuOption = addPartialProps( + { + key: routeName, + label: meta.title, + routeName, + routePath: path + }, + meta?.icon, + menuChildren + ); + + if (!hideInMenu(route)) { + globalMenu.push(menuItem); + } + }); + + return globalMenu; +} diff --git a/src/views/system/exception/403.vue b/src/views/system/exception/403.vue deleted file mode 100644 index d77f1a3a4..000000000 --- a/src/views/system/exception/403.vue +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/src/views/system/exception/404.vue b/src/views/system/exception/404.vue deleted file mode 100644 index 37e5be1ec..000000000 --- a/src/views/system/exception/404.vue +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/src/views/system/exception/500.vue b/src/views/system/exception/500.vue deleted file mode 100644 index d8396bbfd..000000000 --- a/src/views/system/exception/500.vue +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/src/views/system/exception/components/ExceptionBase.vue b/src/views/system/exception/components/ExceptionBase.vue new file mode 100644 index 000000000..c56e418a8 --- /dev/null +++ b/src/views/system/exception/components/ExceptionBase.vue @@ -0,0 +1,42 @@ + + + + diff --git a/src/views/system/exception/components/index.ts b/src/views/system/exception/components/index.ts new file mode 100644 index 000000000..d8b1dedfa --- /dev/null +++ b/src/views/system/exception/components/index.ts @@ -0,0 +1,3 @@ +import ExceptionBase from './ExceptionBase.vue'; + +export { ExceptionBase }; diff --git a/src/views/system/exception/no-permission/index.vue b/src/views/system/exception/no-permission/index.vue new file mode 100644 index 000000000..04e5a44da --- /dev/null +++ b/src/views/system/exception/no-permission/index.vue @@ -0,0 +1,8 @@ + + + + diff --git a/src/views/system/exception/not-found/index.vue b/src/views/system/exception/not-found/index.vue new file mode 100644 index 000000000..65110f04a --- /dev/null +++ b/src/views/system/exception/not-found/index.vue @@ -0,0 +1,8 @@ + + + + diff --git a/src/views/system/exception/service-error/index.vue b/src/views/system/exception/service-error/index.vue new file mode 100644 index 000000000..ad6eb1b36 --- /dev/null +++ b/src/views/system/exception/service-error/index.vue @@ -0,0 +1,8 @@ + + + + diff --git a/src/views/system/index.ts b/src/views/system/index.ts index a86e490d8..32aa8ffba 100644 --- a/src/views/system/index.ts +++ b/src/views/system/index.ts @@ -1,6 +1,6 @@ const Login = () => import('./login/index.vue'); -const NoPermission = () => import('./exception/403.vue'); -const NotFound = () => import('./exception/404.vue'); -const ServiceError = () => import('./exception/500.vue'); +const NoPermission = () => import('./exception/no-permission/index.vue'); +const NotFound = () => import('./exception/not-found/index.vue'); +const ServiceError = () => import('./exception/service-error/index.vue'); export { Login, NoPermission, NotFound, ServiceError }; diff --git a/src/views/system/login/components/CodeLogin/index.vue b/src/views/system/login/components/CodeLogin/index.vue index 1176ec4b2..92e3e2848 100644 --- a/src/views/system/login/components/CodeLogin/index.vue +++ b/src/views/system/login/components/CodeLogin/index.vue @@ -18,7 +18,7 @@ - + - + 确定 返回