diff --git a/.editorconfig b/.editorconfig index da599f40..9d08a1a8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,10 +3,7 @@ root = true [*] charset = utf-8 indent_style = space -indent_size = 4 +indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true - -[*.md] -trim_trailing_whitespace = false diff --git a/.env.example b/.env.example index ab9e6cd5..7ea58d8f 100644 --- a/.env.example +++ b/.env.example @@ -10,10 +10,8 @@ VITE_APP_API_BASEURL = / VITE_APP_DEBUG_TOOL = # 是否在打包时启用 Mock -VITE_BUILD_MOCK = true +VITE_BUILD_MOCK = false # 是否在打包时生成 sourcemap VITE_BUILD_SOURCEMAP = false -# 是否在打包时删除 console 代码 -VITE_BUILD_DROP_CONSOLE = true # 是否在打包时开启压缩,支持 gzip 和 brotli VITE_BUILD_COMPRESS = gzip,brotli diff --git a/.env.production b/.env.production deleted file mode 100644 index 17fad7c5..00000000 --- a/.env.production +++ /dev/null @@ -1,17 +0,0 @@ -NODE_ENV = production - -# 页面标题 -VITE_APP_TITLE = Fantastic-admin 基础版 -# 接口请求地址,会设置到 axios 的 baseURL 参数上 -VITE_APP_API_BASEURL = / -# 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空 -VITE_APP_DEBUG_TOOL = - -# 是否在打包时启用 Mock -VITE_BUILD_MOCK = false -# 是否在打包时生成 sourcemap -VITE_BUILD_SOURCEMAP = false -# 是否在打包时删除 console 代码 -VITE_BUILD_DROP_CONSOLE = true -# 是否在打包时开启压缩,支持 gzip 和 brotli -VITE_BUILD_COMPRESS = gzip,brotli diff --git a/.env.test b/.env.test deleted file mode 100644 index 9a2b3b61..00000000 --- a/.env.test +++ /dev/null @@ -1,17 +0,0 @@ -NODE_ENV = production - -# 页面标题 -VITE_APP_TITLE = Fantastic-admin 专业版 -# 接口请求地址,会设置到 axios 的 baseURL 参数上 -VITE_APP_API_BASEURL = / -# 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空 -VITE_APP_DEBUG_TOOL = - -# 是否在打包时启用 Mock -VITE_BUILD_MOCK = true -# 是否在打包时生成 sourcemap -VITE_BUILD_SOURCEMAP = true -# 是否在打包时删除 console 代码 -VITE_BUILD_DROP_CONSOLE = true -# 是否在打包时开启压缩,支持 gzip 和 brotli -VITE_BUILD_COMPRESS = gzip,brotli diff --git a/.eslintignore b/.eslintignore index 58ce8430..1eae0cf6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,2 @@ dist/ node_modules/ -babel.config.js -.eslintrc.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..09c2e423 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "@antfu" +} diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 3f4cb551..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,101 +0,0 @@ -module.exports = { - root: true, - env: { - browser: true, - es6: true - }, - globals: { - __dirname: true, - process: true, - require: true, - module: true, - // element-plus - ElMessage: true, - ElMessageBox: true, - ElNotification: true, - ElLoading: true - }, - extends: [ - 'plugin:vue/vue3-recommended', - 'plugin:vue/vue3-strongly-recommended', - 'eslint:recommended', - './.eslintrc-auto-import.json' - ], - parser: 'vue-eslint-parser', - parserOptions: { - ecmaVersion: '2020', - ecmaFeatures: { - jsx: true - } - }, - rules: { - // 代码风格 - 'block-spacing': [2, 'always'], - 'brace-style': [2, '1tbs', { - 'allowSingleLine': true - }], - 'comma-spacing': [2, { - 'before': false, - 'after': true - }], - 'comma-dangle': [2, 'never'], - 'comma-style': [2, 'last'], - 'computed-property-spacing': [2, 'never'], - 'indent': [2, 4, { - 'SwitchCase': 1 - }], - 'key-spacing': [2, { - 'beforeColon': false, - 'afterColon': true - }], - 'keyword-spacing': [2, { - 'before': true, - 'after': true - }], - 'linebreak-style': 0, - 'multiline-ternary': [2, 'always-multiline'], - 'no-multiple-empty-lines': [2, { - 'max': 1 - }], - 'no-unneeded-ternary': [2, { - 'defaultAssignment': false - }], - 'quotes': [2, 'single'], - 'semi': [2, 'never'], - 'space-before-blocks': [2, 'always'], - 'space-before-function-paren': [2, 'never'], - 'space-in-parens': [2, 'never'], - 'space-infix-ops': 2, - 'space-unary-ops': [2, { - 'words': true, - 'nonwords': false - }], - 'spaced-comment': [2, 'always', { - 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] - }], - 'switch-colon-spacing': [2, { - 'after': true, - 'before': false - }], - 'object-curly-spacing': [2, 'always'], - // ES6 - 'arrow-parens': [2, 'as-needed'], - 'arrow-spacing': [2, { - 'before': true, - 'after': true - }], - // Vue - https://github.com/vuejs/eslint-plugin-vue - 'vue/multi-word-component-names': 0, - 'vue/html-indent': [2, 4], - 'vue/no-v-html': 0, - 'vue/max-attributes-per-line': 0, - 'vue/require-default-prop': 0, - 'vue/singleline-html-element-content-newline': 0, - 'vue/require-explicit-emits': [2, { - 'allowProps': true - }], - 'vue/script-indent': [2, 4, { - 'switchCase': 1 - }] - } -}; diff --git a/.gitignore b/.gitignore index c66a0a66..165d220b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,5 @@ node_modules dist* dist-ssr *.local -.eslintrc-auto-import.json -public/icons src/assets/sprites/*.* +!src/assets/sprites/.gitkeep diff --git a/.lintstagedrc b/.lintstagedrc index b35aaba8..fc89276a 100644 --- a/.lintstagedrc +++ b/.lintstagedrc @@ -1,5 +1,5 @@ { - "src/**/*.{js,jsx}": "eslint --fix", - "src/**/*.{css,scss}": "stylelint --fix --allow-empty-input", - "src/**/*.vue": ["eslint --fix", "stylelint --fix --allow-empty-input"] + "src/**/*.{js,jsx,ts,tsx}": "eslint --fix", + "src/**/*.{css,scss}": "stylelint --fix --allow-empty-input", + "src/**/*.vue": ["eslint --fix", "stylelint --fix --allow-empty-input"] } diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..cf040424 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +shamefully-hoist=true +strict-peer-dependencies=false diff --git a/.stylelintrc b/.stylelintrc index a848b996..f397ec98 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -1,48 +1,23 @@ { - "extends": [ - "stylelint-config-standard", - "stylelint-config-recommended-vue/scss" - ], - "overrides": [ - { - "files": ["**/*.scss"], - "customSyntax": "postcss-scss" - } - ], - "plugins": [ - "stylelint-scss" - ], - "rules": { - "indentation": 4, - "rule-empty-line-before": "never", - "at-rule-empty-line-before": "never", - "at-rule-no-unknown": null, - "no-descending-specificity": null, - "selector-pseudo-class-no-unknown": null, - "property-no-unknown": null, - "font-family-no-missing-generic-family-keyword": null, - "selector-id-pattern": null, - "selector-class-pattern": null, - "scss/no-global-function-names": null, - "scss/at-import-partial-extension": null, - "function-no-unknown": [ - true, - { - "ignoreFunctions": [ - "v-bind", - "lighten", - "darken" - ] - } - ], - "property-no-vendor-prefix": [ - true, - { - "ignoreProperties": [ - "mask-image" - ] - } - ], - "max-line-length": null + "extends": [ + "stylelint-config-standard", + "stylelint-config-standard-vue/scss" + ], + "overrides": [ + { + "files": ["**/*.scss"], + "customSyntax": "postcss-scss" } + ], + "plugins": [ + "stylelint-scss" + ], + "rules": { + "at-rule-no-unknown": null, + "no-descending-specificity": null, + "property-no-unknown": null, + "font-family-no-missing-generic-family-keyword": null, + "selector-class-pattern": null, + "max-line-length": null + } } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index bd128e51..fef58956 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,9 +1,9 @@ { - "recommendations": [ - "EditorConfig.EditorConfig", - "mikestead.dotenv", - "dbaeumer.vscode-eslint", - "stylelint.vscode-stylelint", - "vue.volar" - ] + "recommendations": [ + "EditorConfig.EditorConfig", + "mikestead.dotenv", + "dbaeumer.vscode-eslint", + "stylelint.vscode-stylelint", + "vue.volar" + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index f1783033..e5e29ce4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { - "editor.codeActionsOnSave": { - "source.fixAll.eslint": true, - "source.fixAll.stylelint": true - }, - "stylelint.validate": ["css", "scss", "vue"] + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true, + "source.fixAll.stylelint": true + }, + "stylelint.validate": ["css", "scss", "vue"] } diff --git a/index.html b/index.html index b673219b..c801c4c5 100644 --- a/index.html +++ b/index.html @@ -1,78 +1,40 @@ - - - - - - <%= title %> - <%- copyrightScript %> - <% if (appMode == 'example') { %> - - <% } %> - - -
-
-
-
-
-
-
-
-
<%= title %>载入中…
-
-
-
为了您的体验,推荐使用以下浏览器
- -
+ + + + + + <%= title %> + <%- copyrightScript %> + + +
+
+
+
+
+
+
- - - <% if (debugTool == 'eruda') { %> - - - <% } %> - <% if (debugTool == 'vconsole') { %> - - - <% } %> - +
<%= title %>载入中…
+
+
+
为了您的体验,推荐使用以下浏览器
+ +
+
+ <%- loadingScript %> + + <%- devtoolScript %> + diff --git a/jsconfig.json b/jsconfig.json deleted file mode 100644 index bfb2bc61..00000000 --- a/jsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "target": "es2017", - "allowSyntheticDefaultImports": false, - "baseUrl": "./", - "paths": { - "@/*": ["src/*"] - }, - "jsx": "preserve" - }, - "exclude": ["node_modules", "dist", "dist-test"] -} diff --git a/package.json b/package.json index df044936..edba3dba 100644 --- a/package.json +++ b/package.json @@ -1,79 +1,77 @@ { - "name": "fantastic-admin", - "version": "0.1.0", - "scripts": { - "dev": "vite", - "build:test": "vite build --mode test", - "build": "vite build", - "build:example": "vite build --mode example", - "serve:test": "http-server ./dist-test -o", - "serve": "http-server ./dist -o", - "serve:example": "http-server ./dist-example -o", - "svgo": "svgo -f src/assets/icons", - "new": "plop", - "lint:eslint": "eslint src/**/*.{js,vue} --fix", - "lint:stylelint": "stylelint src/**/*.{css,scss,vue} --fix", - "prepare": "husky install", - "postinstall": "esno scripts/prepare.js", - "preinstall": "npx only-allow pnpm" - }, - "dependencies": { - "@tinymce/tinymce-vue": "^5.0.0", - "@vueuse/core": "^9.2.0", - "@vueuse/integrations": "^9.2.0", - "axios": "^0.27.2", - "dayjs": "^1.11.5", - "element-plus": "^2.2.16", - "hotkeys-js": "^3.10.0", - "js-cookie": "^3.0.1", - "mavon-editor": "^3.0.0-beta", - "mitt": "^3.0.0", - "mockjs": "^1.1.0", - "nprogress": "^0.2.0", - "path-browserify": "^1.0.1", - "path-to-regexp": "^6.2.1", - "pinia": "^2.0.22", - "qs": "^6.11.0", - "tinymce": "^6.2.0", - "vue": "^3.2.39", - "vue-router": "^4.1.3" - }, - "devDependencies": { - "@iconify-json/ep": "^1.1.7", - "@iconify/vue": "^4.0.0", - "@vitejs/plugin-vue": "^3.1.0", - "@vitejs/plugin-vue-jsx": "^2.0.1", - "@vue/compiler-sfc": "^3.2.39", - "eslint": "^8.23.1", - "eslint-plugin-vue": "^9.3.0", - "esno": "^0.16.3", - "fs-extra": "^10.1.0", - "http-server": "^14.1.1", - "husky": "^8.0.1", - "lint-staged": "^13.0.3", - "plop": "^3.1.1", - "postcss-html": "^1.5.0", - "postcss-scss": "^4.0.5", - "sass": "^1.54.9", - "stylelint": "^14.10.0", - "stylelint-config-recommended-scss": "^7.0.0", - "stylelint-config-recommended-vue": "^1.4.0", - "stylelint-config-standard": "^28.0.0", - "stylelint-scss": "^4.3.0", - "svgo": "^2.8.0", - "unplugin-auto-import": "^0.11.2", - "unplugin-vue-components": "^0.22.7", - "vite": "^3.1.1", - "vite-plugin-banner": "^0.5.0", - "vite-plugin-compression": "^0.5.1", - "vite-plugin-html": "^3.2.0", - "vite-plugin-mock": "^2.9.6", - "vite-plugin-pages": "^0.26.0", - "vite-plugin-restart": "^0.2.0", - "vite-plugin-spritesmith": "^0.1.1", - "vite-plugin-svg-icons": "^2.0.1", - "vite-plugin-vue-layouts": "^0.6.0", - "vite-plugin-vue-setup-extend": "^0.4.0", - "vue-eslint-parser": "^9.1.0" - } + "version": "1.0.0", + "scripts": { + "dev": "vite", + "build:example": "vue-tsc --noEmit && vite build --mode example", + "serve:example": "http-server ./dist-example -o", + "svgo": "svgo -f src/assets/icons", + "new": "plop", + "lint:eslint": "eslint src/**/*.{ts,tsx,vue} --fix", + "lint:stylelint": "stylelint src/**/*.{css,scss,vue} --fix", + "prepare": "husky install", + "postinstall": "esno scripts/prepare.js", + "preinstall": "npx only-allow pnpm" + }, + "dependencies": { + "@tinymce/tinymce-vue": "^5.0.0", + "@vueuse/core": "^9.2.0", + "@vueuse/integrations": "^9.2.0", + "axios": "^0.27.2", + "dayjs": "^1.11.5", + "element-plus": "^2.2.17", + "hotkeys-js": "^3.10.0", + "lodash-es": "^4.17.21", + "mitt": "^3.0.0", + "mockjs": "^1.1.0", + "nprogress": "^0.2.0", + "path-browserify": "^1.0.1", + "path-to-regexp": "^6.2.1", + "pinia": "^2.0.22", + "qs": "^6.11.0", + "tinymce": "^6.2.0", + "vue": "^3.2.39", + "vue-router": "^4.1.5" + }, + "devDependencies": { + "@antfu/eslint-config": "^0.27.0", + "@iconify-json/ep": "^1.1.7", + "@iconify/vue": "^4.0.0", + "@types/nprogress": "^0.2.0", + "@types/path-browserify": "^1.0.0", + "@types/qs": "^6.9.7", + "@vitejs/plugin-vue": "^3.1.0", + "@vitejs/plugin-vue-jsx": "^2.0.1", + "autoprefixer": "^10.4.12", + "eslint": "^8.24.0", + "esno": "^0.16.3", + "fs-extra": "^10.1.0", + "http-server": "^14.1.1", + "husky": "^8.0.1", + "lint-staged": "^13.0.3", + "plop": "^3.1.1", + "postcss-html": "^1.5.0", + "postcss-scss": "^4.0.5", + "sass": "^1.55.0", + "stylelint": "^14.12.1", + "stylelint-config-standard": "^28.0.0", + "stylelint-config-standard-scss": "^5.0.0", + "stylelint-config-standard-vue": "^1.0.0", + "stylelint-scss": "^4.3.0", + "svgo": "^2.8.0", + "typescript": "^4.8.3", + "unplugin-auto-import": "^0.11.2", + "unplugin-vue-components": "^0.22.7", + "vite": "^3.1.3", + "vite-plugin-banner": "^0.5.0", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-html": "^3.2.0", + "vite-plugin-mock": "^2.9.6", + "vite-plugin-pages": "^0.26.0", + "vite-plugin-restart": "^0.2.0", + "vite-plugin-spritesmith": "^0.1.1", + "vite-plugin-svg-icons": "^2.0.1", + "vite-plugin-vue-layouts": "^0.6.0", + "vite-plugin-vue-setup-extend": "^0.4.0", + "vue-tsc": "^0.40.13" + } } diff --git a/plop-templates/component/index.hbs b/plop-templates/component/index.hbs index 2ca042bb..cc1916c9 100644 --- a/plop-templates/component/index.hbs +++ b/plop-templates/component/index.hbs @@ -1,12 +1,11 @@ - \ No newline at end of file diff --git a/src/assets/icons/eye-open.svg b/src/assets/icons/eye-open.svg deleted file mode 100644 index b80fd2b6..00000000 --- a/src/assets/icons/eye-open.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/icons/eye.svg b/src/assets/icons/eye.svg deleted file mode 100644 index 16ed2d87..00000000 --- a/src/assets/icons/eye.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/icons/password.svg b/src/assets/icons/password.svg deleted file mode 100644 index e291d85d..00000000 --- a/src/assets/icons/password.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/icons/sidebar-element.svg b/src/assets/icons/sidebar-element.svg deleted file mode 100644 index 6965ced0..00000000 --- a/src/assets/icons/sidebar-element.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/icons/sidebar-permission.svg b/src/assets/icons/sidebar-permission.svg deleted file mode 100644 index 2f812c4f..00000000 --- a/src/assets/icons/sidebar-permission.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/icons/user.svg b/src/assets/icons/user.svg deleted file mode 100644 index 0ba0716a..00000000 --- a/src/assets/icons/user.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/sprites/.gitkeep b/src/assets/sprites/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/assets/sprites/example/address.png b/src/assets/sprites/example/address.png deleted file mode 100644 index 2b5d1db3..00000000 Binary files a/src/assets/sprites/example/address.png and /dev/null differ diff --git a/src/assets/sprites/example/feedback.png b/src/assets/sprites/example/feedback.png deleted file mode 100644 index 065b55dd..00000000 Binary files a/src/assets/sprites/example/feedback.png and /dev/null differ diff --git a/src/assets/sprites/example/payment.png b/src/assets/sprites/example/payment.png deleted file mode 100644 index 1c2f680a..00000000 Binary files a/src/assets/sprites/example/payment.png and /dev/null differ diff --git a/src/assets/styles/element-plus/dark.scss b/src/assets/styles/element-plus/dark.scss index 1e3ddf33..c20a369e 100644 --- a/src/assets/styles/element-plus/dark.scss +++ b/src/assets/styles/element-plus/dark.scss @@ -1,9 +1,10 @@ // 暗黑模式 @forward "element-plus/theme-chalk/src/dark/var.scss" with ( - $bg-color: ( - "page": #0a0a0a, - "": #141414, - "overlay": #1d1e1f, - ) + $bg-color: ( + "page": #0a0a0a, + "": #141414, + "overlay": #1d1e1f, + ) ); + @use "element-plus/theme-chalk/src/dark/css-vars.scss" as *; diff --git a/src/assets/styles/element-plus/index.scss b/src/assets/styles/element-plus/index.scss index 14cacee8..4ff67107 100644 --- a/src/assets/styles/element-plus/index.scss +++ b/src/assets/styles/element-plus/index.scss @@ -1,9 +1,10 @@ // @forward "element-plus/theme-chalk/src/common/var.scss" with ( -// $colors: ( -// "primary": ( -// "base": green -// ) +// $colors: ( +// "primary": ( +// "base": green // ) +// ) // ); + @use "element-plus/theme-chalk/src/index.scss" as *; @use "./dark.scss"; diff --git a/src/assets/styles/globals.scss b/src/assets/styles/globals.scss index ae012a7f..fedf28c6 100644 --- a/src/assets/styles/globals.scss +++ b/src/assets/styles/globals.scss @@ -3,73 +3,82 @@ // 全局变量 :root { - // 头部宽度(默认自适应宽度,可固定宽度,固定宽度后为居中显示) - --g-header-width: 100%; - // 头部高度 - --g-header-height: 70px; - // 侧边栏宽度 - --g-main-sidebar-width: 70px; - --g-sub-sidebar-width: 220px; - // 侧边栏Logo高度 - --g-sidebar-logo-height: 50px; - // 顶栏高度 - --g-topbar-height: 50px; + // 头部宽度(默认自适应宽度,可固定宽度,固定宽度后为居中显示) + --g-header-width: 100%; + // 头部高度 + --g-header-height: 70px; + // 侧边栏宽度 + --g-main-sidebar-width: 70px; + --g-sub-sidebar-width: 220px; + // 侧边栏Logo高度 + --g-sidebar-logo-height: 50px; + // 顶栏高度 + --g-topbar-height: 50px; } // 全局样式 ::-webkit-scrollbar { - width: 12px; - height: 12px; + width: 12px; + height: 12px; } + ::-webkit-scrollbar-thumb { - background-color: rgb(0 0 0 / 40%); - background-clip: padding-box; - border: 3px solid transparent; - border-radius: 6px; + background-color: rgb(0 0 0 / 40%); + background-clip: padding-box; + border: 3px solid transparent; + border-radius: 6px; } + ::-webkit-scrollbar-thumb:hover { - background-color: rgb(0 0 0 / 50%); + background-color: rgb(0 0 0 / 50%); } + ::-webkit-scrollbar-track { - background-color: transparent; + background-color: transparent; } + html, body { - height: 100%; + height: 100%; } + body { - margin: 0; - box-sizing: border-box; - font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; - -webkit-tap-highlight-color: transparent; - &.hidden { - overflow: hidden; - } + margin: 0; + box-sizing: border-box; + font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; + -webkit-tap-highlight-color: transparent; + + &.hidden { + overflow: hidden; + } } + * { - box-sizing: inherit; + box-sizing: inherit; } // 右侧内容区针对fixed元素,有横向铺满的需求,可在fixed元素上设置 [data-fixed-calc-width] [data-fixed-calc-width] { - position: fixed; - left: 50%; - right: 0; - width: calc(100% - var(--g-main-sidebar-actual-width) - var(--g-sub-sidebar-actual-width)); - transform: translateX(-50%) translateX(calc(var(--g-main-sidebar-actual-width) / 2)) translateX(calc(var(--g-sub-sidebar-actual-width) / 2)); + position: fixed; + left: 50%; + right: 0; + width: calc(100% - var(--g-main-sidebar-actual-width) - var(--g-sub-sidebar-actual-width)); + transform: translateX(-50%) translateX(calc(var(--g-main-sidebar-actual-width) / 2)) translateX(calc(var(--g-sub-sidebar-actual-width) / 2)); } + [data-mode="mobile"] { - [data-fixed-calc-width] { - width: 100% !important; - transform: translateX(-50%) !important; - } + [data-fixed-calc-width] { + width: 100% !important; + transform: translateX(-50%) !important; + } } // textarea 字体跟随系统 textarea { - font-family: inherit; + font-family: inherit; } // 列表页全局样式 .el-table.list-table { - margin: 20px 0; - .el-button + .el-dropdown { - margin-left: 10px; - } + margin: 20px 0; + + .el-button + .el-dropdown { + margin-left: 10px; + } } diff --git a/src/assets/styles/nprogress.scss b/src/assets/styles/nprogress.scss index a96b4945..9947aa68 100644 --- a/src/assets/styles/nprogress.scss +++ b/src/assets/styles/nprogress.scss @@ -1,55 +1,63 @@ #nprogress { - pointer-events: none; - .bar { - position: fixed; - z-index: 1031; - top: 0; - left: 0; - width: 100%; - height: 2px; - background: var(--g-nprogress-color); - } - .peg { - display: block; - position: absolute; - right: 0; - width: 100px; - height: 100%; - opacity: 1; - transform: rotate(3deg) translate(0, -4px); - box-shadow: 0 0 10px var(--g-nprogress-color), 0 0 5px var(--g-nprogress-color); - } - .spinner { - display: block; - position: fixed; - z-index: 1031; - top: 11px; - right: 14px; - .spinner-icon { - width: 18px; - height: 18px; - box-sizing: border-box; - border: solid 2px transparent; - border-radius: 50%; - animation: nprogress-spinner 400ms linear infinite; - border-top-color: var(--g-nprogress-color); - border-left-color: var(--g-nprogress-color); - } + pointer-events: none; + + .bar { + position: fixed; + z-index: 1031; + top: 0; + left: 0; + width: 100%; + height: 2px; + background: var(--g-nprogress-color); + } + + .peg { + display: block; + position: absolute; + right: 0; + width: 100px; + height: 100%; + opacity: 1; + transform: rotate(3deg) translate(0, -4px); + box-shadow: 0 0 10px var(--g-nprogress-color), 0 0 5px var(--g-nprogress-color); + } + + .spinner { + display: block; + position: fixed; + z-index: 1031; + top: 11px; + right: 14px; + + .spinner-icon { + width: 18px; + height: 18px; + box-sizing: border-box; + border: solid 2px transparent; + border-radius: 50%; + animation: nprogress-spinner 400ms linear infinite; + border-top-color: var(--g-nprogress-color); + border-left-color: var(--g-nprogress-color); } + } } + .nprogress-custom-parent { - overflow: hidden; - position: relative; - #nprogress .spinner, - #nprogress .bar { - position: absolute; - } + overflow: hidden; + position: relative; + + #nprogress .spinner, + #nprogress .bar { + position: absolute; + } } + @keyframes nprogress-spinner { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } } + @keyframes nprogress-spinner { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } } diff --git a/src/assets/styles/resources/utils.scss b/src/assets/styles/resources/utils.scss index 40d054ac..ce32009a 100644 --- a/src/assets/styles/resources/utils.scss +++ b/src/assets/styles/resources/utils.scss @@ -1,50 +1,55 @@ // 文字超出隐藏,默认为单行超出隐藏,可设置多行 @mixin text-overflow($line: 1, $fixed-width: true) { - @if ($line == 1 and $fixed-width == true) { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - @else { - /* stylelint-disable-next-line value-no-vendor-prefix */ - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: $line; - overflow: hidden; - } + @if ($line == 1 and $fixed-width == true) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + @else { + /* stylelint-disable-next-line value-no-vendor-prefix */ + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: $line; + overflow: hidden; + } } // 定位居中,默认水平居中,可选择垂直居中,或者水平垂直都居中 @mixin position-center($type: x) { - position: absolute; - @if ($type == x) { - left: 50%; - transform: translateX(-50%); - } - @if ($type == y) { - top: 50%; - transform: translateY(-50%); - } - @if ($type == xy) { - left: 50%; - top: 50%; - transform: translateX(-50%) translateY(-50%); - } + position: absolute; + + @if ($type == x) { + left: 50%; + transform: translateX(-50%); + } + + @if ($type == y) { + top: 50%; + transform: translateY(-50%); + } + + @if ($type == xy) { + left: 50%; + top: 50%; + transform: translateX(-50%) translateY(-50%); + } } // 文字两端对齐 %justify-align { - text-align: justify; - text-align-last: justify; + text-align: justify; + text-align-last: justify; } // 清除浮动 %clearfix { - zoom: 1; - &::before, - &::after { - content: ""; - display: block; - clear: both; - } + zoom: 1; + + &::before, + &::after { + content: ""; + display: block; + clear: both; + } } diff --git a/src/assets/styles/themes.scss b/src/assets/styles/themes.scss index 3ce94315..179e406f 100644 --- a/src/assets/styles/themes.scss +++ b/src/assets/styles/themes.scss @@ -1,5 +1,67 @@ :root { - --g-box-shadow-color: rgb(0 0 0 / 12%); + --g-box-shadow-color: rgb(0 0 0 / 12%); + + /* ----- 应用 ----- */ + // 应用背景色 + --g-app-bg: var(--el-bg-color); + // 主区域背景色 + --g-main-bg: var(--el-bg-color-page); + + /* ----- 顶部 ----- */ + // 背景色 + --g-header-bg: #222b45; + // 文字颜色 + --g-header-color: #fff; + // 导航文字颜色 + --g-header-menu-color: #fff; + // 导航鼠标悬浮文字颜色 + --g-header-menu-hover-color: #fff; + // 导航鼠标悬浮背景色 + --g-header-menu-hover-bg: #334067; + // 导航选中文字颜色 + --g-header-menu-active-color: #fff; + // 导航选中背景色 + --g-header-menu-active-bg: #334067; + + /* ----- 主导航 ----- */ + // 背景色 + --g-main-sidebar-bg: #222b45; + // 菜单文字颜色 + --g-main-sidebar-menu-color: #eee; + // 菜单鼠标悬浮文字颜色 + --g-main-sidebar-menu-hover-color: #fff; + // 菜单鼠标悬浮背景色 + --g-main-sidebar-menu-hover-bg: #334067; + // 菜单选中文字颜色 + --g-main-sidebar-menu-active-color: #fff; + // 菜单选中背景色 + --g-main-sidebar-menu-active-bg: #334067; + + /* ----- 次导航 ----- */ + // 背景色 + --g-sub-sidebar-bg: #fff; + // 菜单背景色 + --g-sub-sidebar-menu-bg: #e8e8e8; + // 菜单文字颜色 + --g-sub-sidebar-menu-color: var(--el-text-color-primary); + // 菜单鼠标悬浮文字颜色 + --g-sub-sidebar-menu-hover-color: var(--el-text-color-primary); + // 菜单鼠标悬浮背景色 + --g-sub-sidebar-menu-hover-bg: #e1e1e1; + // 菜单选中文字颜色 + --g-sub-sidebar-menu-active-color: #fff; + // 菜单选中背景色 + --g-sub-sidebar-menu-active-bg: var(--el-color-primary); + + /* ----- 顶栏 ----- */ + --g-toolbar-bg: var(--el-bg-color); + + /* ----- 进度条 ----- */ + --g-nprogress-color: var(--el-color-primary); + + /* ----- 暗黑模式 ----- */ + &.dark { + --g-box-shadow-color: rgb(0 0 0 / 72%); /* ----- 应用 ----- */ // 应用背景色 @@ -9,116 +71,54 @@ /* ----- 顶部 ----- */ // 背景色 - --g-header-bg: #222b45; + --g-header-bg: var(--el-bg-color); // 文字颜色 --g-header-color: #fff; // 导航文字颜色 - --g-header-menu-color: #fff; + --g-header-menu-color: #ccc; // 导航鼠标悬浮文字颜色 --g-header-menu-hover-color: #fff; // 导航鼠标悬浮背景色 - --g-header-menu-hover-bg: #334067; + --g-header-menu-hover-bg: var(--el-color-primary-light-5); // 导航选中文字颜色 --g-header-menu-active-color: #fff; // 导航选中背景色 - --g-header-menu-active-bg: #334067; + --g-header-menu-active-bg: var(--el-color-primary-light-3); /* ----- 主导航 ----- */ // 背景色 - --g-main-sidebar-bg: #222b45; + --g-main-sidebar-bg: var(--el-bg-color); // 菜单文字颜色 - --g-main-sidebar-menu-color: #eee; + --g-main-sidebar-menu-color: #ccc; // 菜单鼠标悬浮文字颜色 --g-main-sidebar-menu-hover-color: #fff; // 菜单鼠标悬浮背景色 - --g-main-sidebar-menu-hover-bg: #334067; + --g-main-sidebar-menu-hover-bg: var(--el-color-primary-light-5); // 菜单选中文字颜色 --g-main-sidebar-menu-active-color: #fff; // 菜单选中背景色 - --g-main-sidebar-menu-active-bg: #334067; + --g-main-sidebar-menu-active-bg: var(--el-color-primary-light-3); /* ----- 次导航 ----- */ // 背景色 - --g-sub-sidebar-bg: #fff; + --g-sub-sidebar-bg: var(--el-bg-color-page); // 菜单背景色 - --g-sub-sidebar-menu-bg: #e8e8e8; + --g-sub-sidebar-menu-bg: var(--el-bg-color); // 菜单文字颜色 - --g-sub-sidebar-menu-color: var(--el-text-color-primary); + --g-sub-sidebar-menu-color: var(--el-text-color-secondary); // 菜单鼠标悬浮文字颜色 --g-sub-sidebar-menu-hover-color: var(--el-text-color-primary); // 菜单鼠标悬浮背景色 - --g-sub-sidebar-menu-hover-bg: #e1e1e1; + --g-sub-sidebar-menu-hover-bg: var(--el-color-primary-light-5); // 菜单选中文字颜色 - --g-sub-sidebar-menu-active-color: #fff; + --g-sub-sidebar-menu-active-color: var(--el-text-color-primary); // 菜单选中背景色 - --g-sub-sidebar-menu-active-bg: var(--el-color-primary); + --g-sub-sidebar-menu-active-bg: var(--el-color-primary-light-3); /* ----- 顶栏 ----- */ --g-toolbar-bg: var(--el-bg-color); /* ----- 进度条 ----- */ --g-nprogress-color: var(--el-color-primary); - - /* ----- 暗黑模式 ----- */ - &.dark { - --g-box-shadow-color: rgb(0 0 0 / 72%); - - /* ----- 应用 ----- */ - // 应用背景色 - --g-app-bg: var(--el-bg-color); - // 主区域背景色 - --g-main-bg: var(--el-bg-color-page); - - /* ----- 顶部 ----- */ - // 背景色 - --g-header-bg: var(--el-bg-color); - // 文字颜色 - --g-header-color: #fff; - // 导航文字颜色 - --g-header-menu-color: #ccc; - // 导航鼠标悬浮文字颜色 - --g-header-menu-hover-color: #fff; - // 导航鼠标悬浮背景色 - --g-header-menu-hover-bg: var(--el-color-primary-light-5); - // 导航选中文字颜色 - --g-header-menu-active-color: #fff; - // 导航选中背景色 - --g-header-menu-active-bg: var(--el-color-primary-light-3); - - /* ----- 主导航 ----- */ - // 背景色 - --g-main-sidebar-bg: var(--el-bg-color); - // 菜单文字颜色 - --g-main-sidebar-menu-color: #ccc; - // 菜单鼠标悬浮文字颜色 - --g-main-sidebar-menu-hover-color: #fff; - // 菜单鼠标悬浮背景色 - --g-main-sidebar-menu-hover-bg: var(--el-color-primary-light-5); - // 菜单选中文字颜色 - --g-main-sidebar-menu-active-color: #fff; - // 菜单选中背景色 - --g-main-sidebar-menu-active-bg: var(--el-color-primary-light-3); - - /* ----- 次导航 ----- */ - // 背景色 - --g-sub-sidebar-bg: var(--el-bg-color-page); - // 菜单背景色 - --g-sub-sidebar-menu-bg: var(--el-bg-color); - // 菜单文字颜色 - --g-sub-sidebar-menu-color: var(--el-text-color-secondary); - // 菜单鼠标悬浮文字颜色 - --g-sub-sidebar-menu-hover-color: var(--el-text-color-primary); - // 菜单鼠标悬浮背景色 - --g-sub-sidebar-menu-hover-bg: var(--el-color-primary-light-5); - // 菜单选中文字颜色 - --g-sub-sidebar-menu-active-color: var(--el-text-color-primary); - // 菜单选中背景色 - --g-sub-sidebar-menu-active-bg: var(--el-color-primary-light-3); - - /* ----- 顶栏 ----- */ - --g-toolbar-bg: var(--el-bg-color); - - /* ----- 进度条 ----- */ - --g-nprogress-color: var(--el-color-primary); - } + } } diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts new file mode 100644 index 00000000..5e30355b --- /dev/null +++ b/src/auto-imports.d.ts @@ -0,0 +1,75 @@ +// Generated by 'unplugin-auto-import' +export {} +declare global { + const EffectScope: typeof import('vue')['EffectScope'] + const ElMessage: typeof import('element-plus/es')['ElMessage'] + const ElNotification: typeof import('element-plus/es')['ElNotification'] + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] + const computed: typeof import('vue')['computed'] + const createApp: typeof import('vue')['createApp'] + const createPinia: typeof import('pinia')['createPinia'] + const customRef: typeof import('vue')['customRef'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const defineStore: typeof import('pinia')['defineStore'] + const effectScope: typeof import('vue')['effectScope'] + const getActivePinia: typeof import('pinia')['getActivePinia'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const inject: typeof import('vue')['inject'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const mapActions: typeof import('pinia')['mapActions'] + const mapGetters: typeof import('pinia')['mapGetters'] + const mapState: typeof import('pinia')['mapState'] + const mapStores: typeof import('pinia')['mapStores'] + const mapWritableState: typeof import('pinia')['mapWritableState'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const provide: typeof import('vue')['provide'] + const reactive: typeof import('vue')['reactive'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const setActivePinia: typeof import('pinia')['setActivePinia'] + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const storeToRefs: typeof import('pinia')['storeToRefs'] + const toRaw: typeof import('vue')['toRaw'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const triggerRef: typeof import('vue')['triggerRef'] + const unref: typeof import('vue')['unref'] + const useAttrs: typeof import('vue')['useAttrs'] + const useAuth: typeof import('./utils/composables/useAuth')['default'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVars: typeof import('vue')['useCssVars'] + const useCurrentInstance: typeof import('./utils/composables/useCurrentInstance')['default'] + const useMainPage: typeof import('./utils/composables/useMainPage')['default'] + const useMenu: typeof import('./utils/composables/useMenu')['default'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useSlots: typeof import('vue')['useSlots'] + const watch: typeof import('vue')['watch'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] +} diff --git a/src/components.d.ts b/src/components.d.ts new file mode 100644 index 00000000..d96655b3 --- /dev/null +++ b/src/components.d.ts @@ -0,0 +1,30 @@ +// generated by unplugin-vue-components +// We suggest you to commit this file into source control +// Read more: https://github.com/vuejs/core/pull/3399 +import '@vue/runtime-core' + +export {} + +declare module '@vue/runtime-core' { + export interface GlobalComponents { + Auth: typeof import('./components/Auth/index.vue')['default'] + AuthAll: typeof import('./components/AuthAll/index.vue')['default'] + BatchActionBar: typeof import('./components/BatchActionBar/index.vue')['default'] + Copyright: typeof import('./components/Copyright/index.vue')['default'] + Editor: typeof import('./components/Editor/index.vue')['default'] + FileUpload: typeof import('./components/FileUpload/index.vue')['default'] + FixedActionBar: typeof import('./components/FixedActionBar/index.vue')['default'] + ImagePreview: typeof import('./components/ImagePreview/index.vue')['default'] + ImagesUpload: typeof import('./components/ImagesUpload/index.vue')['default'] + ImageUpload: typeof import('./components/ImageUpload/index.vue')['default'] + PageHeader: typeof import('./components/PageHeader/index.vue')['default'] + PageMain: typeof import('./components/PageMain/index.vue')['default'] + PcasCascader: typeof import('./components/PcasCascader/index.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + SearchBar: typeof import('./components/SearchBar/index.vue')['default'] + SvgIcon: typeof import('./components/SvgIcon/index.vue')['default'] + SystemInfo: typeof import('./components/SystemInfo/index.vue')['default'] + Trend: typeof import('./components/Trend/index.vue')['default'] + } +} diff --git a/src/components/Auth/index.vue b/src/components/Auth/index.vue index 9f475e72..a43fa74a 100644 --- a/src/components/Auth/index.vue +++ b/src/components/Auth/index.vue @@ -1,21 +1,16 @@ - diff --git a/src/components/AuthAll/index.vue b/src/components/AuthAll/index.vue index 49390e99..3c4178c8 100644 --- a/src/components/AuthAll/index.vue +++ b/src/components/AuthAll/index.vue @@ -1,21 +1,16 @@ - diff --git a/src/components/BatchActionBar/index.vue b/src/components/BatchActionBar/index.vue index 803ffdc1..1e0dc48a 100644 --- a/src/components/BatchActionBar/index.vue +++ b/src/components/BatchActionBar/index.vue @@ -1,73 +1,82 @@ - diff --git a/src/components/Copyright/index.vue b/src/components/Copyright/index.vue index 25c7196a..e770b65c 100644 --- a/src/components/Copyright/index.vue +++ b/src/components/Copyright/index.vue @@ -1,44 +1,50 @@ - diff --git a/src/components/Editor/index.vue b/src/components/Editor/index.vue index 09f2331c..2d96b9c5 100644 --- a/src/components/Editor/index.vue +++ b/src/components/Editor/index.vue @@ -1,4 +1,4 @@ - diff --git a/src/components/FileUpload/index.vue b/src/components/FileUpload/index.vue index bee145b8..37a1db78 100644 --- a/src/components/FileUpload/index.vue +++ b/src/components/FileUpload/index.vue @@ -1,107 +1,117 @@ - diff --git a/src/components/FixedActionBar/index.vue b/src/components/FixedActionBar/index.vue index c4839659..86bd7eac 100644 --- a/src/components/FixedActionBar/index.vue +++ b/src/components/FixedActionBar/index.vue @@ -1,53 +1,49 @@ - diff --git a/src/components/ImagePreview/index.vue b/src/components/ImagePreview/index.vue index 86b1c05c..cd6fc591 100644 --- a/src/components/ImagePreview/index.vue +++ b/src/components/ImagePreview/index.vue @@ -1,61 +1,64 @@ - diff --git a/src/components/ImageUpload/index.vue b/src/components/ImageUpload/index.vue index e3214837..ac369cbe 100644 --- a/src/components/ImageUpload/index.vue +++ b/src/components/ImageUpload/index.vue @@ -1,242 +1,263 @@ - diff --git a/src/components/ImagesUpload/index.vue b/src/components/ImagesUpload/index.vue index 7696c251..5e765389 100644 --- a/src/components/ImagesUpload/index.vue +++ b/src/components/ImagesUpload/index.vue @@ -1,275 +1,298 @@ - diff --git a/src/components/PageHeader/index.vue b/src/components/PageHeader/index.vue index 52955afb..3dc41f39 100644 --- a/src/components/PageHeader/index.vue +++ b/src/components/PageHeader/index.vue @@ -1,59 +1,68 @@ - diff --git a/src/components/PageMain/index.vue b/src/components/PageMain/index.vue index 84a24091..f325bc12 100644 --- a/src/components/PageMain/index.vue +++ b/src/components/PageMain/index.vue @@ -1,84 +1,89 @@ - diff --git a/src/components/PcasCascader/index.vue b/src/components/PcasCascader/index.vue index d07e3f12..69029488 100644 --- a/src/components/PcasCascader/index.vue +++ b/src/components/PcasCascader/index.vue @@ -1,131 +1,137 @@ - diff --git a/src/components/SearchBar/index.vue b/src/components/SearchBar/index.vue index 4f744ac5..e4906864 100644 --- a/src/components/SearchBar/index.vue +++ b/src/components/SearchBar/index.vue @@ -1,13 +1,13 @@ - diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue index 90c565d0..65fb51b0 100644 --- a/src/components/SvgIcon/index.vue +++ b/src/components/SvgIcon/index.vue @@ -1,53 +1,47 @@ - diff --git a/src/components/SystemInfo/index.vue b/src/components/SystemInfo/index.vue new file mode 100644 index 00000000..d4f4f4b1 --- /dev/null +++ b/src/components/SystemInfo/index.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/components/Trend/index.vue b/src/components/Trend/index.vue index 87641278..e51e5e21 100644 --- a/src/components/Trend/index.vue +++ b/src/components/Trend/index.vue @@ -1,65 +1,68 @@ - diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 00000000..178b234d --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,266 @@ +import type { StyleValue } from "vue" +import type { RouteRecordRaw, RouteMeta, NavigationGuard } from 'vue-router' + +type RecursiveRequired = { + [P in keyof T]-?: RecursiveRequired +} + +declare namespace Settings { + interface app { + /** + * 颜色方案 + * @默认值 `''` 跟随系统 + * @可选值 `'light'` 明亮模式 + * @可选值 `'dark'` 暗黑模式 + */ + colorScheme?: '' | 'light' | 'dark' + /** + * Element Plus 组件默认尺寸 + * @默认值 `'default'` 默认 + * @可选值 `'large'` 大号 + * @可选值 `'small'` 小号 + */ + elementSize?: 'default' | 'large' | 'small' + /** + * 是否将 iconify 图标集中的 Element Plus 图标下载到本地,以便在离线环境下使用 + * @默认值 `false` + */ + iconifyOfflineUse?: boolean + /** + * 是否开启权限功能 + * @默认值 `false` + */ + enablePermission?: boolean + /** + * 是否开启载入进度条 + * @默认值 `true` + */ + enableProgress?: boolean + /** + * 是否开启动态标题 + * @默认值 `false` + */ + enableDynamicTitle?: boolean + /** + * 路由数据来源 + * @默认值 `'frontend'` 前端 + * @可选值 `'backend'` 后端 + * @可选值 `'filesystem'` 文件系统 + */ + routeBaseOn?: 'frontend' | 'backend' | 'filesystem' + } + interface dashboard { + /** + * 是否开启控制台页面 + * @默认值 `true` + */ + enable?: boolean + /** + * 控制台名称 + * @默认值 `'控制台'` + */ + title?: string + } + interface layout { + /** + * 是否开启移动端适配,开启后当页面宽度小于 992px 时自动切换为移动端展示 + * @默认值 `false` + */ + enableMobileAdaptation?: boolean + } + interface menu { + /** + * 导航栏数据来源,当 `app.routeBaseOn: 'filesystem'` 时生效 + * @默认值 `'frontend'` 前端 + * @可选值 `'backend'` 后端 + */ + baseOn?: 'frontend' | 'backend' + /** + * 导航栏模式 + * @默认值 `'side'` 侧边栏模式(有主导航) + * @可选值 `'head'` 顶部模式 + * @可选值 `'single'` 侧边栏模式(无主导航) + */ + menuMode?: 'side' | 'head' | 'single' + /** + * 切换主导航是否跳转页面 + * @默认值 `false` + */ + switchMainMenuAndPageJump?: boolean + /** + * 次导航是否只保持一个子项的展开 + * @默认值 `true` + */ + subMenuUniqueOpened?: boolean + /** + * 次导航是否收起 + * @默认值 `false` + */ + subMenuCollapse?: boolean + /** + * 是否开启次导航的展开/收起按钮 + * @默认值 `false` + */ + enableSubMenuCollapseButton?: boolean + /** + * 是否开启主导航切换快捷键 + * @默认值 `false` + */ + enableHotkeys?: boolean + } + interface topbar { + /** + * 是否固定顶栏 + * @默认值 `true` + */ + fixed?: boolean + } + interface toolbar { + /** + * 是否开启全屏 + * @默认值 `false` + */ + enableFullscreen?: boolean + /** + * 是否开启页面刷新 + * @默认值 `false` + */ + enablePageReload?: boolean + /** + * 是否开启颜色主题 + * @默认值 `false` + */ + enableColorScheme?: boolean + /** + * 是否开启应用配置,强烈建议在生产环境中关闭 + * @默认值 `false` + */ + enableAppSetting?: boolean + } + interface breadcrumb { + /** + * 是否开启面包屑导航 + * @默认值 `true` + */ + enable?: boolean + } + interface navSearch { + /** + * 是否开启导航搜索 + * @默认值 `true` + */ + enable?: boolean + /** + * 是否开启导航搜索快捷键 + * @默认值 `true` + */ + enableHotkeys?: boolean + } + interface copyright { + /** + * 是否开启底部版权,同时在路由 meta 对象里可以单独设置某个路由是否显示底部版权信息 + * @默认值 `true` + */ + enable?: boolean + /** + * 网站运行日期 + * @默认值 `'2020-2022'` + */ + dates?: string + /** + * 公司名称 + * @默认值 `'Fantastic-admin'` + */ + company?: string + /** + * 网站地址 + * @默认值 `'https://hooray.gitee.io/fantastic-admin/'` + */ + website?: string + /** + * 网站备案号 + * @默认值 `''` + */ + beian?: string + } + interface all { + /** 应用设置 */ + app?: app + /** 控制台设置 */ + dashboard?: dashboard + /** 布局设置 */ + layout?: layout + /** 导航栏设置 */ + menu?: menu + /** 顶栏设置 */ + topbar?: topbar + /** 工具栏设置 */ + toolbar?: toolbar + /** 面包屑导航设置 */ + breadcrumb?: breadcrumb + /** 导航搜索设置 */ + navSearch?: navSearch + /** 底部版权设置 */ + copyright?: copyright + } +} + +declare module 'vue-router' { + interface RouteMeta { + title: string | Function + i18n?: string + icon?: string + activeIcon?: string + defaultOpened?: boolean + auth?: string | string[] + sidebar?: boolean + breadcrumb?: boolean + activeMenu?: string + cache?: boolean | string | string[] + breadcrumbNeste?: Route.breadcrumb[] + } +} + +declare namespace Route { + interface recordRaw extends Omit { + meta: RouteMeta + children?: recordRaw[] + } + interface recordMainRaw { + meta: { + title?: string | Function + icon?: string + auth?: string | string[] + } + children: recordRaw[] + } + interface breadcrumb { + path: string + title: string | Function + hide: boolean + } +} + +declare namespace Menu { + /** 原始 */ + interface recordRaw { + path?: string + meta: { + title: string + icon?: string + defaultOpened?: boolean + auth?: string | string[] + sidebar?: boolean + } + children?: recordRaw[] + } + /** 主导航 */ + interface recordMainRaw { + meta: { + title?: string + icon?: string + auth?: string | string[] + } + children: recordRaw[] + } +} diff --git a/src/iconify-ep/index.js b/src/iconify-ep/index.js deleted file mode 100644 index 5bc75eb9..00000000 --- a/src/iconify-ep/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import { addCollection } from '@iconify/vue' - -export async function downloadAndInstall() { - const data = Object.freeze(await fetch('./icons/icons.json').then(r => r.json())) - addCollection(data) -} diff --git a/src/iconify-ep/index.ts b/src/iconify-ep/index.ts new file mode 100644 index 00000000..37deffa0 --- /dev/null +++ b/src/iconify-ep/index.ts @@ -0,0 +1,6 @@ +import { addCollection } from '@iconify/vue' + +export async function downloadAndInstall() { + const data = Object.freeze(await fetch('./icons/icons.json').then(r => r.json())) + addCollection(data) +} diff --git a/src/layout/components/AppSetting/index.vue b/src/layout/components/AppSetting/index.vue deleted file mode 100644 index 6404d264..00000000 --- a/src/layout/components/AppSetting/index.vue +++ /dev/null @@ -1,457 +0,0 @@ - - - - - diff --git a/src/layout/components/BuyIt/index.vue b/src/layout/components/BuyIt/index.vue deleted file mode 100644 index 15213a05..00000000 --- a/src/layout/components/BuyIt/index.vue +++ /dev/null @@ -1,114 +0,0 @@ - - - - - diff --git a/src/layout/components/Header/index.vue b/src/layout/components/Header/index.vue deleted file mode 100644 index 51c68163..00000000 --- a/src/layout/components/Header/index.vue +++ /dev/null @@ -1,170 +0,0 @@ - - - - - diff --git a/src/layout/components/Logo/index.vue b/src/layout/components/Logo/index.vue deleted file mode 100644 index b04cff27..00000000 --- a/src/layout/components/Logo/index.vue +++ /dev/null @@ -1,68 +0,0 @@ - - - - - diff --git a/src/layout/components/MainSidebar/index.vue b/src/layout/components/MainSidebar/index.vue deleted file mode 100644 index 54ab7c70..00000000 --- a/src/layout/components/MainSidebar/index.vue +++ /dev/null @@ -1,101 +0,0 @@ - - - - - diff --git a/src/layout/components/Search/index.vue b/src/layout/components/Search/index.vue deleted file mode 100644 index 52194e7c..00000000 --- a/src/layout/components/Search/index.vue +++ /dev/null @@ -1,398 +0,0 @@ - - - - - diff --git a/src/layout/components/SidebarItem/index.vue b/src/layout/components/SidebarItem/index.vue deleted file mode 100644 index 1e6bcd7e..00000000 --- a/src/layout/components/SidebarItem/index.vue +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - diff --git a/src/layout/components/SubSidebar/index.vue b/src/layout/components/SubSidebar/index.vue deleted file mode 100644 index 20d574d6..00000000 --- a/src/layout/components/SubSidebar/index.vue +++ /dev/null @@ -1,140 +0,0 @@ - - - - - diff --git a/src/layout/components/Tools/index.vue b/src/layout/components/Tools/index.vue deleted file mode 100644 index 84ac25e9..00000000 --- a/src/layout/components/Tools/index.vue +++ /dev/null @@ -1,168 +0,0 @@ - - - - - diff --git a/src/layout/components/Topbar/index.vue b/src/layout/components/Topbar/index.vue deleted file mode 100644 index 851a0bef..00000000 --- a/src/layout/components/Topbar/index.vue +++ /dev/null @@ -1,142 +0,0 @@ - - - - - diff --git a/src/layout/index.vue b/src/layout/index.vue deleted file mode 100644 index 392ffd04..00000000 --- a/src/layout/index.vue +++ /dev/null @@ -1,210 +0,0 @@ - - - - - diff --git a/src/layouts/components/AppSetting/index.vue b/src/layouts/components/AppSetting/index.vue new file mode 100644 index 00000000..8918186c --- /dev/null +++ b/src/layouts/components/AppSetting/index.vue @@ -0,0 +1,532 @@ + + + + + diff --git a/src/layouts/components/BuyIt/index.vue b/src/layouts/components/BuyIt/index.vue new file mode 100644 index 00000000..5e757e3a --- /dev/null +++ b/src/layouts/components/BuyIt/index.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/src/layouts/components/Header/index.vue b/src/layouts/components/Header/index.vue new file mode 100644 index 00000000..6339ad0e --- /dev/null +++ b/src/layouts/components/Header/index.vue @@ -0,0 +1,191 @@ + + + + + diff --git a/src/layouts/components/HotkeysIntro/index.vue b/src/layouts/components/HotkeysIntro/index.vue new file mode 100644 index 00000000..22e25784 --- /dev/null +++ b/src/layouts/components/HotkeysIntro/index.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/layouts/components/Logo/index.vue b/src/layouts/components/Logo/index.vue new file mode 100644 index 00000000..68eed913 --- /dev/null +++ b/src/layouts/components/Logo/index.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/layouts/components/MainSidebar/index.vue b/src/layouts/components/MainSidebar/index.vue new file mode 100644 index 00000000..40d4fa33 --- /dev/null +++ b/src/layouts/components/MainSidebar/index.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/layouts/components/Search/index.vue b/src/layouts/components/Search/index.vue new file mode 100644 index 00000000..2ad595b6 --- /dev/null +++ b/src/layouts/components/Search/index.vue @@ -0,0 +1,448 @@ + + + + + diff --git a/src/layouts/components/SidebarItem/index.vue b/src/layouts/components/SidebarItem/index.vue new file mode 100644 index 00000000..1a236f93 --- /dev/null +++ b/src/layouts/components/SidebarItem/index.vue @@ -0,0 +1,151 @@ + + + + + + + diff --git a/src/layouts/components/SubSidebar/index.vue b/src/layouts/components/SubSidebar/index.vue new file mode 100644 index 00000000..cf6833bf --- /dev/null +++ b/src/layouts/components/SubSidebar/index.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/src/layouts/components/Tools/index.vue b/src/layouts/components/Tools/index.vue new file mode 100644 index 00000000..94bd4570 --- /dev/null +++ b/src/layouts/components/Tools/index.vue @@ -0,0 +1,191 @@ + + + + + diff --git a/src/layouts/components/Topbar/index.vue b/src/layouts/components/Topbar/index.vue new file mode 100644 index 00000000..1d3c4092 --- /dev/null +++ b/src/layouts/components/Topbar/index.vue @@ -0,0 +1,155 @@ + + + + + diff --git a/src/layouts/index.vue b/src/layouts/index.vue new file mode 100644 index 00000000..768dca0c --- /dev/null +++ b/src/layouts/index.vue @@ -0,0 +1,240 @@ + + + + + diff --git a/src/main.js b/src/main.ts similarity index 80% rename from src/main.js rename to src/main.ts index c0c63405..41759e36 100644 --- a/src/main.js +++ b/src/main.ts @@ -1,30 +1,25 @@ import { createApp } from 'vue' +import ElementPlus from 'element-plus' import App from './App.vue' -const app = createApp(App) - import pinia from './store' -app.use(pinia) - import router from './router' -app.use(router) - -/* importElementPlusPlaceholder */ +import useSettingsStore from './store/modules/settings' // 自定义指令 import directive from '@/utils/directive' -directive(app) - // 加载 svg 图标 import 'virtual:svg-icons-register' - -// 加载 iconify 图标(element plus) -import { downloadAndInstall } from '@/iconify-ep' -import useSettingsStore from './store/modules/settings' -if (useSettingsStore().app.iconifyOfflineUse) { - downloadAndInstall() -} - // 全局样式 import '@/assets/styles/globals.scss' +// 加载 iconify 图标(element plus) +import { downloadAndInstall } from '@/iconify-ep' +if (useSettingsStore().app.iconifyOfflineUse) + downloadAndInstall() + +const app = createApp(App) +app.use(ElementPlus) +app.use(pinia) +app.use(router) +directive(app) app.mount('#app') diff --git a/src/menu/index.js b/src/menu/index.js deleted file mode 100644 index 2e76b864..00000000 --- a/src/menu/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import MultilevelMenuExample from './modules/multilevel.menu.example' - -const menu = [ - { - meta: { - title: '演示', - icon: 'sidebar-default' - }, - children: [ - MultilevelMenuExample - ] - } -] - -export default menu diff --git a/src/menu/index.ts b/src/menu/index.ts new file mode 100644 index 00000000..aff0e3e5 --- /dev/null +++ b/src/menu/index.ts @@ -0,0 +1,17 @@ +import MultilevelMenuExample from './modules/multilevel.menu.example' + +import type { Menu } from '@/global' + +const menu: Menu.recordMainRaw[] = [ + { + meta: { + title: '演示', + icon: 'sidebar-default', + }, + children: [ + MultilevelMenuExample, + ], + }, +] + +export default menu diff --git a/src/menu/modules/multilevel.menu.example.js b/src/menu/modules/multilevel.menu.example.js deleted file mode 100644 index 40f6ea72..00000000 --- a/src/menu/modules/multilevel.menu.example.js +++ /dev/null @@ -1,46 +0,0 @@ -export default { - meta: { - title: '多级导航', - icon: 'sidebar-menu' - }, - children: [ - { - path: '/multilevel_menu_example/page', - meta: { - title: '导航1' - } - }, - { - meta: { - title: '导航2' - }, - children: [ - { - path: '/multilevel_menu_example/level2/page', - meta: { - title: '导航2-1' - } - }, - { - meta: { - title: '导航2-2' - }, - children: [ - { - path: '/multilevel_menu_example/level2/level3/page1', - meta: { - title: '导航2-2-1' - } - }, - { - path: '/multilevel_menu_example/level2/level3/page2', - meta: { - title: '导航2-2-2' - } - } - ] - } - ] - } - ] -} diff --git a/src/menu/modules/multilevel.menu.example.ts b/src/menu/modules/multilevel.menu.example.ts new file mode 100644 index 00000000..ce26d431 --- /dev/null +++ b/src/menu/modules/multilevel.menu.example.ts @@ -0,0 +1,50 @@ +import type { Menu } from '@/global' + +const menus: Menu.recordRaw = { + meta: { + title: '多级导航', + icon: 'heroicons-solid:menu-alt-3', + }, + children: [ + { + path: '/multilevel_menu_example/page', + meta: { + title: '导航1', + }, + }, + { + meta: { + title: '导航2', + }, + children: [ + { + path: '/multilevel_menu_example/level2/page', + meta: { + title: '导航2-1', + }, + }, + { + meta: { + title: '导航2-2', + }, + children: [ + { + path: '/multilevel_menu_example/level2/level3/page1', + meta: { + title: '导航2-2-1', + }, + }, + { + path: '/multilevel_menu_example/level2/level3/page2', + meta: { + title: '导航2-2-2', + }, + }, + ], + }, + ], + }, + ], +} + +export default menus diff --git a/src/mock/member.js b/src/mock/member.js deleted file mode 100644 index e9bdef5b..00000000 --- a/src/mock/member.js +++ /dev/null @@ -1,54 +0,0 @@ -export default [ - { - url: '/mock/member/login', - method: 'post', - response: option => { - return { - error: '', - status: 1, - data: { - account: option.body.account, - token: '@string', - failure_time: Math.ceil(new Date().getTime() / 1000) + 24 * 60 * 60 - } - } - } - }, - { - url: '/mock/member/permission', - method: 'get', - response: option => { - let permissions = [] - if (option.query.account == 'admin') { - permissions = [ - 'permission.browse', - 'permission.create', - 'permission.edit', - 'permission.remove' - ] - } else if (option.query.account == 'test') { - permissions = [ - 'permission.browse' - ] - } - return { - error: '', - status: 1, - data: { - permissions - } - } - } - }, - { - url: '/mock/member/edit/password', - method: 'post', - response: { - error: '', - status: 1, - data: { - isSuccess: true - } - } - } -] diff --git a/src/mock/member.ts b/src/mock/member.ts new file mode 100644 index 00000000..5f36cb41 --- /dev/null +++ b/src/mock/member.ts @@ -0,0 +1,55 @@ +export default [ + { + url: '/mock/member/login', + method: 'post', + response: (option: any) => { + return { + error: '', + status: 1, + data: { + account: option.body.account, + token: '@string', + failure_time: Math.ceil(new Date().getTime() / 1000) + 24 * 60 * 60, + }, + } + }, + }, + { + url: '/mock/member/permission', + method: 'get', + response: (option: any) => { + let permissions: string[] = [] + if (option.query.account === 'admin') { + permissions = [ + 'permission.browse', + 'permission.create', + 'permission.edit', + 'permission.remove', + ] + } + else if (option.query.account === 'test') { + permissions = [ + 'permission.browse', + ] + } + return { + error: '', + status: 1, + data: { + permissions, + }, + } + }, + }, + { + url: '/mock/member/edit/password', + method: 'post', + response: { + error: '', + status: 1, + data: { + isSuccess: true, + }, + }, + }, +] diff --git a/src/mock/menu.js b/src/mock/menu.js deleted file mode 100644 index 7b67c94d..00000000 --- a/src/mock/menu.js +++ /dev/null @@ -1,68 +0,0 @@ -export default [ - { - url: '/mock/menu/list', - method: 'get', - response: () => { - return { - error: '', - status: 1, - data: [ - { - meta: { - title: '演示', - icon: 'sidebar-default' - }, - children: [ - { - meta: { - title: '多级导航', - icon: 'sidebar-menu' - }, - children: [ - { - path: '/multilevel_menu_example/page', - meta: { - title: '导航1' - } - }, - { - meta: { - title: '导航2' - }, - children: [ - { - path: '/multilevel_menu_example/level2/page', - meta: { - title: '导航2-1' - } - }, - { - meta: { - title: '导航2-2' - }, - children: [ - { - path: '/multilevel_menu_example/level2/level3/page1', - meta: { - title: '导航2-2-1' - } - }, - { - path: '/multilevel_menu_example/level2/level3/page2', - meta: { - title: '导航2-2-2' - } - } - ] - } - ] - } - ] - } - ] - } - ] - } - } - } -] diff --git a/src/mock/menu.ts b/src/mock/menu.ts new file mode 100644 index 00000000..639a6989 --- /dev/null +++ b/src/mock/menu.ts @@ -0,0 +1,68 @@ +export default [ + { + url: '/mock/menu/list', + method: 'get', + response: () => { + return { + error: '', + status: 1, + data: [ + { + meta: { + title: '演示', + icon: 'sidebar-default', + }, + children: [ + { + meta: { + title: '多级导航', + icon: 'sidebar-menu', + }, + children: [ + { + path: '/multilevel_menu_example/page', + meta: { + title: '导航1', + }, + }, + { + meta: { + title: '导航2', + }, + children: [ + { + path: '/multilevel_menu_example/level2/page', + meta: { + title: '导航2-1', + }, + }, + { + meta: { + title: '导航2-2', + }, + children: [ + { + path: '/multilevel_menu_example/level2/level3/page1', + meta: { + title: '导航2-2-1', + }, + }, + { + path: '/multilevel_menu_example/level2/level3/page2', + meta: { + title: '导航2-2-2', + }, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + } + }, + }, +] diff --git a/src/mock/route.js b/src/mock/route.js deleted file mode 100644 index 051e2a91..00000000 --- a/src/mock/route.js +++ /dev/null @@ -1,86 +0,0 @@ -export default [ - { - url: '/mock/route/list', - method: 'get', - response: () => { - return { - error: '', - status: 1, - data: [ - { - meta: { - title: '演示', - icon: 'sidebar-default' - }, - children: [ - { - path: '/multilevel_menu_example', - component: 'Layout', - redirect: '/multilevel_menu_example/page', - name: 'multilevelMenuExample', - meta: { - title: '多级导航', - icon: 'sidebar-menu' - }, - children: [ - { - path: 'page', - name: 'multilevelMenuExample1', - component: 'multilevel_menu_example/page.vue', - meta: { - title: '导航1' - } - }, - { - path: 'level2', - name: 'multilevelMenuExample2', - redirect: '/multilevel_menu_example/level2/page', - meta: { - title: '导航2' - }, - children: [ - { - path: 'page', - name: 'multilevelMenuExample2-1', - component: 'multilevel_menu_example/level2/page.vue', - meta: { - title: '导航2-1' - } - }, - { - path: 'level3', - name: 'multilevelMenuExample2-2', - redirect: '/multilevel_menu_example/level2/level3/page1', - meta: { - title: '导航2-2' - }, - children: [ - { - path: 'page1', - name: 'multilevelMenuExample2-2-1', - component: 'multilevel_menu_example/level2/level3/page1.vue', - meta: { - title: '导航2-2-1' - } - }, - { - path: 'page2', - name: 'multilevelMenuExample2-2-2', - component: 'multilevel_menu_example/level2/level3/page2.vue', - meta: { - title: '导航2-2-2' - } - } - ] - } - ] - } - ] - } - ] - } - ] - } - } - } -] diff --git a/src/mock/route.ts b/src/mock/route.ts new file mode 100644 index 00000000..e35890ab --- /dev/null +++ b/src/mock/route.ts @@ -0,0 +1,86 @@ +export default [ + { + url: '/mock/route/list', + method: 'get', + response: () => { + return { + error: '', + status: 1, + data: [ + { + meta: { + title: '演示', + icon: 'sidebar-default', + }, + children: [ + { + path: '/multilevel_menu_example', + component: 'Layout', + redirect: '/multilevel_menu_example/page', + name: 'multilevelMenuExample', + meta: { + title: '多级导航', + icon: 'sidebar-menu', + }, + children: [ + { + path: 'page', + name: 'multilevelMenuExample1', + component: 'multilevel_menu_example/page.vue', + meta: { + title: '导航1', + }, + }, + { + path: 'level2', + name: 'multilevelMenuExample2', + redirect: '/multilevel_menu_example/level2/page', + meta: { + title: '导航2', + }, + children: [ + { + path: 'page', + name: 'multilevelMenuExample2-1', + component: 'multilevel_menu_example/level2/page.vue', + meta: { + title: '导航2-1', + }, + }, + { + path: 'level3', + name: 'multilevelMenuExample2-2', + redirect: '/multilevel_menu_example/level2/level3/page1', + meta: { + title: '导航2-2', + }, + children: [ + { + path: 'page1', + name: 'multilevelMenuExample2-2-1', + component: 'multilevel_menu_example/level2/level3/page1.vue', + meta: { + title: '导航2-2-1', + }, + }, + { + path: 'page2', + name: 'multilevelMenuExample2-2-2', + component: 'multilevel_menu_example/level2/level3/page2.vue', + meta: { + title: '导航2-2-2', + }, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + } + }, + }, +] diff --git a/src/mockProdServer.js b/src/mockProdServer.js deleted file mode 100644 index 84271fa8..00000000 --- a/src/mockProdServer.js +++ /dev/null @@ -1,11 +0,0 @@ -import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer' - -const mocks = [] -const mockContext = import.meta.globEager('./mock/*.js') -Object.keys(mockContext).forEach(v => { - mocks.push(...mockContext[v].default) -}) - -export function setupProdMockServer() { - createProdMockServer(mocks) -} diff --git a/src/mockProdServer.ts b/src/mockProdServer.ts new file mode 100644 index 00000000..a9900a0a --- /dev/null +++ b/src/mockProdServer.ts @@ -0,0 +1,11 @@ +import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer' + +const mocks: any[] = [] +const mockContext: any = import.meta.glob('./mock/*.js', { eager: true }) +Object.keys(mockContext).forEach((v) => { + mocks.push(...mockContext[v].default) +}) + +export function setupProdMockServer() { + createProdMockServer(mocks) +} diff --git a/src/router/index.js b/src/router/index.js deleted file mode 100644 index 2d85fb57..00000000 --- a/src/router/index.js +++ /dev/null @@ -1,153 +0,0 @@ -import { createRouter, createWebHashHistory } from 'vue-router' -import useSettingsStore from '@/store/modules/settings' -import useKeepAliveStore from '@/store/modules/keepAlive' -import useUserStore from '@/store/modules/user' -import useMenuStore from '@/store/modules/menu' -import useRouteStore from '@/store/modules/route' - -import '@/assets/styles/nprogress.scss' -import { useNProgress } from '@vueuse/integrations/useNProgress' -const { isLoading } = useNProgress() - -// 路由相关数据 -import { constantRoutes, asyncRoutes } from './routes' - -const router = createRouter({ - history: createWebHashHistory(), - routes: constantRoutes -}) - -router.beforeEach(async(to, from, next) => { - const settingsStore = useSettingsStore() - const userStore = useUserStore() - const menuStore = useMenuStore() - const routeStore = useRouteStore() - settingsStore.app.enableProgress && (isLoading.value = true) - // 是否已登录 - if (userStore.isLogin) { - // 是否已根据权限动态生成并挂载路由 - if (routeStore.isGenerate) { - // 导航栏如果不是 single 模式,则需要根据 path 定位主导航的选中状态 - settingsStore.menu.menuMode !== 'single' && menuStore.setActived(to.path) - if (to.name) { - if (to.matched.length !== 0) { - // 如果已登录状态下,进入登录页会强制跳转到控制台页面 - if (to.name == 'login') { - next({ - name: 'dashboard', - replace: true - }) - } else if (!settingsStore.dashboard.enable && to.name == 'dashboard') { - // 如果未开启控制台页面,则默认进入侧边栏导航第一个模块 - if (menuStore.sidebarMenus.length > 0) { - next({ - path: menuStore.sidebarMenusFirstDeepestPath, - replace: true - }) - } else { - next() - } - } else { - next() - } - } else { - // 如果是通过 name 跳转,并且 name 对应的路由没有权限时,需要做这步处理,手动指向到 404 页面 - next({ - path: '/404' - }) - } - } else { - next() - } - } else { - switch (settingsStore.app.routeBaseOn) { - case 'frontend': - await routeStore.generateRoutesAtFront(asyncRoutes) - break - case 'backend': - await routeStore.generateRoutesAtBack() - break - case 'filesystem': - await routeStore.generateRoutesAtFilesystem(asyncRoutes) - switch (settingsStore.menu.baseOn) { - case 'frontend': - await menuStore.generateMenusAtFront() - break - case 'backend': - await menuStore.generateMenusAtBack() - break - } - break - } - let removeRoutes = [] - routeStore.flatRoutes.forEach(route => { - if (!/^(https?:|mailto:|tel:)/.test(route.path)) { - removeRoutes.push(router.addRoute(route)) - } - }) - if (settingsStore.app.routeBaseOn !== 'filesystem') { - routeStore.flatSystemRoutes.forEach(route => { - removeRoutes.push(router.addRoute(route)) - }) - } - // 记录的 accessRoutes 路由数据,在登出时会使用到,不使用 router.removeRoute 是考虑配置的路由可能不一定有设置 name ,则通过调用 router.addRoute() 返回的回调进行删除 - routeStore.setCurrentRemoveRoutes(removeRoutes) - next({ - path: to.fullPath, - query: to.query, - replace: true - }) - } - } else { - if (to.name != 'login') { - next({ - name: 'login', - query: { - redirect: to.fullPath - } - }) - } else { - next() - } - } -}) - -router.afterEach((to, from) => { - const settingsStore = useSettingsStore() - const keepAliveStore = useKeepAliveStore() - settingsStore.app.enableProgress && (isLoading.value = false) - // 设置页面 title - to.meta.title && settingsStore.setTitle(typeof to.meta.title === 'function' ? to.meta.title() : to.meta.title) - // 判断当前页面是否开启缓存,如果开启,则将当前页面的 name 信息存入 keep-alive 全局状态 - if (to.meta.cache) { - let componentName = to.matched[to.matched.length - 1].components.default.name - if (componentName) { - keepAliveStore.add(componentName) - } else { - console.warn('该页面组件未设置组件名,会导致缓存失效,请检查') - } - } - // 判断离开页面是否开启缓存,如果开启,则根据缓存规则判断是否需要清空 keep-alive 全局状态里离开页面的 name 信息 - if (from.meta.cache) { - let componentName = from.matched[from.matched.length - 1].components.default.name - // 通过 meta.cache 判断针对哪些页面进行缓存 - switch (typeof from.meta.cache) { - case 'string': - if (from.meta.cache != to.name) { - keepAliveStore.remove(componentName) - } - break - case 'object': - if (!from.meta.cache.includes(to.name)) { - keepAliveStore.remove(componentName) - } - break - } - // 如果进入的是 reload 页面,则也将离开页面的缓存清空 - if (to.name == 'reload') { - keepAliveStore.remove(componentName) - } - } -}) - -export default router diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 00000000..3d680a81 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,164 @@ +import { createRouter, createWebHashHistory } from 'vue-router' +import type { RouteRecordRaw } from 'vue-router' +import { useNProgress } from '@vueuse/integrations/useNProgress' +import '@/assets/styles/nprogress.scss' +// 路由相关数据 +import { asyncRoutes, asyncRoutesByFilesystem, constantRoutes, constantRoutesByFilesystem } from './routes' +import pinia from '@/store' +import useSettingsStore from '@/store/modules/settings' +import useKeepAliveStore from '@/store/modules/keepAlive' +import useUserStore from '@/store/modules/user' +import useMenuStore from '@/store/modules/menu' +import useRouteStore from '@/store/modules/route' + +const { isLoading } = useNProgress() + +const router = createRouter({ + history: createWebHashHistory(), + routes: useSettingsStore(pinia).app.routeBaseOn === 'filesystem' ? constantRoutesByFilesystem : constantRoutes as RouteRecordRaw[], +}) + +router.beforeEach(async (to, from, next) => { + const settingsStore = useSettingsStore() + const userStore = useUserStore() + const menuStore = useMenuStore() + const routeStore = useRouteStore() + settingsStore.app.enableProgress && (isLoading.value = true) + // 是否已登录 + if (userStore.isLogin) { + // 是否已根据权限动态生成并挂载路由 + if (routeStore.isGenerate) { + // 导航栏如果不是 single 模式,则需要根据 path 定位主导航的选中状态 + settingsStore.menu.menuMode !== 'single' && menuStore.setActived(to.path) + if (to.name) { + if (to.matched.length !== 0) { + // 如果已登录状态下,进入登录页会强制跳转到控制台页面 + if (to.name === 'login') { + next({ + name: 'dashboard', + replace: true, + }) + } + else if (!settingsStore.dashboard.enable && to.name === 'dashboard') { + // 如果未开启控制台页面,则默认进入侧边栏导航第一个模块 + if (menuStore.sidebarMenus.length > 0) { + next({ + path: menuStore.sidebarMenusFirstDeepestPath, + replace: true, + }) + } + else { + next() + } + } + else { + next() + } + } + else { + // 如果是通过 name 跳转,并且 name 对应的路由没有权限时,需要做这步处理,手动指向到 404 页面 + next({ + path: '/404', + }) + } + } + else { + next() + } + } + else { + switch (settingsStore.app.routeBaseOn) { + case 'frontend': + await routeStore.generateRoutesAtFront(asyncRoutes) + break + case 'backend': + await routeStore.generateRoutesAtBack() + break + case 'filesystem': + await routeStore.generateRoutesAtFilesystem(asyncRoutesByFilesystem) + switch (settingsStore.menu.baseOn) { + case 'frontend': + await menuStore.generateMenusAtFront() + break + case 'backend': + await menuStore.generateMenusAtBack() + break + } + break + } + const removeRoutes: Function[] = [] + routeStore.flatRoutes.forEach((route) => { + if (!/^(https?:|mailto:|tel:)/.test(route.path)) + removeRoutes.push(router.addRoute(route as RouteRecordRaw)) + }) + if (settingsStore.app.routeBaseOn !== 'filesystem') { + routeStore.flatSystemRoutes.forEach((route) => { + removeRoutes.push(router.addRoute(route as RouteRecordRaw)) + }) + } + // 记录路由数据,在登出时会使用到,不使用 router.removeRoute 是考虑配置的路由可能不一定有设置 name ,则通过调用 router.addRoute() 返回的回调进行删除 + routeStore.setCurrentRemoveRoutes(removeRoutes) + next({ + path: to.path, + query: to.query, + replace: true, + }) + } + } + else { + if (to.name !== 'login') { + next({ + name: 'login', + query: { + redirect: to.fullPath, + }, + }) + } + else { + next() + } + } +}) + +router.afterEach((to, from) => { + const settingsStore = useSettingsStore() + const keepAliveStore = useKeepAliveStore() + settingsStore.app.enableProgress && (isLoading.value = false) + // 设置页面 title + to.meta.title && settingsStore.setTitle(typeof to.meta.title === 'function' ? to.meta.title() : to.meta.title) + // 判断当前页面是否开启缓存,如果开启,则将当前页面的 name 信息存入 keep-alive 全局状态 + if (to.meta.cache) { + const componentName = to.matched.at(-1)?.components?.default.name + if (componentName) + keepAliveStore.add(componentName) + + else + console.warn('该页面组件未设置组件名,会导致缓存失效,请检查') + } + // 判断离开页面是否开启缓存,如果开启,则根据缓存规则判断是否需要清空 keep-alive 全局状态里离开页面的 name 信息 + if (from.meta.cache) { + const componentName = from.matched.at(-1)?.components?.default.name + if (componentName) { + // 通过 meta.cache 判断针对哪些页面进行缓存 + switch (typeof from.meta.cache) { + case 'string': + if (from.meta.cache !== to.name) + keepAliveStore.remove(componentName) + + break + case 'object': + if (!from.meta.cache.includes(to.name as string)) + keepAliveStore.remove(componentName) + + break + } + // 如果进入的是 reload 页面,则也将离开页面的缓存清空 + if (to.name === 'reload') + keepAliveStore.remove(componentName) + } + } + document.body.scrollTop = 0 + document.documentElement.scrollTop = 0 +}) + +export default router diff --git a/src/router/modules/breadcrumb.example.js b/src/router/modules/breadcrumb.example.js deleted file mode 100644 index a81d9cd1..00000000 --- a/src/router/modules/breadcrumb.example.js +++ /dev/null @@ -1,61 +0,0 @@ -const Layout = () => import('@/layout/index.vue') - -export default { - path: '/breadcrumb_example', - component: Layout, - redirect: '/breadcrumb_example/list1', - name: 'breadcrumbExample', - meta: { - title: '面包屑导航', - icon: 'sidebar-breadcrumb' - }, - children: [ - { - path: 'list1', - name: 'breadcrumbExampleList1', - component: () => import('@/views/breadcrumb_example/list1.vue'), - meta: { - title: '列表1(平级模式)' - } - }, - { - path: 'detail1', - name: 'breadcrumbExampleDetail1', - component: () => import('@/views/breadcrumb_example/detail1.vue'), - meta: { - title: '详情1', - sidebar: false, - activeMenu: '/breadcrumb_example/list1' - } - }, - { - path: 'list2', - name: 'breadcrumbExampleList2', - redirect: '/breadcrumb_example/list2', - meta: { - title: '列表2(层级模式)' - }, - children: [ - { - path: '', - component: () => import('@/views/breadcrumb_example/list2.vue'), - meta: { - title: '列表2(层级模式)', - sidebar: false, - breadcrumb: false - } - }, - { - path: 'detail2', - name: 'breadcrumbExampleDetail2', - component: () => import('@/views/breadcrumb_example/detail2.vue'), - meta: { - title: '详情2', - sidebar: false, - activeMenu: '/breadcrumb_example/list2' - } - } - ] - } - ] -} diff --git a/src/router/modules/breadcrumb.example.ts b/src/router/modules/breadcrumb.example.ts new file mode 100644 index 00000000..72f492e9 --- /dev/null +++ b/src/router/modules/breadcrumb.example.ts @@ -0,0 +1,65 @@ +import type { Route } from '@/global' + +const Layout = () => import('@/layouts/index.vue') + +const routes: Route.recordRaw = { + path: '/breadcrumb_example', + component: Layout, + redirect: '/breadcrumb_example/list1', + name: 'breadcrumbExample', + meta: { + title: '面包屑导航', + icon: 'sidebar-breadcrumb', + }, + children: [ + { + path: 'list1', + name: 'breadcrumbExampleList1', + component: () => import('@/views/breadcrumb_example/list1.vue'), + meta: { + title: '列表1(平级模式)', + }, + }, + { + path: 'detail1', + name: 'breadcrumbExampleDetail1', + component: () => import('@/views/breadcrumb_example/detail1.vue'), + meta: { + title: '详情1', + sidebar: false, + activeMenu: '/breadcrumb_example/list1', + }, + }, + { + path: 'list2', + name: 'breadcrumbExampleList2', + redirect: '/breadcrumb_example/list2', + meta: { + title: '列表2(层级模式)', + }, + children: [ + { + path: '', + component: () => import('@/views/breadcrumb_example/list2.vue'), + meta: { + title: '列表2(层级模式)', + sidebar: false, + breadcrumb: false, + }, + }, + { + path: 'detail2', + name: 'breadcrumbExampleDetail2', + component: () => import('@/views/breadcrumb_example/detail2.vue'), + meta: { + title: '详情2', + sidebar: false, + activeMenu: '/breadcrumb_example/list2', + }, + }, + ], + }, + ], +} + +export default routes diff --git a/src/router/modules/component.basic.example.js b/src/router/modules/component.basic.example.js deleted file mode 100644 index f9426b68..00000000 --- a/src/router/modules/component.basic.example.js +++ /dev/null @@ -1,126 +0,0 @@ -const Layout = () => import('@/layout/index.vue') - -export default { - path: '/component_basic_example', - component: Layout, - redirect: '/component_basic_example/icon', - name: 'componentBasicExample', - meta: { - title: '基础组件(部分)', - icon: 'sidebar-element' - }, - children: [ - { - path: 'icon', - name: 'componentBasicExampleIcon', - component: () => import('@/views/component_basic_example/icon.vue'), - meta: { - title: '图标' - } - }, - { - path: 'button', - name: 'componentBasicExampleButton', - component: () => import('@/views/component_basic_example/button.vue'), - meta: { - title: '按钮' - } - }, - { - path: 'link', - name: 'componentBasicExampleLink', - component: () => import('@/views/component_basic_example/link.vue'), - meta: { - title: '文字链接' - } - }, - { - path: 'radio', - name: 'componentBasicExampleRadio', - component: () => import('@/views/component_basic_example/radio.vue'), - meta: { - title: '单选框' - } - }, - { - path: 'checkbox', - name: 'componentBasicExampleCheckbox', - component: () => import('@/views/component_basic_example/checkbox.vue'), - meta: { - title: '多选框' - } - }, - { - path: 'input', - name: 'componentBasicExampleInput', - component: () => import('@/views/component_basic_example/input.vue'), - meta: { - title: '输入框' - } - }, - { - path: 'inputnumber', - name: 'componentBasicExampleInputnumber', - component: () => import('@/views/component_basic_example/inputnumber.vue'), - meta: { - title: '计数器' - } - }, - { - path: 'select', - name: 'componentBasicExampleSelect', - component: () => import('@/views/component_basic_example/select.vue'), - meta: { - title: '选择器' - } - }, - { - path: 'switch', - name: 'componentBasicExampleSwitch', - component: () => import('@/views/component_basic_example/switch.vue'), - meta: { - title: '开关' - } - }, - { - path: 'slider', - name: 'componentBasicExampleSlider', - component: () => import('@/views/component_basic_example/slider.vue'), - meta: { - title: '滑块' - } - }, - { - path: 'timepicker', - name: 'componentBasicExampleTimepicker', - component: () => import('@/views/component_basic_example/timepicker.vue'), - meta: { - title: '时间选择器' - } - }, - { - path: 'datepicker', - name: 'componentBasicExampleDatepicker', - component: () => import('@/views/component_basic_example/datepicker.vue'), - meta: { - title: '日期选择器' - } - }, - { - path: 'datetimepicker', - name: 'componentBasicExampleDatetimepicker', - component: () => import('@/views/component_basic_example/datetimepicker.vue'), - meta: { - title: '日期时间选择器' - } - }, - { - path: 'rate', - name: 'componentBasicExampleRate', - component: () => import('@/views/component_basic_example/rate.vue'), - meta: { - title: '评分' - } - } - ] -} diff --git a/src/router/modules/component.basic.example.ts b/src/router/modules/component.basic.example.ts new file mode 100644 index 00000000..d854cf11 --- /dev/null +++ b/src/router/modules/component.basic.example.ts @@ -0,0 +1,122 @@ +import type { Route } from '@/global' + +const Layout = () => import('@/layouts/index.vue') + +const routes: Route.recordRaw = { + path: '/component_basic_example', + component: Layout, + redirect: '/component_basic_example/icon', + name: 'componentBasicExample', + meta: { + title: '基础组件(部分)', + icon: 'ep:element-plus', + }, + children: [ + { + path: 'button', + name: 'componentBasicExampleButton', + component: () => import('@/views/component_basic_example/button.vue'), + meta: { + title: '按钮', + }, + }, + { + path: 'link', + name: 'componentBasicExampleLink', + component: () => import('@/views/component_basic_example/link.vue'), + meta: { + title: '文字链接', + }, + }, + { + path: 'radio', + name: 'componentBasicExampleRadio', + component: () => import('@/views/component_basic_example/radio.vue'), + meta: { + title: '单选框', + }, + }, + { + path: 'checkbox', + name: 'componentBasicExampleCheckbox', + component: () => import('@/views/component_basic_example/checkbox.vue'), + meta: { + title: '多选框', + }, + }, + { + path: 'input', + name: 'componentBasicExampleInput', + component: () => import('@/views/component_basic_example/input.vue'), + meta: { + title: '输入框', + }, + }, + { + path: 'inputnumber', + name: 'componentBasicExampleInputnumber', + component: () => import('@/views/component_basic_example/inputnumber.vue'), + meta: { + title: '计数器', + }, + }, + { + path: 'select', + name: 'componentBasicExampleSelect', + component: () => import('@/views/component_basic_example/select.vue'), + meta: { + title: '选择器', + }, + }, + { + path: 'switch', + name: 'componentBasicExampleSwitch', + component: () => import('@/views/component_basic_example/switch.vue'), + meta: { + title: '开关', + }, + }, + { + path: 'slider', + name: 'componentBasicExampleSlider', + component: () => import('@/views/component_basic_example/slider.vue'), + meta: { + title: '滑块', + }, + }, + { + path: 'timepicker', + name: 'componentBasicExampleTimepicker', + component: () => import('@/views/component_basic_example/timepicker.vue'), + meta: { + title: '时间选择器', + }, + }, + { + path: 'datepicker', + name: 'componentBasicExampleDatepicker', + component: () => import('@/views/component_basic_example/datepicker.vue'), + meta: { + title: '日期选择器', + }, + }, + { + path: 'datetimepicker', + name: 'componentBasicExampleDatetimepicker', + component: () => import('@/views/component_basic_example/datetimepicker.vue'), + meta: { + title: '日期时间选择器', + }, + }, + { + path: 'rate', + name: 'componentBasicExampleRate', + component: () => import('@/views/component_basic_example/rate.vue'), + meta: { + title: '评分', + }, + }, + ], +} + +export default routes diff --git a/src/router/modules/component.extend.example.js b/src/router/modules/component.extend.example.js deleted file mode 100644 index d87dfcc1..00000000 --- a/src/router/modules/component.extend.example.js +++ /dev/null @@ -1,110 +0,0 @@ -const Layout = () => import('@/layout/index.vue') - -export default { - path: '/component_extend_example', - component: Layout, - redirect: '/component_extend_example/pageheader', - name: 'componentExtendExample', - meta: { - title: '扩展组件', - icon: 'sidebar-component' - }, - children: [ - { - path: 'svg', - name: 'componentExtendExampleSvg', - component: () => import('@/views/component_extend_example/svg.demo.vue'), - meta: { - title: 'SVG Icon' - } - }, - { - path: 'pageheader', - name: 'componentExtendExamplePageheader', - component: () => import('@/views/component_extend_example/pageheader.demo.vue'), - meta: { - title: '页头' - } - }, - { - path: 'pagemain', - name: 'componentExtendExamplePagemain', - component: () => import('@/views/component_extend_example/pagemain.demo.vue'), - meta: { - title: '内容块' - } - }, - { - path: 'actionbar', - name: 'componentExtendExampleActionbar', - component: () => import('@/views/component_extend_example/actionbar.demo.vue'), - meta: { - title: '固定底部操作栏' - } - }, - { - path: 'searchbar', - name: 'componentExtendExampleSearchbar', - component: () => import('@/views/component_extend_example/searchbar.demo.vue'), - meta: { - title: '搜索面板' - } - }, - { - path: 'batchactionbar', - name: 'componentExtendExampleBatchactionbar', - component: () => import('@/views/component_extend_example/batchactionbar.demo.vue'), - meta: { - title: '批量操作栏' - } - }, - { - path: 'tableautoheight', - name: 'componentExtendExampleTableautoheight', - component: () => import('@/views/component_extend_example/table.autoheight.demo.vue'), - meta: { - title: '表格高度自适应' - } - }, - { - path: 'upload', - name: 'componentExtendExampleUpload', - component: () => import('@/views/component_extend_example/upload.demo.vue'), - meta: { - title: '上传' - } - }, - { - path: 'imagepreview', - name: 'componentExtendExampleImagePreview', - component: () => import('@/views/component_extend_example/image.preview.demo.vue'), - meta: { - title: '图片预览' - } - }, - { - path: 'trend', - name: 'componentExtendExampleTrend', - component: () => import('@/views/component_extend_example/trend.demo.vue'), - meta: { - title: '趋势标记' - } - }, - { - path: 'pcas', - name: 'componentExtendExamplePcas', - component: () => import('@/views/component_extend_example/pcas.demo.vue'), - meta: { - title: '省市区街道联动' - } - }, - { - path: 'editor', - name: 'componentExtendExampleEditor', - component: () => import('@/views/component_extend_example/editor.demo.vue'), - meta: { - title: '编辑器' - } - } - ] -} diff --git a/src/router/modules/component.extend.example.ts b/src/router/modules/component.extend.example.ts new file mode 100644 index 00000000..339a2f34 --- /dev/null +++ b/src/router/modules/component.extend.example.ts @@ -0,0 +1,114 @@ +import type { Route } from '@/global' + +const Layout = () => import('@/layouts/index.vue') + +const routes: Route.recordRaw = { + path: '/component_extend_example', + component: Layout, + redirect: '/component_extend_example/pageheader', + name: 'componentExtendExample', + meta: { + title: '扩展组件', + icon: 'sidebar-component', + }, + children: [ + { + path: 'svg', + name: 'componentExtendExampleSvg', + component: () => import('@/views/component_extend_example/svg.demo.vue'), + meta: { + title: 'SVG Icon', + }, + }, + { + path: 'pageheader', + name: 'componentExtendExamplePageheader', + component: () => import('@/views/component_extend_example/pageheader.demo.vue'), + meta: { + title: '页头', + }, + }, + { + path: 'pagemain', + name: 'componentExtendExamplePagemain', + component: () => import('@/views/component_extend_example/pagemain.demo.vue'), + meta: { + title: '内容块', + }, + }, + { + path: 'actionbar', + name: 'componentExtendExampleActionbar', + component: () => import('@/views/component_extend_example/actionbar.demo.vue'), + meta: { + title: '固定底部操作栏', + }, + }, + { + path: 'searchbar', + name: 'componentExtendExampleSearchbar', + component: () => import('@/views/component_extend_example/searchbar.demo.vue'), + meta: { + title: '搜索面板', + }, + }, + { + path: 'batchactionbar', + name: 'componentExtendExampleBatchactionbar', + component: () => import('@/views/component_extend_example/batchactionbar.demo.vue'), + meta: { + title: '批量操作栏', + }, + }, + { + path: 'tableautoheight', + name: 'componentExtendExampleTableautoheight', + component: () => import('@/views/component_extend_example/table.autoheight.demo.vue'), + meta: { + title: '表格高度自适应', + }, + }, + { + path: 'upload', + name: 'componentExtendExampleUpload', + component: () => import('@/views/component_extend_example/upload.demo.vue'), + meta: { + title: '上传', + }, + }, + { + path: 'imagepreview', + name: 'componentExtendExampleImagePreview', + component: () => import('@/views/component_extend_example/image.preview.demo.vue'), + meta: { + title: '图片预览', + }, + }, + { + path: 'trend', + name: 'componentExtendExampleTrend', + component: () => import('@/views/component_extend_example/trend.demo.vue'), + meta: { + title: '趋势标记', + }, + }, + { + path: 'pcas', + name: 'componentExtendExamplePcas', + component: () => import('@/views/component_extend_example/pcas.demo.vue'), + meta: { + title: '省市区街道联动', + }, + }, + { + path: 'editor', + name: 'componentExtendExampleEditor', + component: () => import('@/views/component_extend_example/editor.demo.vue'), + meta: { + title: '编辑器', + }, + }, + ], +} + +export default routes diff --git a/src/router/modules/ecology.example.js b/src/router/modules/ecology.example.js deleted file mode 100644 index 65f7f2e0..00000000 --- a/src/router/modules/ecology.example.js +++ /dev/null @@ -1,66 +0,0 @@ -const Layout = () => import('@/layout/index.vue') - -export default [ - { - path: '/official', - meta: { - title: '官方周边', - icon: 'ep:grid' - }, - children: [ - { - path: 'https://hooray.gitee.io/fantastic-startkit/', - meta: { - title: 'Fantastic-startkit 项目启动套件' - } - }, - { - path: 'https://hooray.gitee.io/one-step-admin/', - meta: { - title: 'One-step-admin 干啥都快人一步的中后台框架' - } - } - ] - }, - { - path: '/cooperation', - meta: { - title: '战略合作', - icon: 'sidebar-cooperation' - }, - children: [ - { - path: 'https://www.vform666.com', - meta: { - title: 'VForm 低代码表单' - } - }, - { - path: 'https://gitee.com/sciqtw/quickadmin', - meta: { - title: 'QuickAdmin 快速开发管理框架' - } - } - ] - }, - { - path: '/videos/coming/soon', - component: Layout, - meta: { - title: '视频教程', - icon: 'sidebar-videos' - }, - children: [ - { - path: '', - name: 'videosComingSoon', - component: () => import('@/views/videos_example/coming.soon.vue'), - meta: { - title: '视频教程', - sidebar: false, - breadcrumb: false - } - } - ] - } -] diff --git a/src/router/modules/ecology.example.ts b/src/router/modules/ecology.example.ts new file mode 100644 index 00000000..f24c33c5 --- /dev/null +++ b/src/router/modules/ecology.example.ts @@ -0,0 +1,70 @@ +import type { Route } from '@/global' + +const Layout = () => import('@/layouts/index.vue') + +const routes: Route.recordRaw[] = [ + { + path: '/official', + meta: { + title: '官方周边', + icon: 'ep:grid', + }, + children: [ + { + path: 'https://hooray.gitee.io/fantastic-startkit/', + meta: { + title: 'Fantastic-startkit 项目启动套件', + }, + }, + { + path: 'https://hooray.gitee.io/one-step-admin/', + meta: { + title: 'One-step-admin 干啥都快人一步的中后台框架', + }, + }, + ], + }, + { + path: '/cooperation', + meta: { + title: '战略合作', + icon: 'sidebar-cooperation', + }, + children: [ + { + path: 'https://www.vform666.com', + meta: { + title: 'VForm 低代码表单', + }, + }, + { + path: 'https://gitee.com/sciqtw/quickadmin', + meta: { + title: 'QuickAdmin 快速开发管理框架', + }, + }, + ], + }, + { + path: '/video/coming/soon', + component: Layout, + meta: { + title: '视频教程', + icon: 'sidebar-videos', + }, + children: [ + { + path: '', + name: 'videosComingSoon', + component: () => import('@/views/videos_example/coming.soon.vue'), + meta: { + title: '视频教程', + sidebar: false, + breadcrumb: false, + }, + }, + ], + }, +] + +export default routes diff --git a/src/router/modules/external.link.example.js b/src/router/modules/external.link.example.js deleted file mode 100644 index 1ae9ce61..00000000 --- a/src/router/modules/external.link.example.js +++ /dev/null @@ -1,26 +0,0 @@ -const Layout = () => import('@/layout/index.vue') - -export default { - path: '/link', - component: Layout, - redirect: '/link/iframe', - name: 'externalLinkExample', - meta: { - title: '外链', - icon: 'sidebar-external-link' - }, - children: [ - { - path: 'https://gitee.com/hooray/fantastic-admin', - meta: { - title: 'Gitee 仓库' - } - }, - { - path: 'https://github.com/hooray/fantastic-admin', - meta: { - title: 'Github 仓库' - } - } - ] -} diff --git a/src/router/modules/external.link.example.ts b/src/router/modules/external.link.example.ts new file mode 100644 index 00000000..72e7f94c --- /dev/null +++ b/src/router/modules/external.link.example.ts @@ -0,0 +1,29 @@ +import type { Route } from '@/global' + +const Layout = () => import('@/layouts/index.vue') + +const routes: Route.recordRaw = { + path: '/link', + component: Layout, + name: 'externalLinkExample', + meta: { + title: '外链', + icon: 'sidebar-external-link', + }, + children: [ + { + path: 'https://gitee.com/hooray/fantastic-admin', + meta: { + title: 'Gitee 仓库', + }, + }, + { + path: 'https://github.com/hooray/fantastic-admin', + meta: { + title: 'Github 仓库', + }, + }, + ], +} + +export default routes diff --git a/src/router/modules/jsx.example.js b/src/router/modules/jsx.example.js deleted file mode 100644 index a2edd5e0..00000000 --- a/src/router/modules/jsx.example.js +++ /dev/null @@ -1,25 +0,0 @@ -const Layout = () => import('@/layout/index.vue') - -export default { - path: '/jsx_example', - component: Layout, - redirect: '/jsx_example/index', - name: 'jsxExample', - meta: { - title: 'JSX', - icon: 'sidebar-jsx' - }, - children: [ - { - path: 'index', - name: 'jsxExampleIndex', - component: () => import('@/views/jsx_example/index.jsx'), - meta: { - title: 'JSX', - sidebar: false, - breadcrumb: false, - activeMenu: '/jsx_example' - } - } - ] -} diff --git a/src/router/modules/jsx.example.ts b/src/router/modules/jsx.example.ts new file mode 100644 index 00000000..a545018f --- /dev/null +++ b/src/router/modules/jsx.example.ts @@ -0,0 +1,29 @@ +import type { Route } from '@/global' + +const Layout = () => import('@/layouts/index.vue') + +const routes: Route.recordRaw = { + path: '/jsx_example', + component: Layout, + redirect: '/jsx_example/index', + name: 'jsxExample', + meta: { + title: 'JSX', + icon: 'sidebar-jsx', + }, + children: [ + { + path: 'index', + name: 'jsxExampleIndex', + component: () => import('@/views/jsx_example/index.jsx'), + meta: { + title: 'JSX', + sidebar: false, + breadcrumb: false, + activeMenu: '/jsx_example', + }, + }, + ], +} + +export default routes diff --git a/src/router/modules/keep.alive.example.js b/src/router/modules/keep.alive.example.js deleted file mode 100644 index ffa5d699..00000000 --- a/src/router/modules/keep.alive.example.js +++ /dev/null @@ -1,81 +0,0 @@ -const Layout = () => import('@/layout/index.vue') - -export default { - path: '/keep_alive_example', - component: Layout, - redirect: '/keep_alive_example/page', - name: 'keepAliveExample', - meta: { - title: '页面缓存', - icon: 'sidebar-keep-alive' - }, - children: [ - { - path: 'page', - name: 'keepAliveExamplePage', - component: () => import('@/views/keep_alive_example/page.vue'), - meta: { - title: '页面缓存', - breadcrumb: false - } - }, - { - path: 'detail', - name: 'keepAliveExampleDetail', - component: () => import('@/views/keep_alive_example/detail.vue'), - meta: { - title: '平级路由', - sidebar: false, - activeMenu: '/keep_alive_example/page' - } - }, - { - path: 'nested', - redirect: '/keep_alive_example/detail2', - meta: { - title: '下级路由', - sidebar: false - }, - children: [ - { - path: 'detail', - name: 'keepAliveExampleNestedDetail', - component: () => import('@/views/keep_alive_example/detail.vue'), - meta: { - title: '测试页面', - activeMenu: '/keep_alive_example/page' - } - } - ] - }, - { - path: 'nested', - name: 'keepAliveExampleNested1', - component: () => import('@/views/keep_alive_example/nested/nested.vue'), - meta: { - title: '路由多级缓存(1)' - }, - children: [ - { - path: 'nested', - name: 'keepAliveExampleNested2', - component: () => import('@/views/keep_alive_example/nested/nested/nested.vue'), - meta: { - title: '路由多级缓存(1-1)' - }, - children: [ - { - path: 'test', - name: 'keepAliveExampleNestedTest', - component: () => import('@/views/keep_alive_example/nested/nested/nested/index.vue'), - meta: { - title: '测试页面(1-1-1)', - cache: true - } - } - ] - } - ] - } - ] -} diff --git a/src/router/modules/keep.alive.example.ts b/src/router/modules/keep.alive.example.ts new file mode 100644 index 00000000..caca5ef7 --- /dev/null +++ b/src/router/modules/keep.alive.example.ts @@ -0,0 +1,85 @@ +import type { Route } from '@/global' + +const Layout = () => import('@/layouts/index.vue') + +const routes: Route.recordRaw = { + path: '/keep_alive_example', + component: Layout, + redirect: '/keep_alive_example/page', + name: 'keepAliveExample', + meta: { + title: '页面缓存', + icon: 'sidebar-keep-alive', + }, + children: [ + { + path: 'page', + name: 'keepAliveExamplePage', + component: () => import('@/views/keep_alive_example/page.vue'), + meta: { + title: '页面缓存', + breadcrumb: false, + }, + }, + { + path: 'detail', + name: 'keepAliveExampleDetail', + component: () => import('@/views/keep_alive_example/detail.vue'), + meta: { + title: '平级路由', + sidebar: false, + activeMenu: '/keep_alive_example/page', + }, + }, + { + path: 'nested', + redirect: '/keep_alive_example/nested/detail', + meta: { + title: '嵌套路由', + sidebar: false, + }, + children: [ + { + path: 'detail', + name: 'keepAliveExampleNestedDetail', + component: () => import('@/views/keep_alive_example/detail.vue'), + meta: { + title: '嵌套路由', + activeMenu: '/keep_alive_example/page', + }, + }, + ], + }, + { + path: 'nested1', + name: 'keepAliveExampleNested1', + component: () => import('@/views/keep_alive_example/nested/nested.vue'), + meta: { + title: '路由多级缓存1', + }, + children: [ + { + path: 'nested2', + name: 'keepAliveExampleNested2', + component: () => import('@/views/keep_alive_example/nested/nested/nested.vue'), + meta: { + title: '路由多级缓存1-1', + }, + children: [ + { + path: 'index', + name: 'keepAliveExampleNestedIndex', + component: () => import('@/views/keep_alive_example/nested/nested/nested/index.vue'), + meta: { + title: '路由多级缓存1-1-1', + cache: true, + }, + }, + ], + }, + ], + }, + ], +} + +export default routes diff --git a/src/router/modules/mock.example.js b/src/router/modules/mock.example.js deleted file mode 100644 index 2736a867..00000000 --- a/src/router/modules/mock.example.js +++ /dev/null @@ -1,25 +0,0 @@ -const Layout = () => import('@/layout/index.vue') - -export default { - path: '/mock_example', - component: Layout, - redirect: '/mock_example/index', - name: 'mockExample', - meta: { - title: 'Mock', - icon: 'sidebar-mock' - }, - children: [ - { - path: 'index', - name: 'mockExampleIndex', - component: () => import('@/views/mock_example/index.vue'), - meta: { - title: 'Mock', - sidebar: false, - breadcrumb: false, - activeMenu: '/mock_example' - } - } - ] -} diff --git a/src/router/modules/mock.example.ts b/src/router/modules/mock.example.ts new file mode 100644 index 00000000..36c9df36 --- /dev/null +++ b/src/router/modules/mock.example.ts @@ -0,0 +1,29 @@ +import type { Route } from '@/global' + +const Layout = () => import('@/layouts/index.vue') + +const routes: Route.recordRaw = { + path: '/mock_example', + component: Layout, + redirect: '/mock_example/index', + name: 'mockExample', + meta: { + title: 'Mock', + icon: 'sidebar-mock', + }, + children: [ + { + path: 'index', + name: 'mockExampleIndex', + component: () => import('@/views/mock_example/index.vue'), + meta: { + title: 'Mock', + sidebar: false, + breadcrumb: false, + activeMenu: '/mock_example', + }, + }, + ], +} + +export default routes diff --git a/src/router/modules/multilevel.menu.example.js b/src/router/modules/multilevel.menu.example.js deleted file mode 100644 index 6f632ccd..00000000 --- a/src/router/modules/multilevel.menu.example.js +++ /dev/null @@ -1,66 +0,0 @@ -const Layout = () => import('@/layout/index.vue') - -export default { - path: '/multilevel_menu_example', - component: Layout, - redirect: '/multilevel_menu_example/page', - name: 'multilevelMenuExample', - meta: { - title: '多级导航', - icon: 'sidebar-menu' - }, - children: [ - { - path: 'page', - name: 'multilevelMenuExample1', - component: () => import('@/views/multilevel_menu_example/page.vue'), - meta: { - title: '导航1' - } - }, - { - path: 'level2', - name: 'multilevelMenuExample2', - redirect: '/multilevel_menu_example/level2/page', - meta: { - title: '导航2' - }, - children: [ - { - path: 'page', - name: 'multilevelMenuExample2-1', - component: () => import('@/views/multilevel_menu_example/level2/page.vue'), - meta: { - title: '导航2-1' - } - }, - { - path: 'level3', - name: 'multilevelMenuExample2-2', - redirect: '/multilevel_menu_example/level2/level3/page1', - meta: { - title: '导航2-2' - }, - children: [ - { - path: 'page1', - name: 'multilevelMenuExample2-2-1', - component: () => import('@/views/multilevel_menu_example/level2/level3/page1.vue'), - meta: { - title: '导航2-2-1' - } - }, - { - path: 'page2', - name: 'multilevelMenuExample2-2-2', - component: () => import('@/views/multilevel_menu_example/level2/level3/page2.vue'), - meta: { - title: '导航2-2-2' - } - } - ] - } - ] - } - ] -} diff --git a/src/router/modules/multilevel.menu.example.ts b/src/router/modules/multilevel.menu.example.ts new file mode 100644 index 00000000..75f3eb39 --- /dev/null +++ b/src/router/modules/multilevel.menu.example.ts @@ -0,0 +1,70 @@ +import type { Route } from '@/global' + +const Layout = () => import('@/layouts/index.vue') + +const routes: Route.recordRaw = { + path: '/multilevel_menu_example', + component: Layout, + redirect: '/multilevel_menu_example/page', + name: 'multilevelMenuExample', + meta: { + title: '多级导航', + icon: 'sidebar-menu', + }, + children: [ + { + path: 'page', + name: 'multilevelMenuExample1', + component: () => import('@/views/multilevel_menu_example/page.vue'), + meta: { + title: '导航1', + }, + }, + { + path: 'level2', + name: 'multilevelMenuExample2', + redirect: '/multilevel_menu_example/level2/page', + meta: { + title: '导航2', + }, + children: [ + { + path: 'page', + name: 'multilevelMenuExample2-1', + component: () => import('@/views/multilevel_menu_example/level2/page.vue'), + meta: { + title: '导航2-1', + }, + }, + { + path: 'level3', + name: 'multilevelMenuExample2-2', + redirect: '/multilevel_menu_example/level2/level3/page1', + meta: { + title: '导航2-2', + }, + children: [ + { + path: 'page1', + name: 'multilevelMenuExample2-2-1', + component: () => import('@/views/multilevel_menu_example/level2/level3/page1.vue'), + meta: { + title: '导航2-2-1', + }, + }, + { + path: 'page2', + name: 'multilevelMenuExample2-2-2', + component: () => import('@/views/multilevel_menu_example/level2/level3/page2.vue'), + meta: { + title: '导航2-2-2', + }, + }, + ], + }, + ], + }, + ], +} + +export default routes diff --git a/src/router/modules/permission.example.js b/src/router/modules/permission.example.js deleted file mode 100644 index 7a2b18af..00000000 --- a/src/router/modules/permission.example.js +++ /dev/null @@ -1,26 +0,0 @@ -const Layout = () => import('@/layout/index.vue') - -export default { - path: '/permission_example', - component: Layout, - redirect: '/permission_example/index', - name: 'permissionExample', - meta: { - title: '权限验证', - icon: 'sidebar-permission' - }, - children: [ - { - path: 'index', - name: 'permissionExampleIndex', - component: () => import('@/views/permission_example/index.vue'), - meta: { - title: '权限验证', - auth: ['permission.browse'], - sidebar: false, - breadcrumb: false, - activeMenu: '/permission_example' - } - } - ] -} diff --git a/src/router/modules/permission.example.ts b/src/router/modules/permission.example.ts new file mode 100644 index 00000000..26431ede --- /dev/null +++ b/src/router/modules/permission.example.ts @@ -0,0 +1,30 @@ +import type { Route } from '@/global' + +const Layout = () => import('@/layouts/index.vue') + +const routes: Route.recordRaw = { + path: '/permission_example', + component: Layout, + redirect: '/permission_example/index', + name: 'permissionExample', + meta: { + title: '权限验证', + icon: 'ep:key', + }, + children: [ + { + path: 'index', + name: 'permissionExampleIndex', + component: () => import('@/views/permission_example/index.vue'), + meta: { + title: '权限验证', + auth: ['permission.browse'], + sidebar: false, + breadcrumb: false, + activeMenu: '/permission_example', + }, + }, + ], +} + +export default routes diff --git a/src/router/routes.js b/src/router/routes.js deleted file mode 100644 index 1e0f9d8a..00000000 --- a/src/router/routes.js +++ /dev/null @@ -1,161 +0,0 @@ -import pinia from '@/store' -import useSettingsStore from '@/store/modules/settings' - -// 固定路由(默认路由) -let constantRoutes = [ - { - path: '/', - redirect: '/dashboard' - }, - { - path: '/login', - name: 'login', - component: () => import('@/views/login.vue'), - meta: { - title: '登录' - } - }, - { - path: '/:all(.*)*', - name: 'notFound', - component: () => import('@/views/[...all].vue'), - meta: { - title: '找不到页面' - } - } -] - -// 系统路由 -let systemRoutes = [ - { - path: '/dashboard', - component: () => import('@/layout/index.vue'), - meta: { - title: () => { - return useSettingsStore().dashboard.title - }, - breadcrumb: false - }, - children: [ - { - path: '', - name: 'dashboard', - component: () => import('@/views/index.vue'), - meta: { - title: () => { - return useSettingsStore().dashboard.title - }, - breadcrumb: false - } - } - ] - }, - { - path: '/personal', - component: () => import('@/layout/index.vue'), - redirect: '/personal/setting', - meta: { - title: '个人中心', - breadcrumb: false - }, - children: [ - { - path: 'setting', - name: 'personalSetting', - component: () => import('@/views/personal/setting.vue'), - meta: { - title: '个人设置', - i18n: 'route.personal.setting', - cache: 'personalEditPassword' - } - }, - { - path: 'edit/password', - name: 'personalEditPassword', - component: () => import('@/views/personal/edit.password.vue'), - meta: { - title: '修改密码', - i18n: 'route.personal.editpassword' - } - } - ] - }, - { - path: '/reload', - component: () => import('@/layout/index.vue'), - meta: { - title: '重新加载', - breadcrumb: false - }, - children: [ - { - path: '', - name: 'reload', - component: () => import('@/views/reload.vue'), - meta: { - title: '重新加载', - breadcrumb: false - } - } - ] - } -] - -import MultilevelMenuExample from './modules/multilevel.menu.example' -import BreadcrumbExample from './modules/breadcrumb.example' -import KeepAliveExample from './modules/keep.alive.example' -import ComponentBasicExample from './modules/component.basic.example' -import ComponentExtendExample from './modules/component.extend.example' -import PermissionExample from './modules/permission.example' -import MockExample from './modules/mock.example' -import JsxExample from './modules/jsx.example' -import ExternalLinkExample from './modules/external.link.example' -import EcologyExample from './modules/ecology.example' - -// 动态路由(异步路由、导航栏路由) -let asyncRoutes = [ - { - meta: { - title: '演示', - icon: 'sidebar-default' - }, - children: [ - MultilevelMenuExample, - BreadcrumbExample, - KeepAliveExample, - ComponentBasicExample, - ComponentExtendExample, - PermissionExample, - MockExample, - JsxExample, - ExternalLinkExample - ] - }, - { - meta: { - title: '生态', - icon: 'sidebar-ecology' - }, - children: [ - ...EcologyExample - ] - } -] - -import { setupLayouts } from 'virtual:generated-layouts' -import generatedRoutes from 'virtual:generated-pages' - -if (useSettingsStore(pinia).app.routeBaseOn === 'filesystem') { - constantRoutes = generatedRoutes.filter(item => { - return item.meta?.enabled !== false && item.meta?.constant === true - }) - asyncRoutes = setupLayouts(generatedRoutes.filter(item => { - return item.meta?.enabled !== false && item.meta?.constant !== true && item.meta?.layout !== false - })) -} - -export { - constantRoutes, - systemRoutes, - asyncRoutes -} diff --git a/src/router/routes.ts b/src/router/routes.ts new file mode 100644 index 00000000..1fa3bc58 --- /dev/null +++ b/src/router/routes.ts @@ -0,0 +1,157 @@ +import { setupLayouts } from 'virtual:generated-layouts' +import generatedRoutes from 'virtual:generated-pages' +import MultilevelMenuExample from './modules/multilevel.menu.example' +import BreadcrumbExample from './modules/breadcrumb.example' +import KeepAliveExample from './modules/keep.alive.example' +import ComponentBasicExample from './modules/component.basic.example' +import ComponentExtendExample from './modules/component.extend.example' +import PermissionExample from './modules/permission.example' +import MockExample from './modules/mock.example' +import JsxExample from './modules/jsx.example' +import ExternalLinkExample from './modules/external.link.example' +import EcologyExample from './modules/ecology.example' +import type { Route } from '@/global' +import useSettingsStore from '@/store/modules/settings' + +// 固定路由(默认路由) +const constantRoutes: Route.recordRaw[] = [ + { + path: '/', + redirect: '/dashboard', + meta: { + title: '', + }, + }, + { + path: '/login', + name: 'login', + component: () => import('@/views/login.vue'), + meta: { + title: '登录', + }, + }, + { + path: '/:all(.*)*', + name: 'notFound', + component: () => import('@/views/[...all].vue'), + meta: { + title: '找不到页面', + }, + }, +] + +// 系统路由 +const systemRoutes: Route.recordRaw[] = [ + { + path: '/dashboard', + component: () => import('@/layouts/index.vue'), + meta: { + title: () => useSettingsStore().dashboard.title, + breadcrumb: false, + }, + children: [ + { + path: '', + name: 'dashboard', + component: () => import('@/views/index.vue'), + meta: { + title: () => useSettingsStore().dashboard.title, + breadcrumb: false, + }, + }, + ], + }, + { + path: '/personal', + component: () => import('@/layouts/index.vue'), + redirect: '/personal/setting', + meta: { + title: '个人中心', + breadcrumb: false, + }, + children: [ + { + path: 'setting', + name: 'personalSetting', + component: () => import('@/views/personal/setting.vue'), + meta: { + title: '个人设置', + cache: 'personalEditPassword', + }, + }, + { + path: 'edit/password', + name: 'personalEditPassword', + component: () => import('@/views/personal/edit.password.vue'), + meta: { + title: '修改密码', + }, + }, + ], + }, + { + path: '/reload', + component: () => import('@/layouts/index.vue'), + meta: { + title: '重新加载', + breadcrumb: false, + }, + children: [ + { + path: '', + name: 'reload', + component: () => import('@/views/reload.vue'), + meta: { + title: '重新加载', + breadcrumb: false, + }, + }, + ], + }, +] + +// 动态路由(异步路由、导航栏路由) +const asyncRoutes: Route.recordMainRaw[] = [ + { + meta: { + title: '演示', + icon: 'sidebar-default', + }, + children: [ + MultilevelMenuExample, + BreadcrumbExample, + KeepAliveExample, + ComponentBasicExample, + ComponentExtendExample, + PermissionExample, + MockExample, + JsxExample, + ExternalLinkExample, + ], + }, + { + meta: { + title: '生态', + icon: 'sidebar-ecology', + }, + children: [ + ...EcologyExample, + ], + }, +] + +const constantRoutesByFilesystem = generatedRoutes.filter((item) => { + return item.meta?.enabled !== false && item.meta?.constant === true +}) + +const asyncRoutesByFilesystem = setupLayouts(generatedRoutes.filter((item) => { + return item.meta?.enabled !== false && item.meta?.constant !== true && item.meta?.layout !== false +})) as Route.recordRaw[] + +export { + constantRoutes, + systemRoutes, + asyncRoutes, + constantRoutesByFilesystem, + asyncRoutesByFilesystem, +} diff --git a/src/settings.custom.json b/src/settings.custom.json deleted file mode 100644 index 0967ef42..00000000 --- a/src/settings.custom.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/src/settings.default.ts b/src/settings.default.ts new file mode 100644 index 00000000..99c4ad49 --- /dev/null +++ b/src/settings.default.ts @@ -0,0 +1,56 @@ +// 该文件为系统默认配置,请勿修改!!! + +import type { RecursiveRequired, Settings } from './global' + +const globalSettingsDefault: RecursiveRequired = { + app: { + colorScheme: 'light', + elementSize: 'default', + iconifyOfflineUse: false, + enablePermission: false, + enableProgress: true, + enableDynamicTitle: false, + routeBaseOn: 'frontend', + }, + dashboard: { + enable: true, + title: '控制台', + }, + layout: { + enableMobileAdaptation: false, + }, + menu: { + baseOn: 'frontend', + menuMode: 'side', + switchMainMenuAndPageJump: false, + subMenuUniqueOpened: true, + subMenuCollapse: false, + enableSubMenuCollapseButton: false, + enableHotkeys: false, + }, + topbar: { + fixed: true, + }, + toolbar: { + enableFullscreen: false, + enablePageReload: false, + enableColorScheme: false, + enableAppSetting: false, + }, + breadcrumb: { + enable: true, + }, + navSearch: { + enable: true, + enableHotkeys: true, + }, + copyright: { + enable: true, + dates: '2020-2022', + company: 'Fantastic-admin', + website: 'https://hooray.gitee.io/fantastic-admin/', + beian: '', + }, +} + +export default globalSettingsDefault diff --git a/src/settings.js b/src/settings.js deleted file mode 100644 index 9351269e..00000000 --- a/src/settings.js +++ /dev/null @@ -1,102 +0,0 @@ -let globalSettings = { - app: { - // 颜色方案,可选值为 light、dark - colorScheme: 'light', - // Element 组件默认尺寸,支持:large、default、small - elementSize: 'default', - // 是否将 iconify 图标集中的 Element Plus 图标下载到本地,以便在离线环境下使用 - iconifyOfflineUse: false, - // 是否开启权限功能 - enablePermission: false, - // 是否开启载入进度条 - enableProgress: true, - // 是否开启动态标题 - enableDynamicTitle: false, - /** - * 路由数据来源 - * frontend 前端 - * backend 后端 - * filesystem 文件系统 - */ - routeBaseOn: 'frontend' - }, - // 控制台 - dashboard: { - // 是否开启 - enable: true, - // 控制台名称 - title: '控制台' - }, - // 布局 - layout: { - // 是否开启移动端适配,开启后当页面宽度小于 992px 时自动切换为移动端展示 - enableMobileAdaptation: true - }, - // 导航栏 - menu: { - /** - * 数据来源,当 app.routeBaseOn 为 filesystem 时生效 - * frontend 前端 - * backend 后端 - */ - baseOn: 'frontend', - /** - * 导航栏模式 - * side 侧边栏模式(含主导航) - * head 顶部模式 - * single 侧边栏模式(无主导航) - */ - menuMode: 'side', - // 切换主导航同时跳转页面 - switchMainMenuAndPageJump: false, - // 次导航只保持一个子项的展开 - subMenuUniqueOpened: true, - // 次导航是否收起 - subMenuCollapse: false - }, - // 顶栏 - topbar: { - // 是否固定 - fixed: true, - // 是否开启侧边栏展开收起按钮 - enableSidebarCollapse: false, - // 是否开启面包屑导航 - enableBreadcrumb: true, - // 是否开启导航搜索 - enableNavSearch: true, - // 是否开启全屏 - enableFullscreen: false, - // 是否开启页面刷新 - enablePageReload: false, - // 是否开启颜色主题 - enableColorScheme: false, - // 是否开启应用配置(建议在生产环境关闭) - enableAppSetting: false - }, - // 底部版权 - copyright: { - // 是否开启,同时在路由 meta 对象里可以单独设置某个路由是否显示底部版权信息 - enable: true, - // 版权信息配置,格式为:Copyright © [dates] - dates: '2020-2022', - company: 'Fantastic-admin', - website: 'https://hooray.gitee.io/fantastic-admin/', - beian: '' - } -} - -import settingsCustom from './settings.custom.json' -Object.assign(globalSettings, settingsCustom) - -// 演示&开发环境开启全部功能(这部分代码可删除,仅方便作者打包演示环境) -if (import.meta.env.VITE_APP_MODE === 'example' || import.meta.env.MODE === 'development') { - globalSettings.app.enablePermission = true - globalSettings.app.enableDynamicTitle = true - globalSettings.topbar.enableSidebarCollapse = true - globalSettings.topbar.enableFullscreen = true - globalSettings.topbar.enablePageReload = true - globalSettings.topbar.enableColorScheme = true - globalSettings.topbar.enableAppSetting = true -} - -export default globalSettings diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 00000000..6e4d2f3b --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,23 @@ +import type { Settings } from './global' + +const globalSettings: Settings.all = { + app: { + enablePermission: true, + enableDynamicTitle: true, + }, + layout: { + enableMobileAdaptation: true, + }, + menu: { + enableSubMenuCollapseButton: true, + enableHotkeys: true, + }, + toolbar: { + enableFullscreen: true, + enablePageReload: true, + enableColorScheme: true, + enableAppSetting: true, + }, +} + +export default globalSettings diff --git a/src/shims.d.ts b/src/shims.d.ts new file mode 100644 index 00000000..691ab88a --- /dev/null +++ b/src/shims.d.ts @@ -0,0 +1,19 @@ +declare interface Window { + webkitDevicePixelRatio: any + mozDevicePixelRatio: any +} + +declare module '*.vue' { + import { type DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} + +declare const __SYSTEM_INFO__: { + pkg: { + version: string + dependencies: Recordable + devDependencies: Recordable + } + lastBuildTime: string +} diff --git a/src/store/index.js b/src/store/index.ts similarity index 100% rename from src/store/index.js rename to src/store/index.ts diff --git a/src/store/modules/keepAlive.js b/src/store/modules/keepAlive.js deleted file mode 100644 index f61c87c5..00000000 --- a/src/store/modules/keepAlive.js +++ /dev/null @@ -1,36 +0,0 @@ -const useKeepAliveStore = defineStore( - // 唯一ID - 'keepAlive', - { - state: () => ({ - list: [] - }), - actions: { - add(name) { - if (typeof name === 'string') { - !this.list.includes(name) && this.list.push(name) - } else { - name.map(v => { - v && !this.list.includes(v) && this.list.push(v) - }) - } - }, - remove(name) { - if (typeof name === 'string') { - this.list = this.list.filter(v => { - return v !== name - }) - } else { - this.list = this.list.filter(v => { - return !name.includes(v) - }) - } - }, - clean() { - this.list = [] - } - } - } -) - -export default useKeepAliveStore diff --git a/src/store/modules/keepAlive.ts b/src/store/modules/keepAlive.ts new file mode 100644 index 00000000..30aaec1d --- /dev/null +++ b/src/store/modules/keepAlive.ts @@ -0,0 +1,38 @@ +const useKeepAliveStore = defineStore( + // 唯一ID + 'keepAlive', + { + state: () => ({ + list: [] as string[], + }), + actions: { + add(name: string | string[]) { + if (typeof name === 'string') { + !this.list.includes(name) && this.list.push(name) + } + else { + name.forEach((v) => { + v && !this.list.includes(v) && this.list.push(v) + }) + } + }, + remove(name: string | string[]) { + if (typeof name === 'string') { + this.list = this.list.filter((v) => { + return v !== name + }) + } + else { + this.list = this.list.filter((v) => { + return !name.includes(v) + }) + } + }, + clean() { + this.list = [] + }, + }, + }, +) + +export default useKeepAliveStore diff --git a/src/store/modules/menu.js b/src/store/modules/menu.js deleted file mode 100644 index 4508ccd0..00000000 --- a/src/store/modules/menu.js +++ /dev/null @@ -1,180 +0,0 @@ -import { deepClone, resolveRoutePath } from '@/utils' -import path from 'path-browserify' -import api from '@/api' -import menu from '@/menu' - -import useSettingsStore from './settings' -import useUserStore from './user' -import useRouteStore from './route' - -function getDeepestPath(routes, rootPath = '') { - let retnPath - if (routes.children) { - if ( - routes.children.some(item => { - return item.meta.sidebar != false - }) - ) { - for (let i = 0; i < routes.children.length; i++) { - if (routes.children[i].meta.sidebar != false) { - retnPath = getDeepestPath(routes.children[i], resolveRoutePath(rootPath, routes.path)) - break - } - } - } else { - retnPath = getDeepestPath(routes.children[0], resolveRoutePath(rootPath, routes.path)) - } - } else { - retnPath = resolveRoutePath(rootPath, routes.path) - } - return retnPath -} - -function hasPermission(permissions, route) { - let isAuth = false - if (route.meta && route.meta.auth) { - isAuth = permissions.some(auth => { - if (typeof route.meta.auth == 'string') { - return route.meta.auth === auth - } else { - return route.meta.auth.some(routeAuth => { - return routeAuth === auth - }) - } - }) - } else { - isAuth = true - } - return isAuth -} - -function filterAsyncMenus(menus, permissions) { - const res = [] - menus.forEach(menu => { - let tmpMenu = deepClone(menu) - if (hasPermission(permissions, tmpMenu)) { - if (tmpMenu.children) { - tmpMenu.children = filterAsyncMenus(tmpMenu.children, permissions) - tmpMenu.children.length && res.push(tmpMenu) - } else { - res.push(tmpMenu) - } - } - }) - return res -} - -const useMenuStore = defineStore( - // 唯一ID - 'menu', - { - state: () => ({ - menus: [], - actived: 0 - }), - getters: { - // 完整导航数据 - allMenus() { - const settingsStore = useSettingsStore() - let menus - if (settingsStore.app.routeBaseOn !== 'filesystem') { - const routeStore = useRouteStore() - if (settingsStore.menu.menuMode === 'single') { - menus = [{ children: [] }] - routeStore.routes.map(item => { - menus[0].children.push(...item.children) - }) - } else { - menus = routeStore.routes - } - } else { - menus = this.menus - } - return menus - }, - // 次导航数据 - sidebarMenus() { - return this.allMenus.length > 0 ? this.allMenus[this.actived].children : [] - }, - // 次导航里第一个导航的路径 - sidebarMenusFirstDeepestPath() { - return this.allMenus.length > 0 ? getDeepestPath(this.sidebarMenus[0]) : '/' - }, - defaultOpenedPaths() { - const settingsStore = useSettingsStore() - let defaultOpenedPaths = [] - if (settingsStore.app.routeBaseOn !== 'filesystem') { - const routeStore = useRouteStore() - routeStore.routes.map(item => { - item.meta.defaultOpened && defaultOpenedPaths.push(item.path) - item.children && item.children.map(child => { - child.meta.defaultOpened && defaultOpenedPaths.push(path.resolve(item.path, child.path)) - }) - }) - } - return defaultOpenedPaths - } - }, - actions: { - // 生成导航(前端生成) - generateMenusAtFront() { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async resolve => { - const settingsStore = useSettingsStore() - const userStore = useUserStore() - let accessedMenus - // 如果权限功能开启,则需要对导航数据进行筛选过滤 - if (settingsStore.app.enablePermission) { - const permissions = await userStore.getPermissions() - accessedMenus = filterAsyncMenus(menu, permissions) - } else { - accessedMenus = deepClone(menu) - } - this.menus = accessedMenus.filter(item => item.children.length != 0) - resolve() - }) - }, - // 生成导航(后端生成) - generateMenusAtBack() { - return new Promise(resolve => { - api.get('menu/list', { - baseURL: '/mock/' - }).then(async res => { - const settingsStore = useSettingsStore() - const userStore = useUserStore() - let accessedMenus - // 如果权限功能开启,则需要对导航数据进行筛选过滤 - if (settingsStore.app.enablePermission) { - const permissions = await userStore.getPermissions() - accessedMenus = filterAsyncMenus(res.data, permissions) - } else { - accessedMenus = deepClone(res.data) - } - this.menus = accessedMenus.filter(item => item.children.length != 0) - resolve() - }) - }) - }, - // 切换主导航 - setActived(data) { - if (typeof data === 'number') { - // 如果是 number 类型,则认为是主导航的索引 - this.actived = data - } else { - // 如果是 string 类型,则认为是路由,需要查找对应的主导航索引 - this.allMenus.map((item, index) => { - if ( - item.children.some(r => { - return data.indexOf(r.path + '/') === 0 || data == r.path - }) - ) { - this.actived = index - } - }) - } - } - } - } -) - -export default useMenuStore diff --git a/src/store/modules/menu.ts b/src/store/modules/menu.ts new file mode 100644 index 00000000..43e44825 --- /dev/null +++ b/src/store/modules/menu.ts @@ -0,0 +1,206 @@ +import { cloneDeep } from 'lodash-es' +import useSettingsStore from './settings' +import useUserStore from './user' +import useRouteStore from './route' +import type { Menu } from '@/global' + +import { resolveRoutePath } from '@/utils' +import api from '@/api' +import menu from '@/menu' + +function getDeepestPath(menu: Menu.recordRaw, rootPath = '') { + let retnPath = '' + if (menu.path) { + if (menu.children) { + if ( + menu.children.some((item) => { + return item.meta.sidebar !== false + }) + ) { + for (let i = 0; i < menu.children.length; i++) { + if (menu.children[i].meta.sidebar !== false) { + retnPath = getDeepestPath(menu.children[i], resolveRoutePath(rootPath, menu.path)) + break + } + } + } + else { + retnPath = getDeepestPath(menu.children[0], resolveRoutePath(rootPath, menu.path)) + } + } + else { + retnPath = resolveRoutePath(rootPath, menu.path) + } + } + return retnPath +} + +function hasPermission(permissions: string[], menu: Menu.recordMainRaw | Menu.recordRaw) { + let isAuth = false + if (menu.meta && menu.meta.auth) { + isAuth = permissions.some((auth) => { + return typeof menu.meta.auth === 'string' + ? menu.meta.auth === auth + : typeof menu.meta.auth === 'object' + ? menu.meta.auth.includes(auth) + : false + }) + } + else { + isAuth = true + } + return isAuth +} + +function filterAsyncMenus(menus: T, permissions: string[]): T { + const res: any = [] + menus.forEach((menu) => { + const tmpMenu = cloneDeep(menu) + if (hasPermission(permissions, tmpMenu)) { + if (tmpMenu.children) { + tmpMenu.children = filterAsyncMenus(tmpMenu.children, permissions) as Menu.recordRaw[] + tmpMenu.children.length && res.push(tmpMenu) + } + else { + res.push(tmpMenu) + } + } + }) + return res +} + +function getDefaultOpenedPaths(menus: Menu.recordRaw[], rootPath = '') { + const defaultOpenedPaths: string[] = [] + menus.forEach((item) => { + if (item.path && item.meta.defaultOpened && item.children) { + defaultOpenedPaths.push(resolveRoutePath(rootPath, item.path)) + const childrenDefaultOpenedPaths = getDefaultOpenedPaths(item.children, resolveRoutePath(rootPath, item.path)) + if (childrenDefaultOpenedPaths.length > 0) + defaultOpenedPaths.push(...childrenDefaultOpenedPaths) + } + }) + return defaultOpenedPaths +} + +const useMenuStore = defineStore( + // 唯一ID + 'menu', + { + state: () => ({ + menus: [{ + meta: {}, + children: [], + }] as Menu.recordMainRaw[], + actived: 0, + }), + getters: { + // 完整导航数据 + allMenus() { + const settingsStore = useSettingsStore() + let menus: Menu.recordMainRaw[] = [{ + meta: {}, + children: [], + }] + if (settingsStore.app.routeBaseOn !== 'filesystem') { + const routeStore = useRouteStore() + if (settingsStore.menu.menuMode === 'single') { + menus[0].children = [] + routeStore.routes.forEach((item) => { + menus[0].children?.push(...item.children as Menu.recordRaw[]) + }) + } + else { + menus = routeStore.routes as Menu.recordMainRaw[] + } + } + else { + menus = this.menus + } + return menus + }, + // 次导航数据 + sidebarMenus(): Menu.recordMainRaw['children'] { + return this.allMenus.length > 0 + ? this.allMenus[this.actived].children + : [] + }, + // 次导航里第一个导航的路径 + sidebarMenusFirstDeepestPath(): string { + return this.allMenus.length > 0 + ? getDeepestPath(this.sidebarMenus[0]) + : '/' + }, + defaultOpenedPaths() { + const settingsStore = useSettingsStore() + let defaultOpenedPaths: string[] = [] + if (settingsStore.app.routeBaseOn !== 'filesystem') + defaultOpenedPaths = getDefaultOpenedPaths(this.sidebarMenus) + + return defaultOpenedPaths + }, + }, + actions: { + // 生成导航(前端生成) + generateMenusAtFront() { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve) => { + const settingsStore = useSettingsStore() + const userStore = useUserStore() + let accessedMenus + // 如果权限功能开启,则需要对导航数据进行筛选过滤 + if (settingsStore.app.enablePermission) { + const permissions = await userStore.getPermissions() + accessedMenus = filterAsyncMenus(menu, permissions) + } + else { + accessedMenus = cloneDeep(menu) + } + this.menus = accessedMenus.filter(item => item.children.length !== 0) + resolve() + }) + }, + // 生成导航(后端生成) + generateMenusAtBack() { + return new Promise((resolve) => { + api.get('menu/list', { + baseURL: '/mock/', + }).then(async (res) => { + const settingsStore = useSettingsStore() + const userStore = useUserStore() + let accessedMenus: Menu.recordMainRaw[] + // 如果权限功能开启,则需要对导航数据进行筛选过滤 + if (settingsStore.app.enablePermission) { + const permissions = await userStore.getPermissions() + accessedMenus = filterAsyncMenus(res.data, permissions) + } + else { + accessedMenus = cloneDeep(res.data) + } + this.menus = accessedMenus.filter(item => item.children.length !== 0) + resolve() + }) + }) + }, + // 切换主导航 + setActived(data: number | string) { + if (typeof data === 'number') { + // 如果是 number 类型,则认为是主导航的索引 + this.actived = data + } + else { + // 如果是 string 类型,则认为是路由,需要查找对应的主导航索引 + this.allMenus.forEach((item, index) => { + if ( + item.children.some((r) => { + return data.indexOf(`${r.path}/`) === 0 || data === r.path + }) + ) + this.actived = index + }) + } + }, + }, + }, +) + +export default useMenuStore diff --git a/src/store/modules/route.js b/src/store/modules/route.js deleted file mode 100644 index e82c672a..00000000 --- a/src/store/modules/route.js +++ /dev/null @@ -1,233 +0,0 @@ -import { deepClone, resolveRoutePath } from '@/utils' -import api from '@/api' -import { systemRoutes } from '@/router/routes' - -import useSettingsStore from './settings' -import useUserStore from './user' - -function hasPermission(permissions, route) { - let isAuth = false - if (route.meta && route.meta.auth) { - isAuth = permissions.some(auth => { - if (typeof route.meta.auth == 'string') { - return route.meta.auth === auth - } else { - return route.meta.auth.some(routeAuth => { - return routeAuth === auth - }) - } - }) - } else { - isAuth = true - } - return isAuth -} - -function filterAsyncRoutes(routes, permissions) { - const res = [] - routes.forEach(route => { - let tmpRoute = deepClone(route) - if (hasPermission(permissions, tmpRoute)) { - if (tmpRoute.children) { - tmpRoute.children = filterAsyncRoutes(tmpRoute.children, permissions) - tmpRoute.children.length && res.push(tmpRoute) - } else { - res.push(tmpRoute) - } - } - }) - return res -} - -function formatBackRoutes(routes, views = import.meta.glob('../../views/**/*.vue')) { - return routes.map(route => { - switch (route.component) { - case 'Layout': - route.component = () => import('@/layout/index.vue') - break - default: - if (route.component) { - route.component = views[`../../views/${route.component}`] - } - } - if (route.children) { - route.children = formatBackRoutes(route.children, views) - } - return route - }) -} - -// 将多层嵌套路由处理成两层,保留顶层和最子层路由,中间层级将被拍平 -function flatAsyncRoutes(routes) { - if (routes.children) { - routes.children = flatAsyncRoutesRecursive(routes.children, [{ - path: routes.path, - title: routes.meta.title, - hide: !routes.meta.breadcrumb && routes.meta.breadcrumb === false - }], routes.path) - } - return routes -} -function flatAsyncRoutesRecursive(routes, breadcrumb, baseUrl) { - let res = [] - routes.forEach(route => { - if (route.children) { - let childrenBaseUrl = resolveRoutePath(baseUrl, route.path) - let tmpBreadcrumb = deepClone(breadcrumb) - if (route.meta.breadcrumb !== false) { - tmpBreadcrumb.push({ - path: childrenBaseUrl, - title: route.meta.title, - hide: !route.meta.breadcrumb && route.meta.breadcrumb === false - }) - } - let tmpRoute = deepClone(route) - tmpRoute.path = childrenBaseUrl - tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb - delete tmpRoute.children - res.push(tmpRoute) - let childrenRoutes = flatAsyncRoutesRecursive(route.children, tmpBreadcrumb, childrenBaseUrl) - childrenRoutes.map(item => { - // 如果 path 一样则覆盖,因为子路由的 path 可能设置为空,导致和父路由一样,直接注册会提示路由重复 - if (res.some(v => v.path == item.path)) { - res.forEach((v, i) => { - if (v.path == item.path) { - res[i] = item - } - }) - } else { - res.push(item) - } - }) - } else { - let tmpRoute = deepClone(route) - tmpRoute.path = resolveRoutePath(baseUrl, tmpRoute.path) - // 处理面包屑导航 - let tmpBreadcrumb = deepClone(breadcrumb) - tmpBreadcrumb.push({ - path: tmpRoute.path, - title: tmpRoute.meta.title, - hide: !tmpRoute.meta.breadcrumb && tmpRoute.meta.breadcrumb === false - }) - tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb - res.push(tmpRoute) - } - }) - return res -} - -const useRouteStore = defineStore( - // 唯一ID - 'route', - { - state: () => ({ - isGenerate: false, - routes: [], - currentRemoveRoutes: [] - }), - getters: { - // 扁平化路由(将三级及以上路由数据拍平成二级) - flatRoutes: state => { - const settingsStore = useSettingsStore() - let routes = [] - if (state.routes) { - if (settingsStore.app.routeBaseOn !== 'filesystem') { - state.routes.map(item => { - routes.push(...deepClone(item.children)) - }) - routes.map(item => flatAsyncRoutes(item)) - } else { - routes.push(...deepClone(state.routes)) - } - } - return routes - }, - flatSystemRoutes: () => { - let routes = [...systemRoutes] - routes.map(item => flatAsyncRoutes(item)) - return routes - } - }, - actions: { - // 根据权限动态生成路由(前端生成) - generateRoutesAtFront(asyncRoutes) { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async resolve => { - const settingsStore = useSettingsStore() - const userStore = useUserStore() - let accessedRoutes - // 如果权限功能开启,则需要对路由数据进行筛选过滤 - if (settingsStore.app.enablePermission) { - const permissions = await userStore.getPermissions() - accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions) - } else { - accessedRoutes = deepClone(asyncRoutes) - } - // 设置 routes 数据 - this.isGenerate = true - this.routes = accessedRoutes.filter(item => item.children.length != 0) - resolve() - }) - }, - // 根据权限动态生成路由(后端获取) - generateRoutesAtBack() { - return new Promise(resolve => { - api.get('route/list', { - baseURL: '/mock/' - }).then(async res => { - const settingsStore = useSettingsStore() - const userStore = useUserStore() - let asyncRoutes = formatBackRoutes(res.data) - let accessedRoutes - // 如果权限功能开启,则需要对路由数据进行筛选过滤 - if (settingsStore.app.enablePermission) { - const permissions = await userStore.getPermissions() - accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions) - } else { - accessedRoutes = deepClone(asyncRoutes) - } - // 设置 routes 数据 - this.isGenerate = true - this.routes = accessedRoutes.filter(item => item.children.length != 0) - resolve() - }) - }) - }, - // 根据权限动态生成路由(文件系统生成) - generateRoutesAtFilesystem(asyncRoutes) { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async resolve => { - const settingsStore = useSettingsStore() - const userStore = useUserStore() - let accessedRoutes - // 如果权限功能开启,则需要对路由数据进行筛选过滤 - if (settingsStore.app.enablePermission) { - const permissions = await userStore.getPermissions() - accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions) - } else { - accessedRoutes = deepClone(asyncRoutes) - } - // 设置 routes 数据 - this.isGenerate = true - this.routes = accessedRoutes.filter(item => item.children.length != 0) - resolve() - }) - }, - // 记录 accessRoutes 路由,用于登出时删除路由 - setCurrentRemoveRoutes(routes) { - this.currentRemoveRoutes = routes - }, - // 清空路由 - removeRoutes() { - this.isGenerate = false - this.routes = [] - this.currentRemoveRoutes.forEach(removeRoute => { - removeRoute() - }) - this.currentRemoveRoutes = [] - } - } - } -) - -export default useRouteStore diff --git a/src/store/modules/route.ts b/src/store/modules/route.ts new file mode 100644 index 00000000..4a4d0374 --- /dev/null +++ b/src/store/modules/route.ts @@ -0,0 +1,240 @@ +import { cloneDeep } from 'lodash-es' +import useSettingsStore from './settings' +import useUserStore from './user' +import api from '@/api' +import { resolveRoutePath } from '@/utils' +import { systemRoutes } from '@/router/routes' +import type { Route } from '@/global' + +function hasPermission(permissions: string[], route: Route.recordMainRaw | Route.recordRaw) { + let isAuth = false + if (route.meta?.auth) { + isAuth = permissions.some((auth) => { + return typeof route.meta.auth === 'string' + ? route.meta.auth === auth + : typeof route.meta.auth === 'object' + ? route.meta.auth.includes(auth) + : false + }) + } + else { + isAuth = true + } + return isAuth +} + +function filterAsyncRoutes(routes: T, permissions: string[]): T { + const res: any = [] + routes.forEach((route) => { + if (hasPermission(permissions, route)) { + const tmpRoute = cloneDeep(route) + if (tmpRoute.children) { + tmpRoute.children = filterAsyncRoutes(tmpRoute.children, permissions) + tmpRoute.children.length && res.push(tmpRoute) + } + else { + res.push(tmpRoute) + } + } + }) + return res +} + +function formatBackRoutes(routes: any, views = import.meta.glob('../../views/**/*.vue')): Route.recordMainRaw[] { + return routes.map((route: any) => { + switch (route.component) { + case 'Layout': + route.component = () => import('@/layouts/index.vue') + break + default: + if (route.component) + route.component = views[`../../views/${route.component}`] + } + if (route.children) + route.children = formatBackRoutes(route.children, views) + + return route + }) +} + +// 将多层嵌套路由处理成两层,保留顶层和最子层路由,中间层级将被拍平 +function flatAsyncRoutes(routes: T): T { + if (routes.children) { + routes.children = flatAsyncRoutesRecursive(routes.children, [{ + path: routes.path, + title: routes.meta.title, + hide: !routes.meta.breadcrumb && routes.meta.breadcrumb === false, + }], routes.path) + } + return routes +} +function flatAsyncRoutesRecursive(routes: Route.recordRaw[], breadcrumb: Route.breadcrumb[] = [], baseUrl = ''): Route.recordRaw[] { + const res: Route.recordRaw[] = [] + routes.forEach((route) => { + if (route.children) { + const childrenBaseUrl = resolveRoutePath(baseUrl, route.path) + const tmpBreadcrumb = cloneDeep(breadcrumb) + if (route.meta.breadcrumb !== false) { + tmpBreadcrumb.push({ + path: childrenBaseUrl, + title: route.meta.title, + hide: !route.meta.breadcrumb && route.meta.breadcrumb === false, + }) + } + const tmpRoute = cloneDeep(route) + tmpRoute.path = childrenBaseUrl + tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb + delete tmpRoute.children + res.push(tmpRoute) + const childrenRoutes = flatAsyncRoutesRecursive(route.children, tmpBreadcrumb, childrenBaseUrl) + childrenRoutes.forEach((item) => { + // 如果 path 一样则覆盖,因为子路由的 path 可能设置为空,导致和父路由一样,直接注册会提示路由重复 + if (res.some(v => v.path === item.path)) { + res.forEach((v, i) => { + if (v.path === item.path) + res[i] = item + }) + } + else { + res.push(item) + } + }) + } + else { + const tmpRoute = cloneDeep(route) + tmpRoute.path = resolveRoutePath(baseUrl, tmpRoute.path) + // 处理面包屑导航 + const tmpBreadcrumb = cloneDeep(breadcrumb) + tmpBreadcrumb.push({ + path: tmpRoute.path, + title: tmpRoute.meta.title, + hide: !tmpRoute.meta.breadcrumb && tmpRoute.meta.breadcrumb === false, + }) + tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb + res.push(tmpRoute) + } + }) + return res +} + +const useRouteStore = defineStore( + // 唯一ID + 'route', + { + state: () => ({ + isGenerate: false, + routes: [] as Route.recordMainRaw[], + fileSystemRoutes: [] as Route.recordRaw[], + currentRemoveRoutes: [] as Function[], + }), + getters: { + // 扁平化路由(将三级及以上路由数据拍平成二级) + flatRoutes: (state) => { + const settingsStore = useSettingsStore() + const routes: Route.recordRaw[] = [] + if (state.routes) { + if (settingsStore.app.routeBaseOn !== 'filesystem') { + state.routes.forEach((item) => { + routes.push(...cloneDeep(item.children)) + }) + routes.forEach(item => flatAsyncRoutes(item)) + } + else { + // @ts-expect-error: ignore + routes.push(...cloneDeep(state.fileSystemRoutes)) + } + } + return routes + }, + flatSystemRoutes: () => { + const routes = [...systemRoutes] + routes.forEach(item => flatAsyncRoutes(item)) + return routes + }, + }, + actions: { + // 根据权限动态生成路由(前端生成) + generateRoutesAtFront(asyncRoutes: Route.recordMainRaw[]) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve) => { + const settingsStore = useSettingsStore() + const userStore = useUserStore() + let accessedRoutes + // 如果权限功能开启,则需要对路由数据进行筛选过滤 + if (settingsStore.app.enablePermission) { + const permissions = await userStore.getPermissions() + accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions) + } + else { + accessedRoutes = cloneDeep(asyncRoutes) + } + // 设置 routes 数据 + this.isGenerate = true + this.routes = accessedRoutes.filter(item => item.children?.length !== 0) + resolve() + }) + }, + // 根据权限动态生成路由(后端获取) + generateRoutesAtBack() { + return new Promise((resolve) => { + api.get('route/list', { + baseURL: '/mock/', + }).then(async (res) => { + const settingsStore = useSettingsStore() + const userStore = useUserStore() + const asyncRoutes = formatBackRoutes(res.data) + let accessedRoutes + // 如果权限功能开启,则需要对路由数据进行筛选过滤 + if (settingsStore.app.enablePermission) { + const permissions = await userStore.getPermissions() + accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions) + } + else { + accessedRoutes = cloneDeep(asyncRoutes) + } + // 设置 routes 数据 + this.isGenerate = true + this.routes = accessedRoutes.filter(item => item.children.length !== 0) + resolve() + }) + }) + }, + // 根据权限动态生成路由(文件系统生成) + generateRoutesAtFilesystem(asyncRoutes: Route.recordRaw[]) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve) => { + const settingsStore = useSettingsStore() + const userStore = useUserStore() + let accessedRoutes + // 如果权限功能开启,则需要对路由数据进行筛选过滤 + if (settingsStore.app.enablePermission) { + const permissions = await userStore.getPermissions() + accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions) + } + else { + accessedRoutes = cloneDeep(asyncRoutes) + } + // 设置 routes 数据 + this.isGenerate = true + this.fileSystemRoutes = accessedRoutes.filter(item => item.children?.length !== 0) + resolve() + }) + }, + // 记录 accessRoutes 路由,用于登出时删除路由 + setCurrentRemoveRoutes(routes: Function[]) { + this.currentRemoveRoutes = routes + }, + // 清空动态路由 + removeRoutes() { + this.isGenerate = false + this.routes = [] + this.currentRemoveRoutes.forEach((removeRoute) => { + removeRoute() + }) + this.currentRemoveRoutes = [] + }, + }, + }, +) + +export default useRouteStore diff --git a/src/store/modules/settings.js b/src/store/modules/settings.js deleted file mode 100644 index e6d4c4fb..00000000 --- a/src/store/modules/settings.js +++ /dev/null @@ -1,58 +0,0 @@ -import settings from '@/settings' - -const useSettingsStore = defineStore( - // 唯一ID - 'settings', - { - state: () => ({ - ...settings, - // 侧边栏是否收起(用于记录 pc 模式下最后的状态) - subMenuCollapseLastStatus: settings.menu.subMenuCollapse, - // 显示模式,支持:mobile、pc - mode: 'pc', - // 页面标题 - title: '' - }), - actions: { - // 设置访问模式 - setMode(width) { - if (this.layout.enableMobileAdaptation) { - // 先判断 UA 是否为移动端设备(手机&平板) - if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { - this.mode = 'mobile' - } else { - // 如果为桌面设备,再根据页面宽度判断是否需要切换为移动端展示 - if (width < 992) { - this.mode = 'mobile' - } else { - this.mode = 'pc' - } - } - } else { - this.mode = 'pc' - } - }, - // 设置网页标题 - setTitle(title) { - this.title = title - }, - // 切换侧边栏导航展开/收起 - toggleSidebarCollapse() { - this.menu.subMenuCollapse = !this.menu.subMenuCollapse - if (this.mode == 'pc') { - this.subMenuCollapseLastStatus = !this.subMenuCollapseLastStatus - } - }, - // 设置主题颜色模式 - setColorScheme(color) { - this.app.colorScheme = color - }, - // 更新主题配置 - updateThemeSetting(data) { - Object.assign(this, data) - } - } - } -) - -export default useSettingsStore diff --git a/src/store/modules/settings.ts b/src/store/modules/settings.ts new file mode 100644 index 00000000..644817dd --- /dev/null +++ b/src/store/modules/settings.ts @@ -0,0 +1,72 @@ +import type { RecursiveRequired, Settings } from '@/global' +import settings from '@/settings' +import settingsDefault from '@/settings.default' + +interface SettingsStore extends RecursiveRequired { + subMenuCollapseLastStatus: boolean + mode: 'pc' | 'mobile' + title: string +} + +// 合并配置 +Object.keys(settingsDefault).forEach((key) => { + Object.assign(settingsDefault[key as keyof Settings.all], settings[key as keyof Settings.all]) +}) + +const useSettingsStore = defineStore( + // 唯一ID + 'settings', + { + state: (): SettingsStore => ({ + ...settingsDefault, + // 侧边栏是否收起(用于记录 pc 模式下最后的状态) + subMenuCollapseLastStatus: settingsDefault.menu.subMenuCollapse, + // 显示模式,支持:mobile、pc + mode: 'pc', + // 页面标题 + title: '', + }), + actions: { + // 设置网页标题 + setTitle(title: string) { + this.title = title + }, + // 设置访问模式 + setMode(width: number) { + if (this.layout.enableMobileAdaptation) { + // 先判断 UA 是否为移动端设备(手机&平板) + if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { + this.mode = 'mobile' + } + else { + // 如果为桌面设备,再根据页面宽度判断是否需要切换为移动端展示 + if (width < 992) + this.mode = 'mobile' + + else + this.mode = 'pc' + } + } + else { + this.mode = 'pc' + } + }, + // 切换侧边栏导航展开/收起 + toggleSidebarCollapse() { + this.menu.subMenuCollapse = !this.menu.subMenuCollapse + if (this.mode === 'pc') + this.subMenuCollapseLastStatus = !this.subMenuCollapseLastStatus + }, + // 设置主题颜色模式 + setColorScheme(color: Required['colorScheme']) { + this.app.colorScheme = color + }, + // 更新应用配置 + updateSettings(data: Settings.all) { + Object.assign(this, data) + }, + }, + }, +) + +export default useSettingsStore diff --git a/src/store/modules/user.js b/src/store/modules/user.js deleted file mode 100644 index 926c8f3c..00000000 --- a/src/store/modules/user.js +++ /dev/null @@ -1,93 +0,0 @@ -import api from '@/api' - -import useRouteStore from './route' -import useMenuStore from './menu' - -const useUserStore = defineStore( - // 唯一ID - 'user', - { - state: () => ({ - account: localStorage.account || '', - token: localStorage.token || '', - failure_time: localStorage.failure_time || '', - permissions: [] - }), - getters: { - isLogin: state => { - let retn = false - if (state.token) { - if (new Date().getTime() < state.failure_time * 1000) { - retn = true - } - } - return retn - } - }, - actions: { - login(data) { - return new Promise((resolve, reject) => { - // 通过 mock 进行登录 - api.post('member/login', data, { - baseURL: '/mock/' - }).then(res => { - localStorage.setItem('account', res.data.account) - localStorage.setItem('token', res.data.token) - localStorage.setItem('failure_time', res.data.failure_time) - this.account = res.data.account - this.token = res.data.token - this.failure_time = res.data.failure_time - resolve() - }).catch(error => { - reject(error) - }) - }) - }, - logout() { - return new Promise(resolve => { - const routeStore = useRouteStore() - const menuStore = useMenuStore() - localStorage.removeItem('account') - localStorage.removeItem('token') - localStorage.removeItem('failure_time') - this.account = '' - this.token = '' - this.failure_time = '' - routeStore.removeRoutes() - menuStore.setActived(0) - resolve() - }) - }, - // 获取我的权限 - getPermissions() { - return new Promise(resolve => { - // 通过 mock 获取权限 - api.get('member/permission', { - baseURL: '/mock/', - params: { - account: this.account - } - }).then(res => { - this.permissions = res.data.permissions - resolve(res.data.permissions) - }) - }) - }, - editPassword(data) { - return new Promise(resolve => { - api.post('member/edit/password', { - account: this.account, - password: data.password, - newpassword: data.newpassword - }, { - baseURL: '/mock/' - }).then(() => { - resolve() - }) - }) - } - } - } -) - -export default useUserStore diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts new file mode 100644 index 00000000..1fdd5ec5 --- /dev/null +++ b/src/store/modules/user.ts @@ -0,0 +1,97 @@ +import useRouteStore from './route' +import useMenuStore from './menu' +import api from '@/api' + +const useUserStore = defineStore( + // 唯一ID + 'user', + { + state: () => ({ + account: localStorage.account || '', + token: localStorage.token || '', + failure_time: localStorage.failure_time || '', + permissions: [], + }), + getters: { + isLogin: (state) => { + let retn = false + if (state.token) { + if (new Date().getTime() < parseInt(state.failure_time) * 1000) + retn = true + } + return retn + }, + }, + actions: { + login(data: { + account: string + password: string + }) { + return new Promise((resolve, reject) => { + // 通过 mock 进行登录 + api.post('member/login', data, { + baseURL: '/mock/', + }).then((res) => { + localStorage.setItem('account', res.data.account) + localStorage.setItem('token', res.data.token) + localStorage.setItem('failure_time', res.data.failure_time) + this.account = res.data.account + this.token = res.data.token + this.failure_time = res.data.failure_time + resolve() + }).catch((error) => { + reject(error) + }) + }) + }, + logout() { + return new Promise((resolve) => { + const routeStore = useRouteStore() + const menuStore = useMenuStore() + localStorage.removeItem('account') + localStorage.removeItem('token') + localStorage.removeItem('failure_time') + this.account = '' + this.token = '' + this.failure_time = '' + routeStore.removeRoutes() + menuStore.setActived(0) + resolve() + }) + }, + // 获取我的权限 + getPermissions() { + return new Promise((resolve) => { + // 通过 mock 获取权限 + api.get('member/permission', { + baseURL: '/mock/', + params: { + account: this.account, + }, + }).then((res) => { + this.permissions = res.data.permissions + resolve(res.data.permissions) + }) + }) + }, + editPassword(data: { + password: string + newpassword: string + }) { + return new Promise((resolve) => { + api.post('member/edit/password', { + account: this.account, + password: data.password, + newpassword: data.newpassword, + }, { + baseURL: '/mock/', + }).then(() => { + resolve() + }) + }) + }, + }, + }, +) + +export default useUserStore diff --git a/src/utils/composables/auth/index.js b/src/utils/composables/auth/index.js deleted file mode 100644 index 4fce7a36..00000000 --- a/src/utils/composables/auth/index.js +++ /dev/null @@ -1,43 +0,0 @@ -import useSettingsStore from '@/store/modules/settings' -import useUserStore from '@/store/modules/user' - -function useAuth() { - function hasPermission(permission) { - const settingsStore = useSettingsStore() - const userStore = useUserStore() - if (settingsStore.app.enablePermission) { - return userStore.permissions.some(v => { - return v === permission - }) - } else { - return true - } - } - - function auth(value) { - let auth - if (typeof value === 'string') { - auth = hasPermission(value) - } else { - auth = value.some(item => { - return hasPermission(item) - }) - } - return auth - } - - function authAll(value) { - const auth = value.every(item => { - return hasPermission(item) - }) - return auth - } - - return { - hasPermission, - auth, - authAll - } -} - -export default useAuth diff --git a/src/utils/composables/index.js b/src/utils/composables/index.js deleted file mode 100644 index f3593063..00000000 --- a/src/utils/composables/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import useAuth from './auth' -import useMainPage from './mainPage' -import useMenu from './menu' - -export { - useAuth, - useMainPage, - useMenu -} diff --git a/src/utils/composables/mainPage/index.js b/src/utils/composables/mainPage/index.js deleted file mode 100644 index 52ded1e3..00000000 --- a/src/utils/composables/mainPage/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import router from '@/router' - -function useMainPage() { - function reload() { - router.push({ - name: 'reload' - }) - } - - return { - reload - } -} - -export default useMainPage diff --git a/src/utils/composables/menu/index.js b/src/utils/composables/menu/index.js deleted file mode 100644 index ef2a487c..00000000 --- a/src/utils/composables/menu/index.js +++ /dev/null @@ -1,24 +0,0 @@ -import router from '@/router' -import useSettingsStore from '@/store/modules/settings' -import useMenuStore from '@/store/modules/menu' -import { isExternalLink } from '@/utils' - -export default function useMenu() { - const settingsStore = useSettingsStore() - const menuStore = useMenuStore() - - function switchTo(index) { - menuStore.setActived(index) - if (settingsStore.menu.switchMainMenuAndPageJump) { - if (isExternalLink(menuStore.sidebarMenusFirstDeepestPath)) - window.open(menuStore.sidebarMenusFirstDeepestPath, '_blank') - - else - router.push(menuStore.sidebarMenusFirstDeepestPath) - } - } - - return { - switchTo - } -} diff --git a/src/utils/composables/useAuth.ts b/src/utils/composables/useAuth.ts new file mode 100644 index 00000000..cb9ba136 --- /dev/null +++ b/src/utils/composables/useAuth.ts @@ -0,0 +1,39 @@ +import useSettingsStore from '@/store/modules/settings' +import useUserStore from '@/store/modules/user' + +export default function useAuth() { + function hasPermission(permission: string) { + const settingsStore = useSettingsStore() + const userStore = useUserStore() + if (settingsStore.app.enablePermission) + return userStore.permissions.includes(permission) + + else + return true + } + + function auth(value: string | string[]) { + let auth + if (typeof value === 'string') { + auth = hasPermission(value) + } + else { + auth = value.some((item) => { + return hasPermission(item) + }) + } + return auth + } + + function authAll(value: string[]) { + const auth = value.every((item) => { + return hasPermission(item) + }) + return auth + } + + return { + auth, + authAll, + } +} diff --git a/src/utils/composables/useCurrentInstance.ts b/src/utils/composables/useCurrentInstance.ts new file mode 100644 index 00000000..c75fa942 --- /dev/null +++ b/src/utils/composables/useCurrentInstance.ts @@ -0,0 +1,6 @@ +import type { ComponentInternalInstance } from 'vue' + +export default function useCurrentInstance() { + const { appContext } = getCurrentInstance() as ComponentInternalInstance + return appContext.config.globalProperties +} diff --git a/src/utils/composables/useMainPage.ts b/src/utils/composables/useMainPage.ts new file mode 100644 index 00000000..3ccb739a --- /dev/null +++ b/src/utils/composables/useMainPage.ts @@ -0,0 +1,13 @@ +import router from '@/router' + +export default function useMainPage() { + function reload() { + router.push({ + name: 'reload', + }) + } + + return { + reload, + } +} diff --git a/src/utils/composables/useMenu.ts b/src/utils/composables/useMenu.ts new file mode 100644 index 00000000..aca65ca8 --- /dev/null +++ b/src/utils/composables/useMenu.ts @@ -0,0 +1,24 @@ +import router from '@/router' +import useSettingsStore from '@/store/modules/settings' +import useMenuStore from '@/store/modules/menu' +import { isExternalLink } from '@/utils' + +export default function useMenu() { + const settingsStore = useSettingsStore() + const menuStore = useMenuStore() + + function switchTo(index: number | string) { + menuStore.setActived(index) + if (settingsStore.menu.switchMainMenuAndPageJump) { + if (isExternalLink(menuStore.sidebarMenusFirstDeepestPath)) + window.open(menuStore.sidebarMenusFirstDeepestPath, '_blank') + + else + router.push(menuStore.sidebarMenusFirstDeepestPath) + } + } + + return { + switchTo, + } +} diff --git a/src/utils/dayjs.ts b/src/utils/dayjs.ts new file mode 100644 index 00000000..37bf2f2e --- /dev/null +++ b/src/utils/dayjs.ts @@ -0,0 +1,6 @@ +import dayjs from 'dayjs' +import 'dayjs/locale/zh-cn' + +dayjs.locale('zh-cn') + +export default dayjs diff --git a/src/utils/directive.js b/src/utils/directive.js deleted file mode 100644 index b5b53e74..00000000 --- a/src/utils/directive.js +++ /dev/null @@ -1,19 +0,0 @@ -import { useAuth } from '@/utils/composables' - -export default function directive(app) { - // 注册 v-auth 和 v-auth-all 指令 - app.directive('auth', { - mounted: (el, binding) => { - if (!useAuth().auth(binding.value)) { - el.remove() - } - } - }) - app.directive('auth-all', { - mounted: (el, binding) => { - if (!useAuth().authAll(binding.value)) { - el.remove() - } - } - }) -} diff --git a/src/utils/directive.ts b/src/utils/directive.ts new file mode 100644 index 00000000..b42fb0f1 --- /dev/null +++ b/src/utils/directive.ts @@ -0,0 +1,17 @@ +import type { App } from 'vue' + +export default function directive(app: App) { + // 注册 v-auth 和 v-auth-all 指令 + app.directive('auth', { + mounted: (el, binding) => { + if (!useAuth().auth(binding.value)) + el.remove() + }, + }) + app.directive('auth-all', { + mounted: (el, binding) => { + if (!useAuth().authAll(binding.value)) + el.remove() + }, + }) +} diff --git a/src/utils/eventBus.js b/src/utils/eventBus.ts similarity index 100% rename from src/utils/eventBus.js rename to src/utils/eventBus.ts diff --git a/src/utils/index.js b/src/utils/index.js deleted file mode 100644 index 29f9e511..00000000 --- a/src/utils/index.js +++ /dev/null @@ -1,48 +0,0 @@ -import path from 'path-browserify' - -export function deepClone(target) { - // 定义一个变量 - let result - // 如果当前需要深拷贝的是一个对象的话 - if (typeof target === 'object') { - // 如果是一个数组的话 - if (Array.isArray(target)) { - result = [] // 将result赋值为一个数组,并且执行遍历 - for (let i in target) { - // 递归克隆数组中的每一项 - result.push(deepClone(target[i])) - } - // 判断如果当前的值是null的话;直接赋值为null - } else if (target === null) { - result = null - // 判断如果当前的值是一个RegExp对象的话,直接赋值 - } else if (target.constructor === RegExp) { - result = target - } else { - // 否则是普通对象,直接for in循环,递归赋值对象的所有值 - result = {} - for (let i in target) { - result[i] = deepClone(target[i]) - } - } - // 如果不是对象的话,就是基本数据类型,那么直接赋值 - } else { - result = target - } - // 返回最终结果 - return result -} - -export function isExternalLink(path) { - return /^(https?:|mailto:|tel:)/.test(path) -} - -export function resolveRoutePath(basePath, routePath) { - if (isExternalLink(routePath)) { - return routePath - } - if (isExternalLink(basePath)) { - return basePath - } - return basePath ? path.resolve(basePath, routePath) : routePath -} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000..c48e7d7f --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,15 @@ +import path from 'path-browserify' + +export function isExternalLink(path: string) { + return /^(https?:|mailto:|tel:)/.test(path) +} + +export function resolveRoutePath(basePath: string, routePath: string) { + if (isExternalLink(routePath)) + return routePath + + if (isExternalLink(basePath)) + return basePath + + return basePath ? path.resolve(basePath, routePath) : routePath +} diff --git a/src/views/[...all].vue b/src/views/[...all].vue index 289e4920..4de4f13a 100644 --- a/src/views/[...all].vue +++ b/src/views/[...all].vue @@ -1,75 +1,80 @@ - -{ - name: 'notFound', - meta: { - title: "找不到页面", - constant: true, - layout: false - } -} + +name: notFound +meta: + title: 找不到页面 + constant: true + layout: false - diff --git a/src/views/breadcrumb_example/detail1.vue b/src/views/breadcrumb_example/detail1.vue index a2748508..82959057 100644 --- a/src/views/breadcrumb_example/detail1.vue +++ b/src/views/breadcrumb_example/detail1.vue @@ -1,15 +1,12 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false diff --git a/src/views/breadcrumb_example/detail2.vue b/src/views/breadcrumb_example/detail2.vue index a2748508..82959057 100644 --- a/src/views/breadcrumb_example/detail2.vue +++ b/src/views/breadcrumb_example/detail2.vue @@ -1,15 +1,12 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false diff --git a/src/views/breadcrumb_example/list1.vue b/src/views/breadcrumb_example/list1.vue index 280c001b..b9c15483 100644 --- a/src/views/breadcrumb_example/list1.vue +++ b/src/views/breadcrumb_example/list1.vue @@ -1,13 +1,12 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false diff --git a/src/views/breadcrumb_example/list2.vue b/src/views/breadcrumb_example/list2.vue index 0d0640fd..17f62e52 100644 --- a/src/views/breadcrumb_example/list2.vue +++ b/src/views/breadcrumb_example/list2.vue @@ -1,13 +1,12 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false diff --git a/src/views/component_basic_example/button.vue b/src/views/component_basic_example/button.vue index 1493bef0..d1f41211 100644 --- a/src/views/component_basic_example/button.vue +++ b/src/views/component_basic_example/button.vue @@ -1,211 +1,294 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_basic_example/checkbox.vue b/src/views/component_basic_example/checkbox.vue index 4bdb5f49..8ecb89ed 100644 --- a/src/views/component_basic_example/checkbox.vue +++ b/src/views/component_basic_example/checkbox.vue @@ -1,69 +1,65 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_basic_example/components/alert.vue b/src/views/component_basic_example/components/alert.vue index 7a7e2a83..ec63da85 100644 --- a/src/views/component_basic_example/components/alert.vue +++ b/src/views/component_basic_example/components/alert.vue @@ -1,17 +1,20 @@ diff --git a/src/views/component_basic_example/datepicker.vue b/src/views/component_basic_example/datepicker.vue index 27ac92ad..152d6c0c 100644 --- a/src/views/component_basic_example/datepicker.vue +++ b/src/views/component_basic_example/datepicker.vue @@ -1,83 +1,73 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_basic_example/datetimepicker.vue b/src/views/component_basic_example/datetimepicker.vue index 052b75ed..347f7787 100644 --- a/src/views/component_basic_example/datetimepicker.vue +++ b/src/views/component_basic_example/datetimepicker.vue @@ -1,41 +1,32 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_basic_example/icon.vue b/src/views/component_basic_example/icon.vue index b69fd5fb..f68468dc 100644 --- a/src/views/component_basic_example/icon.vue +++ b/src/views/component_basic_example/icon.vue @@ -1,59 +1,65 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_basic_example/input.vue b/src/views/component_basic_example/input.vue index 92b7933e..27353eb9 100644 --- a/src/views/component_basic_example/input.vue +++ b/src/views/component_basic_example/input.vue @@ -1,63 +1,51 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_basic_example/inputnumber.vue b/src/views/component_basic_example/inputnumber.vue index f603fccb..a2efcb2a 100644 --- a/src/views/component_basic_example/inputnumber.vue +++ b/src/views/component_basic_example/inputnumber.vue @@ -1,48 +1,36 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_basic_example/link.vue b/src/views/component_basic_example/link.vue index f86342a3..07ffda33 100644 --- a/src/views/component_basic_example/link.vue +++ b/src/views/component_basic_example/link.vue @@ -1,60 +1,83 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_basic_example/radio.vue b/src/views/component_basic_example/radio.vue index 9675c491..81b8e234 100644 --- a/src/views/component_basic_example/radio.vue +++ b/src/views/component_basic_example/radio.vue @@ -1,60 +1,66 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_basic_example/rate.vue b/src/views/component_basic_example/rate.vue index 22de01ef..9341f910 100644 --- a/src/views/component_basic_example/rate.vue +++ b/src/views/component_basic_example/rate.vue @@ -1,40 +1,28 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_basic_example/select.vue b/src/views/component_basic_example/select.vue index cfd6151d..27509941 100644 --- a/src/views/component_basic_example/select.vue +++ b/src/views/component_basic_example/select.vue @@ -1,75 +1,63 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_basic_example/slider.vue b/src/views/component_basic_example/slider.vue index ee6b63e1..35fc9fde 100644 --- a/src/views/component_basic_example/slider.vue +++ b/src/views/component_basic_example/slider.vue @@ -1,69 +1,56 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_basic_example/switch.vue b/src/views/component_basic_example/switch.vue index 123854b0..9c7db807 100644 --- a/src/views/component_basic_example/switch.vue +++ b/src/views/component_basic_example/switch.vue @@ -1,42 +1,30 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_basic_example/timepicker.vue b/src/views/component_basic_example/timepicker.vue index 77c735ab..24dffc62 100644 --- a/src/views/component_basic_example/timepicker.vue +++ b/src/views/component_basic_example/timepicker.vue @@ -1,68 +1,59 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_extend_example/actionbar.demo.vue b/src/views/component_extend_example/actionbar.demo.vue index 2ff58049..71048280 100644 --- a/src/views/component_extend_example/actionbar.demo.vue +++ b/src/views/component_extend_example/actionbar.demo.vue @@ -1,163 +1,166 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_extend_example/batchactionbar.demo.vue b/src/views/component_extend_example/batchactionbar.demo.vue index f8805554..874e09d4 100644 --- a/src/views/component_extend_example/batchactionbar.demo.vue +++ b/src/views/component_extend_example/batchactionbar.demo.vue @@ -1,77 +1,79 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_extend_example/editor.demo.vue b/src/views/component_extend_example/editor.demo.vue index fffac700..8b336b62 100644 --- a/src/views/component_extend_example/editor.demo.vue +++ b/src/views/component_extend_example/editor.demo.vue @@ -1,43 +1,36 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_extend_example/image.preview.demo.vue b/src/views/component_extend_example/image.preview.demo.vue index 2f75b35e..65fa2a8c 100644 --- a/src/views/component_extend_example/image.preview.demo.vue +++ b/src/views/component_extend_example/image.preview.demo.vue @@ -1,23 +1,20 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false diff --git a/src/views/component_extend_example/pageheader.demo.vue b/src/views/component_extend_example/pageheader.demo.vue index 29fc5502..11913ca7 100644 --- a/src/views/component_extend_example/pageheader.demo.vue +++ b/src/views/component_extend_example/pageheader.demo.vue @@ -1,27 +1,24 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false diff --git a/src/views/component_extend_example/pagemain.demo.vue b/src/views/component_extend_example/pagemain.demo.vue index 80bc72e4..105b73a9 100644 --- a/src/views/component_extend_example/pagemain.demo.vue +++ b/src/views/component_extend_example/pagemain.demo.vue @@ -1,50 +1,49 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false diff --git a/src/views/component_extend_example/pcas.demo.vue b/src/views/component_extend_example/pcas.demo.vue index b6321b43..27707dff 100644 --- a/src/views/component_extend_example/pcas.demo.vue +++ b/src/views/component_extend_example/pcas.demo.vue @@ -1,71 +1,69 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_extend_example/searchbar.demo.vue b/src/views/component_extend_example/searchbar.demo.vue index 2f8007cf..d3690f23 100644 --- a/src/views/component_extend_example/searchbar.demo.vue +++ b/src/views/component_extend_example/searchbar.demo.vue @@ -1,154 +1,157 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_extend_example/svg.demo.vue b/src/views/component_extend_example/svg.demo.vue index 28a2e8f8..256872e4 100644 --- a/src/views/component_extend_example/svg.demo.vue +++ b/src/views/component_extend_example/svg.demo.vue @@ -1,67 +1,66 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_extend_example/table.autoheight.demo.vue b/src/views/component_extend_example/table.autoheight.demo.vue index a8386ab5..20124291 100644 --- a/src/views/component_extend_example/table.autoheight.demo.vue +++ b/src/views/component_extend_example/table.autoheight.demo.vue @@ -1,160 +1,155 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/component_extend_example/trend.demo.vue b/src/views/component_extend_example/trend.demo.vue index 44c3c672..f9f2fccc 100644 --- a/src/views/component_extend_example/trend.demo.vue +++ b/src/views/component_extend_example/trend.demo.vue @@ -1,50 +1,49 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false diff --git a/src/views/component_extend_example/upload.demo.vue b/src/views/component_extend_example/upload.demo.vue index d8820499..f27966d3 100644 --- a/src/views/component_extend_example/upload.demo.vue +++ b/src/views/component_extend_example/upload.demo.vue @@ -1,68 +1,70 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/index.vue b/src/views/index.vue index a02422ec..8082fa94 100644 --- a/src/views/index.vue +++ b/src/views/index.vue @@ -1,279 +1,300 @@ - -{ - name: 'dashboard', - meta: { - title: "控制台" - } -} + +name: dashboard +meta: + title: 控制台 - diff --git a/src/views/jsx_example/components/JsxComp/index.vue b/src/views/jsx_example/components/JsxComp/index.vue index 5ea360b6..08ed2c4a 100644 --- a/src/views/jsx_example/components/JsxComp/index.vue +++ b/src/views/jsx_example/components/JsxComp/index.vue @@ -1,20 +1,21 @@ - diff --git a/src/views/jsx_example/index.jsx b/src/views/jsx_example/index.jsx deleted file mode 100644 index 3ee4218f..00000000 --- a/src/views/jsx_example/index.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import styles from './index.module.scss' -import JsxComp from './components/JsxComp/index.vue' - -export default defineComponent({ - name: 'JsxExample', - render() { - const iconsArr = ref([ - 'sidebar-jsx', - 'sidebar-element' - ]) - const icons = ( - iconsArr.value.map(v => { - return ( - - - - ) - }) - ) - - let count = ref(0) - function onPlus(v = 1) { - count.value += v - } - const html = '

这是一段HTML代码

' - const html2 =

- 这也是一段HTML代码 -

- - return
- - -

这是两个 Svg Icon 图标

- {icons} - -
-
-
-
-
-
- - onPlus(10)}>点我:{ count.value } -
- {html2} - - -
-
- } -}) diff --git a/src/views/jsx_example/index.module.scss b/src/views/jsx_example/index.module.scss index 529deba5..e954ed28 100644 --- a/src/views/jsx_example/index.module.scss +++ b/src/views/jsx_example/index.module.scss @@ -1,19 +1,23 @@ .example-icon { - font-size: 48px; + font-size: 48px; } + .test1 { - font-size: 24px; - .a { - width: 100px; - height: 100px; - background-color: #000; - } + font-size: 24px; + + .a { + width: 100px; + height: 100px; + background-color: #000; + } } + .test2 { - font-size: 24px; - .a { - width: 100px; - height: 100px; - background-color: #ff0; - } + font-size: 24px; + + .a { + width: 100px; + height: 100px; + background-color: #ff0; + } } diff --git a/src/views/jsx_example/index.tsx b/src/views/jsx_example/index.tsx new file mode 100644 index 00000000..905a923b --- /dev/null +++ b/src/views/jsx_example/index.tsx @@ -0,0 +1,51 @@ +import styles from './index.module.scss' +import JsxComp from './components/JsxComp/index.vue' + +export default defineComponent({ + name: 'JsxExample', + render() { + const iconsArr = ref([ + 'sidebar-jsx', + 'ep:element-plus', + ]) + const icons = ( + iconsArr.value.map((v) => { + return ( + + + + ) + }) + ) + + const count = ref(0) + function onPlus(v = 1) { + count.value += v + } + const html = '

这是一段HTML代码

' + const html2 =

+ 这也是一段HTML代码 +

+ + return
+ + +

这是两个 Svg Icon 图标

+ {icons} + +
+
+
+
+
+
+ + onPlus(10)}>点我:{ count.value } +
+ {html2} + + +
+
+ }, +}) diff --git a/src/views/keep_alive_example/detail.vue b/src/views/keep_alive_example/detail.vue index 07805d2f..a954c718 100644 --- a/src/views/keep_alive_example/detail.vue +++ b/src/views/keep_alive_example/detail.vue @@ -1,13 +1,12 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false diff --git a/src/views/keep_alive_example/nested/nested.vue b/src/views/keep_alive_example/nested/nested.vue index 28717362..6bb0a2a4 100644 --- a/src/views/keep_alive_example/nested/nested.vue +++ b/src/views/keep_alive_example/nested/nested.vue @@ -1,25 +1,22 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/keep_alive_example/nested/nested/nested.vue b/src/views/keep_alive_example/nested/nested/nested.vue index d5266909..530f7594 100644 --- a/src/views/keep_alive_example/nested/nested/nested.vue +++ b/src/views/keep_alive_example/nested/nested/nested.vue @@ -1,25 +1,22 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/keep_alive_example/nested/nested/nested/index.vue b/src/views/keep_alive_example/nested/nested/nested/index.vue index 842c3d70..543306d1 100644 --- a/src/views/keep_alive_example/nested/nested/nested/index.vue +++ b/src/views/keep_alive_example/nested/nested/nested/index.vue @@ -1,27 +1,17 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/keep_alive_example/nested/nested/nested/index2.vue b/src/views/keep_alive_example/nested/nested/nested/index2.vue index 1d6fdd8f..dbbc55e6 100644 --- a/src/views/keep_alive_example/nested/nested/nested/index2.vue +++ b/src/views/keep_alive_example/nested/nested/nested/index2.vue @@ -1,27 +1,17 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/keep_alive_example/page.vue b/src/views/keep_alive_example/page.vue index 958dd010..bc72dafe 100644 --- a/src/views/keep_alive_example/page.vue +++ b/src/views/keep_alive_example/page.vue @@ -1,68 +1,71 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/login.vue b/src/views/login.vue index 96164dbd..8f636b44 100644 --- a/src/views/login.vue +++ b/src/views/login.vue @@ -1,20 +1,17 @@ - -{ - meta: { - title: "登录", - constant: true, - layout: false - } -} + +meta: + title: 登录 + constant: true + layout: false - diff --git a/src/views/mock_example/index.vue b/src/views/mock_example/index.vue index ff029942..e6c6d0db 100644 --- a/src/views/mock_example/index.vue +++ b/src/views/mock_example/index.vue @@ -1,44 +1,43 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/multilevel_menu_example/level2/level3/page1.vue b/src/views/multilevel_menu_example/level2/level3/page1.vue index a8c02465..22356834 100644 --- a/src/views/multilevel_menu_example/level2/level3/page1.vue +++ b/src/views/multilevel_menu_example/level2/level3/page1.vue @@ -1,15 +1,12 @@ - -{ - meta: { - title: '导航2-2-1' - } -} + +meta: + title: 导航2-2-1 diff --git a/src/views/multilevel_menu_example/level2/level3/page2.vue b/src/views/multilevel_menu_example/level2/level3/page2.vue index 948df997..451294c0 100644 --- a/src/views/multilevel_menu_example/level2/level3/page2.vue +++ b/src/views/multilevel_menu_example/level2/level3/page2.vue @@ -1,15 +1,12 @@ - -{ - meta: { - title: '导航2-2-2' - } -} + +meta: + title: 导航2-2-2 diff --git a/src/views/multilevel_menu_example/level2/page.vue b/src/views/multilevel_menu_example/level2/page.vue index 3800c048..c092eeae 100644 --- a/src/views/multilevel_menu_example/level2/page.vue +++ b/src/views/multilevel_menu_example/level2/page.vue @@ -1,15 +1,12 @@ - -{ - meta: { - title: '导航2-1' - } -} + +meta: + title: 导航2-1 diff --git a/src/views/multilevel_menu_example/page.vue b/src/views/multilevel_menu_example/page.vue index a80ce220..59c83ade 100644 --- a/src/views/multilevel_menu_example/page.vue +++ b/src/views/multilevel_menu_example/page.vue @@ -1,15 +1,12 @@ - -{ - meta: { - title: '导航1' - } -} + +meta: + title: 导航1 diff --git a/src/views/permission_example/index.vue b/src/views/permission_example/index.vue index d84e9dca..40235c5e 100644 --- a/src/views/permission_example/index.vue +++ b/src/views/permission_example/index.vue @@ -1,118 +1,132 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false - diff --git a/src/views/personal/edit.password.vue b/src/views/personal/edit.password.vue index 84a95b67..57054be7 100644 --- a/src/views/personal/edit.password.vue +++ b/src/views/personal/edit.password.vue @@ -1,12 +1,11 @@ - -{ - meta: { - title: "修改密码" - } -} + +name: personalEditPassword +meta: + title: 修改密码 - diff --git a/src/views/personal/setting.vue b/src/views/personal/setting.vue index 989a6ec1..2da2e562 100644 --- a/src/views/personal/setting.vue +++ b/src/views/personal/setting.vue @@ -1,163 +1,191 @@ - -{ - meta: { - title: "个人设置", - cache: "personal-edit.password" - } -} + +name: personalSetting +meta: + title: 个人设置 + cache: personal-edit.password - diff --git a/src/views/reload.vue b/src/views/reload.vue index 8206cfcf..80ffa153 100644 --- a/src/views/reload.vue +++ b/src/views/reload.vue @@ -1,17 +1,15 @@ - -{ - name: 'reload' -} + +name: reload - diff --git a/src/views/videos_example/coming.soon.vue b/src/views/videos_example/coming.soon.vue index 8f98cf05..2e78ae13 100644 --- a/src/views/videos_example/coming.soon.vue +++ b/src/views/videos_example/coming.soon.vue @@ -1,13 +1,10 @@ - -{ - meta: { - enabled: false - } -} + +meta: + enabled: false diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..fc53447f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": [ + "ESNext", + "DOM" + ], + "skipLibCheck": true, + "baseUrl": "./", + "paths": { + "@/*": [ + "src/*" + ] + }, + "types": [ + "vite/client", + "element-plus/global", + "vite-plugin-pages/client", + "vite-plugin-vue-layouts/client" + ] + }, + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 00000000..62bdcce0 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "esModuleInterop": true, + "resolveJsonModule": true + }, + "include": [ + "package.json", + "vite.config.ts", + "vite/**/*.ts" + ] +} diff --git a/vite.config.js b/vite.config.js deleted file mode 100644 index 39676ab9..00000000 --- a/vite.config.js +++ /dev/null @@ -1,88 +0,0 @@ -import { defineConfig, loadEnv } from 'vite' -import createVitePlugins from './vite/plugins' - -const fs = require('fs') -const path = require('path') - -// https://vitejs.dev/config/ -export default ({ mode, command }) => { - const env = loadEnv(mode, process.cwd()) - // 全局 scss 资源 - const scssResources = [] - fs.readdirSync('src/assets/styles/resources').map(dirname => { - if (fs.statSync(`src/assets/styles/resources/${dirname}`).isFile()) { - scssResources.push(`@use "src/assets/styles/resources/${dirname}" as *;`) - } - }) - // css 精灵图相关 - fs.readdirSync('src/assets/sprites').map(dirname => { - if (fs.statSync(`src/assets/sprites/${dirname}`).isDirectory()) { - // css 精灵图生成的 scss 文件也需要放入全局 scss 资源 - scssResources.push(`@use "src/assets/sprites/_${dirname}.scss" as *;`) - } - }) - return defineConfig({ - base: './', - // 开发服务器选项 https://cn.vitejs.dev/config/#server-options - server: { - open: true, - port: 9000, - proxy: { - '/proxy': { - target: env.VITE_APP_API_BASEURL, - changeOrigin: command === 'serve' && env.VITE_OPEN_PROXY == 'true', - rewrite: path => path.replace(/\/proxy/, '') - } - } - }, - // 构建选项 https://cn.vitejs.dev/config/#server-fsserve-root - build: { - outDir: mode == 'production' ? 'dist' : `dist-${mode}`, - sourcemap: env.VITE_BUILD_SOURCEMAP == 'true', - minify: 'terser', - terserOptions: { - compress: { - drop_console: env.VITE_BUILD_DROP_CONSOLE == 'true' - } - } - }, - optimizeDeps: { - include: [ - 'element-plus/es', - 'element-plus/es/components/message/style/css', - 'element-plus/es/components/notification/style/css', - 'element-plus/es/components/message-box/style/css' - ] - }, - plugins: [ - ...createVitePlugins(env, command === 'build'), - // BUG https://github.com/antfu/unplugin-vue-components/issues/361 - { - name: 'dev-auto-import-element-plus', - transform(code, id) { - if (command === 'serve' && /src\/main.js$/.test(id)) { - return { - code: code.replace('/* importElementPlusPlaceholder */', ` - import ElementPlus from 'element-plus'; - app.use(ElementPlus); - `), - map: null - } - } - } - } - ], - resolve: { - alias: { - '@': path.resolve(__dirname, 'src') - } - }, - css: { - preprocessorOptions: { - scss: { - additionalData: scssResources.join('') - } - } - } - }) -} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 00000000..3219f3b6 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,77 @@ +import fs from 'node:fs' +import path from 'node:path' +import { defineConfig, loadEnv } from 'vite' +import dayjs from 'dayjs' +import pkg from './package.json' +import createVitePlugins from './vite/plugins' + +// https://vitejs.dev/config/ +export default ({ mode, command }) => { + const env = loadEnv(mode, process.cwd()) + // 全局 scss 资源 + const scssResources = [] + fs.readdirSync('src/assets/styles/resources').forEach((dirname) => { + if (fs.statSync(`src/assets/styles/resources/${dirname}`).isFile()) + scssResources.push(`@use "src/assets/styles/resources/${dirname}" as *;`) + }) + // css 精灵图相关 + fs.readdirSync('src/assets/sprites').forEach((dirname) => { + if (fs.statSync(`src/assets/sprites/${dirname}`).isDirectory()) { + // css 精灵图生成的 scss 文件也需要放入全局 scss 资源 + scssResources.push(`@use "src/assets/sprites/_${dirname}.scss" as *;`) + } + }) + return defineConfig({ + base: './', + // 开发服务器选项 https://cn.vitejs.dev/config/#server-options + server: { + open: true, + port: 9000, + proxy: { + '/proxy': { + target: env.VITE_APP_API_BASEURL, + changeOrigin: command === 'serve' && env.VITE_OPEN_PROXY === 'true', + rewrite: path => path.replace(/\/proxy/, ''), + }, + }, + }, + // 构建选项 https://cn.vitejs.dev/config/#server-fsserve-root + build: { + outDir: mode === 'production' ? 'dist' : `dist-${mode}`, + sourcemap: env.VITE_BUILD_SOURCEMAP === 'true', + }, + define: { + __SYSTEM_INFO__: JSON.stringify({ + pkg: { + version: pkg.version, + dependencies: pkg.dependencies, + devDependencies: pkg.devDependencies, + }, + lastBuildTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), + }), + }, + optimizeDeps: { + include: [ + 'element-plus', + 'element-plus/es', + 'element-plus/es/components/message/style/css', + 'element-plus/es/components/notification/style/css', + 'element-plus/es/components/message-box/style/css', + 'element-plus/es/components/loading/style/css', + ], + }, + plugins: createVitePlugins(env, command === 'build'), + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: scssResources.join(''), + }, + }, + }, + }) +} diff --git a/vite/plugins/auto-import.js b/vite/plugins/auto-import.js deleted file mode 100644 index 57ec6a8b..00000000 --- a/vite/plugins/auto-import.js +++ /dev/null @@ -1,29 +0,0 @@ -import autoImport from 'unplugin-auto-import/vite' -import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' - -export default function createAutoImport() { - return autoImport({ - imports: [ - 'vue', - 'vue-router', - { - 'vue': [ - 'defineProps', - 'defineEmits', - 'defineExpose', - 'withDefaults' - ] - }, - 'pinia' - ], - eslintrc: { - enabled: true, - filepath: './.eslintrc-auto-import.json', - globalsPropValue: true - }, - resolvers: [ - ElementPlusResolver() - ], - dts: false - }) -} diff --git a/vite/plugins/auto-import.ts b/vite/plugins/auto-import.ts new file mode 100644 index 00000000..3e60cfab --- /dev/null +++ b/vite/plugins/auto-import.ts @@ -0,0 +1,19 @@ +import autoImport from 'unplugin-auto-import/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' + +export default function createAutoImport() { + return autoImport({ + imports: [ + 'vue', + 'vue-router', + 'pinia', + ], + dts: 'src/auto-imports.d.ts', + dirs: [ + './src/utils/composables/**', + ], + resolvers: [ + ElementPlusResolver(), + ], + }) +} diff --git a/vite/plugins/banner.js b/vite/plugins/banner.ts similarity index 76% rename from vite/plugins/banner.js rename to vite/plugins/banner.ts index ca9a744e..3340a5f8 100644 --- a/vite/plugins/banner.js +++ b/vite/plugins/banner.ts @@ -1,14 +1,12 @@ import banner from 'vite-plugin-banner' export default function createBanner() { - return banner(` + return banner(` /** * 由 Fantastic-admin 提供技术支持 * Powered by Fantastic-admin - * https://fantastic-admin.netlify.app - * 代码仓库 * Gitee https://gitee.com/hooray/fantastic-admin * Github https://github.com/hooray/fantastic-admin */ - `) + `) } diff --git a/vite/plugins/components.js b/vite/plugins/components.js deleted file mode 100644 index bc0ed749..00000000 --- a/vite/plugins/components.js +++ /dev/null @@ -1,13 +0,0 @@ -import components from 'unplugin-vue-components/vite' -import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' - -export default function createComponents(isBuild) { - const resolvers = [] - isBuild && resolvers.push(ElementPlusResolver()) - return components({ - resolvers, - dirs: ['src/components'], - include: [/\.vue$/, /\.vue\?vue/, /\.jsx$/], - dts: false - }) -} diff --git a/vite/plugins/components.ts b/vite/plugins/components.ts new file mode 100644 index 00000000..c9e5e529 --- /dev/null +++ b/vite/plugins/components.ts @@ -0,0 +1,9 @@ +import components from 'unplugin-vue-components/vite' + +export default function createComponents() { + return components({ + dirs: ['src/components'], + include: [/\.vue$/, /\.vue\?vue/, /\.tsx$/], + dts: 'src/components.d.ts', + }) +} diff --git a/vite/plugins/compression.js b/vite/plugins/compression.js deleted file mode 100644 index f808ecad..00000000 --- a/vite/plugins/compression.js +++ /dev/null @@ -1,25 +0,0 @@ -import compression from 'vite-plugin-compression' - -export default function createCompression(env) { - const { VITE_BUILD_COMPRESS } = env - const compressList = VITE_BUILD_COMPRESS.split(',') - const plugin = [] - if (compressList.includes('gzip')) { - plugin.push( - compression({ - ext: '.gz', - deleteOriginFile: false - }) - ) - } - if (compressList.includes('brotli')) { - plugin.push( - compression({ - ext: '.br', - algorithm: 'brotliCompress', - deleteOriginFile: false - }) - ) - } - return plugin -} diff --git a/vite/plugins/compression.ts b/vite/plugins/compression.ts new file mode 100644 index 00000000..317582d7 --- /dev/null +++ b/vite/plugins/compression.ts @@ -0,0 +1,25 @@ +import compression from 'vite-plugin-compression' + +export default function createCompression(env) { + const { VITE_BUILD_COMPRESS } = env + const compressList = VITE_BUILD_COMPRESS.split(',') + const plugin: any[] = [] + if (compressList.includes('gzip')) { + plugin.push( + compression({ + ext: '.gz', + deleteOriginFile: false, + }), + ) + } + if (compressList.includes('brotli')) { + plugin.push( + compression({ + ext: '.br', + algorithm: 'brotliCompress', + deleteOriginFile: false, + }), + ) + } + return plugin +} diff --git a/vite/plugins/html.js b/vite/plugins/html.js deleted file mode 100644 index 66bbeb6b..00000000 --- a/vite/plugins/html.js +++ /dev/null @@ -1,29 +0,0 @@ -import { createHtmlPlugin } from 'vite-plugin-html' - -const copyright_common_style = 'font-size: 14px; margin-bottom: 2px; padding: 6px 8px; color: #fff;' -const copyright_main_style = `${copyright_common_style} background: #e24329;` -const copyright_sub_style = `${copyright_common_style} background: #707070;` - -export default function createHtml(env, isBuild) { - const { VITE_APP_TITLE, VITE_APP_DEBUG_TOOL, VITE_APP_MODE } = env - const html = createHtmlPlugin({ - inject: { - data: { - title: VITE_APP_TITLE, - debugTool: VITE_APP_DEBUG_TOOL, - appMode: VITE_APP_MODE, - copyrightScript: ` - - ` - } - }, - minify: isBuild - }) - return html -} diff --git a/vite/plugins/html.ts b/vite/plugins/html.ts new file mode 100644 index 00000000..54b7102b --- /dev/null +++ b/vite/plugins/html.ts @@ -0,0 +1,87 @@ +import { createHtmlPlugin } from 'vite-plugin-html' + +const copyright_common_style = 'font-size: 14px; margin-bottom: 2px; padding: 6px 8px; color: #fff;' +const copyright_main_style = `${copyright_common_style} background: #e24329;` +const copyright_sub_style = `${copyright_common_style} background: #707070;` + +export default function createHtml(env, isBuild) { + const { VITE_APP_TITLE, VITE_APP_DEBUG_TOOL, VITE_APP_MODE } = env + let copyrightScript = ` + + ` + if (VITE_APP_MODE === 'example') { + copyrightScript += ` + + ` + } + let devtoolScript = '' + switch (VITE_APP_DEBUG_TOOL) { + case 'eruda': + devtoolScript = ` + + + ` + break + case 'vconsole': + devtoolScript = ` + + + ` + break + } + const loadingScript = ` + + ` + const html = createHtmlPlugin({ + inject: { + data: { + title: VITE_APP_TITLE, + copyrightScript, + loadingScript, + devtoolScript, + }, + }, + minify: isBuild, + }) + return html +} diff --git a/vite/plugins/index.js b/vite/plugins/index.js deleted file mode 100644 index 7c10c0d8..00000000 --- a/vite/plugins/index.js +++ /dev/null @@ -1,33 +0,0 @@ -import vue from '@vitejs/plugin-vue' - -import createRestart from './restart' -import createJsx from './jsx' -import createHtml from './html' -import createAutoImport from './auto-import' -import createComponents from './components' -import createSetupExtend from './setup-extend' -import createSvgIcon from './svg-icon' -import createMock from './mock' -import createLayouts from './layouts' -import createPages from './pages' -import createCompression from './compression' -import createSpritesmith from './spritesmith' -import createBanner from './banner' - -export default function createVitePlugins(viteEnv, isBuild = false) { - const vitePlugins = [vue()] - !isBuild && vitePlugins.push(createRestart()) - vitePlugins.push(createJsx()) - vitePlugins.push(createHtml(viteEnv, isBuild)) - vitePlugins.push(createAutoImport()) - vitePlugins.push(createComponents(isBuild)) - vitePlugins.push(createSetupExtend()) - vitePlugins.push(createSvgIcon(isBuild)) - vitePlugins.push(createMock(viteEnv, isBuild)) - vitePlugins.push(createLayouts()) - vitePlugins.push(createPages()) - isBuild && vitePlugins.push(...createCompression(viteEnv)) - vitePlugins.push(...createSpritesmith(isBuild)) - vitePlugins.push(createBanner()) - return vitePlugins -} diff --git a/vite/plugins/index.ts b/vite/plugins/index.ts new file mode 100644 index 00000000..ce35e40d --- /dev/null +++ b/vite/plugins/index.ts @@ -0,0 +1,38 @@ +import type { PluginOption } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +import createRestart from './restart' +import createHtml from './html' +import createAutoImport from './auto-import' +import createComponents from './components' +import createSetupExtend from './setup-extend' +import createSvgIcon from './svg-icon' +import createMock from './mock' +import createLayouts from './layouts' +import createPages from './pages' +import createCompression from './compression' +import createSpritesmith from './spritesmith' +import createBanner from './banner' + +export default function createVitePlugins(viteEnv, isBuild = false) { + const vitePlugins: (PluginOption | PluginOption[])[] = [ + vue({ + reactivityTransform: true, + }), + vueJsx(), + ] + !isBuild && vitePlugins.push(createRestart()) + vitePlugins.push(createHtml(viteEnv, isBuild)) + vitePlugins.push(createAutoImport()) + vitePlugins.push(createComponents()) + vitePlugins.push(createSetupExtend()) + vitePlugins.push(createSvgIcon(isBuild)) + vitePlugins.push(createMock(viteEnv, isBuild)) + vitePlugins.push(createLayouts()) + vitePlugins.push(createPages()) + isBuild && vitePlugins.push(...createCompression(viteEnv)) + vitePlugins.push(...createSpritesmith(isBuild)) + vitePlugins.push(createBanner()) + return vitePlugins +} diff --git a/vite/plugins/jsx.js b/vite/plugins/jsx.js deleted file mode 100644 index e22d7c84..00000000 --- a/vite/plugins/jsx.js +++ /dev/null @@ -1,5 +0,0 @@ -import vueJsx from '@vitejs/plugin-vue-jsx' - -export default function createJsx() { - return vueJsx() -} diff --git a/vite/plugins/layouts.js b/vite/plugins/layouts.js deleted file mode 100644 index b6ba36fd..00000000 --- a/vite/plugins/layouts.js +++ /dev/null @@ -1,8 +0,0 @@ -import Layouts from 'vite-plugin-vue-layouts' - -export default function createPages() { - return Layouts({ - layoutsDirs: 'src/layout', - defaultLayout: 'index' - }) -} diff --git a/vite/plugins/layouts.ts b/vite/plugins/layouts.ts new file mode 100644 index 00000000..4f7e03b4 --- /dev/null +++ b/vite/plugins/layouts.ts @@ -0,0 +1,7 @@ +import Layouts from 'vite-plugin-vue-layouts' + +export default function createLayouts() { + return Layouts({ + defaultLayout: 'index', + }) +} diff --git a/vite/plugins/mock.js b/vite/plugins/mock.js deleted file mode 100644 index 8ccb9197..00000000 --- a/vite/plugins/mock.js +++ /dev/null @@ -1,14 +0,0 @@ -import { viteMockServe } from 'vite-plugin-mock' - -export default function createMock(env, isBuild) { - const { VITE_BUILD_MOCK } = env - return viteMockServe({ - mockPath: 'src/mock', - localEnabled: !isBuild, - prodEnabled: isBuild && VITE_BUILD_MOCK === 'true', - injectCode: ` - import { setupProdMockServer } from './mockProdServer'; - setupProdMockServer(); - ` - }) -} diff --git a/vite/plugins/mock.ts b/vite/plugins/mock.ts new file mode 100644 index 00000000..a709524c --- /dev/null +++ b/vite/plugins/mock.ts @@ -0,0 +1,14 @@ +import { viteMockServe } from 'vite-plugin-mock' + +export default function createMock(env, isBuild) { + const { VITE_BUILD_MOCK } = env + return viteMockServe({ + mockPath: 'src/mock', + localEnabled: !isBuild, + prodEnabled: isBuild && VITE_BUILD_MOCK === 'true', + injectCode: ` +import { setupProdMockServer } from './mockProdServer'; +setupProdMockServer(); + `, + }) +} diff --git a/vite/plugins/pages.js b/vite/plugins/pages.js deleted file mode 100644 index 8a28099c..00000000 --- a/vite/plugins/pages.js +++ /dev/null @@ -1,10 +0,0 @@ -import Pages from 'vite-plugin-pages' - -export default function createPages() { - return Pages({ - dirs: 'src/views', - exclude: [ - '**/components/**/*.vue' - ] - }) -} diff --git a/vite/plugins/pages.ts b/vite/plugins/pages.ts new file mode 100644 index 00000000..470bcca5 --- /dev/null +++ b/vite/plugins/pages.ts @@ -0,0 +1,10 @@ +import Pages from 'vite-plugin-pages' + +export default function createPages() { + return Pages({ + dirs: 'src/views', + exclude: [ + '**/components/**/*.vue', + ], + }) +} diff --git a/vite/plugins/restart.js b/vite/plugins/restart.js deleted file mode 100644 index 24814386..00000000 --- a/vite/plugins/restart.js +++ /dev/null @@ -1,14 +0,0 @@ -import ViteRestart from 'vite-plugin-restart' - -export default function createRestart() { - const pluginsFile = [] - const fs = require('fs') - fs.readdirSync('vite/plugins').map(dirname => { - if (fs.statSync(`vite/plugins/${dirname}`).isFile()) { - pluginsFile.push(`vite/plugins/${dirname}`) - } - }) - return ViteRestart({ - restart: pluginsFile - }) -} diff --git a/vite/plugins/restart.ts b/vite/plugins/restart.ts new file mode 100644 index 00000000..0b5724ce --- /dev/null +++ b/vite/plugins/restart.ts @@ -0,0 +1,13 @@ +import fs from 'node:fs' +import ViteRestart from 'vite-plugin-restart' + +export default function createRestart() { + const pluginsFile: string[] = [] + fs.readdirSync('vite/plugins').forEach((dirname) => { + if (fs.statSync(`vite/plugins/${dirname}`).isFile()) + pluginsFile.push(`vite/plugins/${dirname}`) + }) + return ViteRestart({ + restart: pluginsFile, + }) +} diff --git a/vite/plugins/setup-extend.js b/vite/plugins/setup-extend.ts similarity index 80% rename from vite/plugins/setup-extend.js rename to vite/plugins/setup-extend.ts index a4980f30..60d33f51 100644 --- a/vite/plugins/setup-extend.js +++ b/vite/plugins/setup-extend.ts @@ -1,5 +1,5 @@ import setupExtend from 'vite-plugin-vue-setup-extend' export default function createSetupExtend() { - return setupExtend() + return setupExtend() } diff --git a/vite/plugins/spritesmith.js b/vite/plugins/spritesmith.js deleted file mode 100644 index e016f315..00000000 --- a/vite/plugins/spritesmith.js +++ /dev/null @@ -1,49 +0,0 @@ -import spritesmith from 'vite-plugin-spritesmith' -import fs from 'fs' - -export default function createSpritesmith(isBuild) { - const spriteDirnames = [] - fs.readdirSync('src/assets/sprites').map(dirname => { - if (fs.statSync(`src/assets/sprites/${dirname}`).isDirectory()) { - spriteDirnames.push(dirname) - } - }) - const plugin = [] - spriteDirnames.map(item => { - plugin.push( - spritesmith({ - watch: !isBuild, - src: { - cwd: `./src/assets/sprites/${item}`, - glob: '*.png' - }, - target: { - image: `./src/assets/sprites/${item}.png`, - css: [ - [ - `./src/assets/sprites/_${item}.scss`, - { - format: 'handlebars_based_template' - } - ] - ] - }, - apiOptions: { - cssImageRef: `@/assets/sprites/${item}.png`, - spritesheet_info: { - name: item, - format: 'handlebars_based_template' - } - }, - customTemplates: { - handlebars_based_template: './scss.template.hbs' - }, - spritesmithOptions: { - algorithm: 'binary-tree', - padding: 10 - } - }) - ) - }) - return plugin -} diff --git a/vite/plugins/spritesmith.ts b/vite/plugins/spritesmith.ts new file mode 100644 index 00000000..5d5e28a8 --- /dev/null +++ b/vite/plugins/spritesmith.ts @@ -0,0 +1,48 @@ +import fs from 'fs' +import spritesmith from 'vite-plugin-spritesmith' + +export default function createSpritesmith(isBuild) { + const spriteDirnames: string[] = [] + fs.readdirSync('src/assets/sprites').forEach((dirname) => { + if (fs.statSync(`src/assets/sprites/${dirname}`).isDirectory()) + spriteDirnames.push(dirname) + }) + const plugin: any[] = [] + spriteDirnames.forEach((item) => { + plugin.push( + spritesmith({ + watch: !isBuild, + src: { + cwd: `./src/assets/sprites/${item}`, + glob: '*.png', + }, + target: { + image: `./src/assets/sprites/${item}.png`, + css: [ + [ + `./src/assets/sprites/_${item}.scss`, + { + format: 'handlebars_based_template', + }, + ], + ], + }, + apiOptions: { + cssImageRef: `@/assets/sprites/${item}.png`, + spritesheet_info: { + name: item, + format: 'handlebars_based_template', + }, + }, + customTemplates: { + handlebars_based_template: './scss.template.hbs', + }, + spritesmithOptions: { + algorithm: 'binary-tree', + padding: 10, + }, + }), + ) + }) + return plugin +} diff --git a/vite/plugins/svg-icon.js b/vite/plugins/svg-icon.js deleted file mode 100644 index dbd89b76..00000000 --- a/vite/plugins/svg-icon.js +++ /dev/null @@ -1,10 +0,0 @@ -import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' -import path from 'path' - -export default function createSvgIcon(isBuild) { - return createSvgIconsPlugin({ - iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/')], - symbolId: 'icon-[dir]-[name]', - svgoOptions: isBuild - }) -} diff --git a/vite/plugins/svg-icon.ts b/vite/plugins/svg-icon.ts new file mode 100644 index 00000000..99595fe0 --- /dev/null +++ b/vite/plugins/svg-icon.ts @@ -0,0 +1,10 @@ +import path from 'node:path' +import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' + +export default function createSvgIcon(isBuild) { + return createSvgIconsPlugin({ + iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/')], + symbolId: 'icon-[dir]-[name]', + svgoOptions: isBuild, + }) +}