mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-10-16 22:53:42 +08:00
Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
97c92626cc | ||
|
55ddc9cab0 | ||
|
401f0c748d | ||
|
d5c751153c | ||
|
69d51318ff | ||
|
de5fb84215 | ||
|
c275f2632c | ||
|
e899914426 | ||
|
861c8b9852 | ||
|
a782461453 | ||
|
e8488e4d52 | ||
|
889c859865 | ||
|
3c8dd772f8 | ||
|
251b5b9664 | ||
|
41e46a5d80 | ||
|
5c75e9d958 | ||
|
7f4350aeb6 | ||
|
807448aec5 | ||
|
b9c5c34979 | ||
|
20347b7d65 | ||
|
dbeb595c0b | ||
|
c9d3e5a3fd | ||
|
5e276421ad | ||
|
219f87f467 | ||
|
b35ed8960d | ||
|
24010d05fb | ||
|
ec0776e268 | ||
|
cecce83bc3 | ||
|
4eb46ea3dd | ||
|
e8b534b84e | ||
|
46e1ae7825 | ||
|
60a55a776e | ||
|
bed4292ed3 | ||
|
6bed9ead38 | ||
|
3fb13ca9e7 | ||
|
2d6d179d66 | ||
|
eebb753884 | ||
|
bb1bbf2724 | ||
|
df56abe18d | ||
|
ca2dfa6185 | ||
|
bbfdcc8276 | ||
|
1715504789 | ||
|
9a90f18e77 | ||
|
21645537d5 | ||
|
e6c26fcb4a | ||
|
20911dd882 | ||
|
cd7ca8f4c7 | ||
|
ca707a456b |
5
.env
5
.env
@@ -6,4 +6,7 @@ VITE_APP_TITLE=Soybean管理系统
|
|||||||
|
|
||||||
VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版
|
VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版
|
||||||
|
|
||||||
VITE_HTTP_PROXY=true
|
# 权限路由模式: static | dynamic
|
||||||
|
VITE_AUTH_ROUTE_MODE=dynamic
|
||||||
|
|
||||||
|
VITE_ROUTE_HOME_PATH=/dashboard/analysis
|
||||||
|
@@ -1,28 +1,20 @@
|
|||||||
/** 请求环境配置 */
|
/** 请求环境配置 */
|
||||||
type ServiceEnv = Record<
|
type ServiceEnv = Record<EnvType, EnvConfig>;
|
||||||
EnvType,
|
|
||||||
{
|
|
||||||
/** 请求地址 */
|
|
||||||
url: string;
|
|
||||||
/** 代理地址 */
|
|
||||||
proxy: string;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
/** 请求的环境 */
|
/** 环境配置 */
|
||||||
const serviceEnvConfig: ServiceEnv = {
|
const serviceEnvConfig: ServiceEnv = {
|
||||||
dev: {
|
dev: {
|
||||||
url: 'http://localhost:8080',
|
url: 'http://localhost:8080',
|
||||||
proxy: '/api',
|
proxy: '/api'
|
||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
url: 'http://localhost:8080',
|
url: 'http://localhost:8080',
|
||||||
proxy: '/api',
|
proxy: '/api'
|
||||||
},
|
},
|
||||||
prod: {
|
prod: {
|
||||||
url: 'http://localhost:8080',
|
url: 'http://localhost:8080',
|
||||||
proxy: '/api',
|
proxy: '/api'
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,8 +23,8 @@ const serviceEnvConfig: ServiceEnv = {
|
|||||||
*/
|
*/
|
||||||
export function getEnvConfig(env: ImportMetaEnv) {
|
export function getEnvConfig(env: ImportMetaEnv) {
|
||||||
const { VITE_ENV_TYPE = 'dev' } = env;
|
const { VITE_ENV_TYPE = 'dev' } = env;
|
||||||
const envConfig = {
|
|
||||||
http: serviceEnvConfig[VITE_ENV_TYPE],
|
const envConfig = serviceEnvConfig[VITE_ENV_TYPE];
|
||||||
};
|
|
||||||
return envConfig;
|
return envConfig;
|
||||||
}
|
}
|
||||||
|
@@ -1,2 +1 @@
|
|||||||
# 是否开启打包文件大小结果分析
|
VITE_HTTP_PROXY=true
|
||||||
VITE_VISUALIZER=false
|
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
VITE_VISUALIZER=false
|
||||||
|
|
||||||
|
VITE_COMPRESS=false
|
||||||
|
|
||||||
|
# gzip | brotliCompress | deflate | deflateRaw
|
||||||
|
VITE_COMPRESS_TYPE=gzip
|
||||||
|
@@ -13,3 +13,4 @@ lib
|
|||||||
.local
|
.local
|
||||||
package.json
|
package.json
|
||||||
!.env-config.ts
|
!.env-config.ts
|
||||||
|
components.d.ts
|
||||||
|
111
.eslintrc.js
111
.eslintrc.js
@@ -2,19 +2,19 @@ module.exports = {
|
|||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
es2021: true,
|
es2021: true,
|
||||||
'vue/setup-compiler-macros': true,
|
'vue/setup-compiler-macros': true
|
||||||
},
|
},
|
||||||
globals: {
|
globals: {
|
||||||
PROJECT_BUILD_TIME: 'readonly',
|
PROJECT_BUILD_TIME: 'readonly',
|
||||||
AMap: 'readonly',
|
AMap: 'readonly',
|
||||||
BMap: 'readonly',
|
BMap: 'readonly',
|
||||||
TMap: 'readonly',
|
TMap: 'readonly'
|
||||||
},
|
},
|
||||||
parser: 'vue-eslint-parser',
|
parser: 'vue-eslint-parser',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 12,
|
ecmaVersion: 12,
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
sourceType: 'module',
|
sourceType: 'module'
|
||||||
},
|
},
|
||||||
plugins: ['vue', '@typescript-eslint'],
|
plugins: ['vue', '@typescript-eslint'],
|
||||||
extends: [
|
extends: [
|
||||||
@@ -24,7 +24,7 @@ module.exports = {
|
|||||||
'plugin:prettier/recommended',
|
'plugin:prettier/recommended',
|
||||||
'@vue/eslint-config-typescript/recommended',
|
'@vue/eslint-config-typescript/recommended',
|
||||||
'@vue/eslint-config-prettier',
|
'@vue/eslint-config-prettier',
|
||||||
'@vue/typescript/recommended',
|
'@vue/typescript/recommended'
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'import/extensions': 'off',
|
'import/extensions': 'off',
|
||||||
@@ -38,118 +38,111 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
pattern: 'vue',
|
pattern: 'vue',
|
||||||
group: 'external',
|
group: 'external',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: 'vue-router',
|
pattern: 'vue-router',
|
||||||
group: 'external',
|
group: 'external',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: 'vuex',
|
pattern: 'vuex',
|
||||||
group: 'external',
|
group: 'external',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: 'pinia',
|
pattern: 'pinia',
|
||||||
group: 'external',
|
group: 'external',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: 'naive-ui',
|
||||||
|
group: 'external',
|
||||||
|
position: 'before'
|
||||||
},
|
},
|
||||||
// ui framework, such as "naive-ui"
|
|
||||||
// {
|
|
||||||
// pattern: 'naive-ui',
|
|
||||||
// group: 'external',
|
|
||||||
// position: 'before'
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
pattern: '@/config',
|
pattern: '@/config',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/settings',
|
pattern: '@/settings',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/enum',
|
pattern: '@/enum',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/plugins',
|
pattern: '@/plugins',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/layouts',
|
pattern: '@/layouts',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/views',
|
pattern: '@/views',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/components',
|
pattern: '@/components',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/router',
|
pattern: '@/router',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/store',
|
pattern: '@/store',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/composables',
|
pattern: '@/composables',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/hooks',
|
pattern: '@/hooks',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/service',
|
pattern: '@/service',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/utils',
|
pattern: '@/utils',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/assets',
|
pattern: '@/assets',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/**',
|
pattern: '@/**',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: '@/interface',
|
pattern: '@/interface',
|
||||||
group: 'internal',
|
group: 'internal',
|
||||||
position: 'before',
|
position: 'before'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
pathGroupsExcludedImportTypes: [
|
pathGroupsExcludedImportTypes: ['vue', 'vue-router', 'vuex', 'pinia', 'naive-ui']
|
||||||
'vue',
|
}
|
||||||
'vue-router',
|
|
||||||
'vuex',
|
|
||||||
'pinia',
|
|
||||||
// 'naive-ui'
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
'import/no-unresolved': 'off',
|
'import/no-unresolved': 'off',
|
||||||
'import/prefer-default-export': 'off',
|
'import/prefer-default-export': 'off',
|
||||||
@@ -158,8 +151,8 @@ module.exports = {
|
|||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
props: true,
|
props: true,
|
||||||
ignorePropertyModificationsFor: ['state', 'acc', 'e'],
|
ignorePropertyModificationsFor: ['state', 'acc', 'e']
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
'no-plusplus': 'off',
|
'no-plusplus': 'off',
|
||||||
'no-shadow': 'off',
|
'no-shadow': 'off',
|
||||||
@@ -168,44 +161,34 @@ module.exports = {
|
|||||||
'vue/multi-word-component-names': [
|
'vue/multi-word-component-names': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
ignores: ['index'],
|
ignores: ['index']
|
||||||
},
|
}
|
||||||
],
|
|
||||||
|
|
||||||
'@typescript-eslint/ban-types': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
types: {
|
|
||||||
'{}': {
|
|
||||||
message: 'Use object instead',
|
|
||||||
fixWith: 'object',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
'@typescript-eslint/ban-types': 'off',
|
||||||
'@typescript-eslint/no-empty-interface': [
|
'@typescript-eslint/no-empty-interface': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
allowSingleExtends: true,
|
allowSingleExtends: true
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
'@typescript-eslint/no-shadow': 'error',
|
'@typescript-eslint/no-shadow': 'error',
|
||||||
'@typescript-eslint/no-unused-vars': ['warn', { ignoreRestSiblings: true, varsIgnorePattern: '^_' }],
|
'@typescript-eslint/no-unused-vars': ['warn', { ignoreRestSiblings: true, varsIgnorePattern: '^_' }],
|
||||||
'@typescript-eslint/no-use-before-define': ['error', { classes: true, functions: false, typedefs: false }],
|
'@typescript-eslint/no-use-before-define': ['warn', { classes: true, functions: false, typedefs: false }]
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ['*.vue'],
|
files: ['*.vue'],
|
||||||
rules: {
|
rules: {
|
||||||
'no-undef': 'off',
|
'no-undef': 'off'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['*.html'],
|
files: ['*.html'],
|
||||||
rules: {
|
rules: {
|
||||||
'vue/comment-directive': 'off',
|
'vue/comment-directive': 'off'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
};
|
};
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
module.exports = { // https://prettier.io/docs/en/options.html
|
module.exports = { // https://prettier.io/docs/en/options.html
|
||||||
arrowParens: 'always',
|
arrowParens: 'avoid',
|
||||||
bracketSameLine: false,
|
bracketSameLine: false,
|
||||||
bracketSpacing: true,
|
bracketSpacing: true,
|
||||||
embeddedLanguageFormatting: 'auto',
|
embeddedLanguageFormatting: 'auto',
|
||||||
@@ -13,7 +13,7 @@ module.exports = { // https://prettier.io/docs/en/options.html
|
|||||||
semi: true,
|
semi: true,
|
||||||
singleQuote: true,
|
singleQuote: true,
|
||||||
tabWidth: 2,
|
tabWidth: 2,
|
||||||
trailingComma: 'es5',
|
trailingComma: 'none',
|
||||||
useTabs: false,
|
useTabs: false,
|
||||||
vueIndentScriptAndStyle: false,
|
vueIndentScriptAndStyle: false,
|
||||||
overrides: [
|
overrides: [
|
||||||
|
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@@ -31,6 +31,6 @@
|
|||||||
"johnsoncodehk.vscode-typescript-vue-plugin",
|
"johnsoncodehk.vscode-typescript-vue-plugin",
|
||||||
"dariofuzinato.vue-peek",
|
"dariofuzinato.vue-peek",
|
||||||
"wscats.vue",
|
"wscats.vue",
|
||||||
"voorjaar.windicss-intellisense"
|
"antfu.unocss"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@@ -33,7 +33,6 @@
|
|||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"javascript.updateImportsOnFileMove.enabled": "always",
|
|
||||||
"[javascriptreact]": {
|
"[javascriptreact]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
@@ -69,6 +68,9 @@
|
|||||||
"composables": "hook",
|
"composables": "hook",
|
||||||
"directive": "tools",
|
"directive": "tools",
|
||||||
"directives": "tools",
|
"directives": "tools",
|
||||||
"business": "core"
|
"business": "core",
|
||||||
}
|
"request": "api",
|
||||||
|
"adapter": "middleware"
|
||||||
|
},
|
||||||
|
"unocss.root": "src"
|
||||||
}
|
}
|
||||||
|
31
CHANGELOG.md
31
CHANGELOG.md
@@ -2,6 +2,37 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
### [0.9.4](https://github.com/honghuangdc/soybean-admin/compare/v0.9.3...v0.9.4) (2022-04-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **layouts:** 添加侧边栏/头部的反转模式来增加对比度 ([861c8b9](https://github.com/honghuangdc/soybean-admin/commit/861c8b9852e0097a1f6b79ac2c10d19add123bde))
|
||||||
|
* **layouts:** 添加侧边栏/头部的反转模式来增加对比度 ([3c8dd77](https://github.com/honghuangdc/soybean-admin/commit/3c8dd772f89d2b656a42c4f7164e581acdb2b1a5))
|
||||||
|
* **projects:** 插件方式按需引入naiveUI ([6bed9ea](https://github.com/honghuangdc/soybean-admin/commit/6bed9ead38af6d58f6cd9e520db848ae5cbfa4db))
|
||||||
|
* **projects:** 登录页背景图片位置适配移动端 ([24010d0](https://github.com/honghuangdc/soybean-admin/commit/24010d05fb1ff51cb5e5d94ffe310206a9638711))
|
||||||
|
* **projects:** 登录页面适配移动端 ([ec0776e](https://github.com/honghuangdc/soybean-admin/commit/ec0776e268cd3d1031e9ecd794abce271a675793))
|
||||||
|
* **projects:** 权限完善及权限示例页面 ([807448a](https://github.com/honghuangdc/soybean-admin/commit/807448aec5b041535fe4fbac90eca1138b2f439c))
|
||||||
|
* **projects:** 添加请求适配器的请求示例 ([bed4292](https://github.com/honghuangdc/soybean-admin/commit/bed4292ed380e77ac428ab057abc42eceb72af53))
|
||||||
|
* **projects:** 新增静态路由 ([ca2dfa6](https://github.com/honghuangdc/soybean-admin/commit/ca2dfa6185aa7a4e58184bcfef2a1246a52f88fd))
|
||||||
|
* **projects:** 引入unocss替换windicss ([c9d3e5a](https://github.com/honghuangdc/soybean-admin/commit/c9d3e5a3fdf59179dcfc122ab8369c492ea7832e))
|
||||||
|
* **projects:** HTML lang 修改为 zh-cmn-Hans ([b9c5c34](https://github.com/honghuangdc/soybean-admin/commit/b9c5c349790b1e83a7acd1f2c53a86c9221944ff))
|
||||||
|
* **projects:** HTML lang 修改为 zh-cmn-Hans ([dbeb595](https://github.com/honghuangdc/soybean-admin/commit/dbeb595c0b9fc11e7d166a7684af37cc971f1a11))
|
||||||
|
* **projects:** mock添加权限过滤 ([7f4350a](https://github.com/honghuangdc/soybean-admin/commit/7f4350aeb673dab59192584177a897aacebe4b28))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **projects:** 去除从环境文件引入端口号导致的错误 ([2d6d179](https://github.com/honghuangdc/soybean-admin/commit/2d6d179d669ea71cca3fe97ac840e4856bff4051))
|
||||||
|
* **projects:** 全局搜索弹窗弹出时动画闪屏问题 ([bb1bbf2](https://github.com/honghuangdc/soybean-admin/commit/bb1bbf272438f4ed440735118c6a9ec04c7d109f))
|
||||||
|
* **projects:** 添加.npmrc修复无法获取自动引入的全局组件声明类型 ([e8488e4](https://github.com/honghuangdc/soybean-admin/commit/e8488e4d5237e5e03ec07ff07d03115389d5b1ef))
|
||||||
|
* **projects:** 添加获取路由组件文件未找到时的错误提示 ([219f87f](https://github.com/honghuangdc/soybean-admin/commit/219f87f46758f328f26697f66d8583f49c0d41de))
|
||||||
|
* **projects:** 修复获取vite环境变量的方式 ([46e1ae7](https://github.com/honghuangdc/soybean-admin/commit/46e1ae7825b2b204ce3cdd63b3c64f39bff096d0))
|
||||||
|
* **projects:** 修复路由守卫的动态路由逻辑 ([e6c26fc](https://github.com/honghuangdc/soybean-admin/commit/e6c26fcb4ae085f9fd7d7eb9183ddba020d0b5da))
|
||||||
|
* **projects:** 修复样式 ([e899914](https://github.com/honghuangdc/soybean-admin/commit/e8999144266761b3b701442975c3c00251240d53))
|
||||||
|
* **projects:** 修复在新版vite下环境变量获取不到的问题 ([3fb13ca](https://github.com/honghuangdc/soybean-admin/commit/3fb13ca9e710549d2ddeb774fe08fabd27d5ae11))
|
||||||
|
* **projects:** 修复vite alias ([cd7ca8f](https://github.com/honghuangdc/soybean-admin/commit/cd7ca8f4c77ac8c753b753ba698a9573d6c37bf9))
|
||||||
|
|
||||||
### [0.9.3](https://github.com/honghuangdc/soybean-admin/compare/v0.9.2...v0.9.3) (2022-03-12)
|
### [0.9.3](https://github.com/honghuangdc/soybean-admin/compare/v0.9.2...v0.9.3) (2022-03-12)
|
||||||
|
|
||||||
|
|
||||||
|
35
README.md
35
README.md
@@ -13,10 +13,10 @@ Soybean Admin 是一个基于 Vue3、Vite、TypeScript、Naive UI 的免费中
|
|||||||
|
|
||||||
- **最新技术栈**:使用 Vue3/vite2 等前端前沿技术开发, 使用高效率的npm包管理器pnpm
|
- **最新技术栈**:使用 Vue3/vite2 等前端前沿技术开发, 使用高效率的npm包管理器pnpm
|
||||||
- **TypeScript**: 应用程序级 JavaScript 的语言
|
- **TypeScript**: 应用程序级 JavaScript 的语言
|
||||||
- **主题**:丰富可配置的主题、暗黑模式,基于windicss的动态主题颜色
|
- **主题**:丰富可配置的主题、暗黑模式,基于原子css - unocss的动态主题颜色
|
||||||
- **代码规范**:丰富的规范插件及极高的代码规范
|
- **代码规范**:丰富的规范插件及极高的代码规范
|
||||||
- **权限路由**:简易的路由配置、基于mock的动态路由能快速实现后端动态路由
|
- **权限路由**:简易的路由配置、基于mock的动态路由能快速实现后端动态路由
|
||||||
- **请求函数**:基于axios的完善的请求函数封装,提供Promise和hooks两种请求函数
|
- **请求函数**:基于axios的完善的请求函数封装,提供Promise和hooks两种请求函数,加入请求结果数据转换的适配器
|
||||||
|
|
||||||
## 预览
|
## 预览
|
||||||
|
|
||||||
@@ -50,16 +50,23 @@ Soybean Admin 是一个基于 Vue3、Vite、TypeScript、Naive UI 的免费中
|
|||||||
|
|
||||||
## 开发计划
|
## 开发计划
|
||||||
|
|
||||||
- [ ] 示例页面完善
|
- [x] 添加前端静态路由
|
||||||
|
- [x] 集成unocss替换windicss
|
||||||
|
- [x] 用户角色切换示例、按钮级别权限指令
|
||||||
|
- [ ] 引入ECharts替换AntV G2Plot
|
||||||
|
- [ ] 最近功能的有关文档更新
|
||||||
|
- [ ] 性能优化(优化递归函数)
|
||||||
|
- [ ] 精简版(新分支thin)
|
||||||
- [ ] 表单、表格示例
|
- [ ] 表单、表格示例
|
||||||
- [ ] 添加锁屏组件、全局Iframe组件
|
- [ ] 添加锁屏组件、全局Iframe组件
|
||||||
- [ ] 用户角色切换示例、按钮级别权限指令
|
- [ ] 示例页面完善
|
||||||
- [ ] 性能优化(优化递归函数)
|
|
||||||
- [ ] element-plus版本
|
|
||||||
- [ ] 其他UI版本
|
- [ ] 其他UI版本
|
||||||
|
- [ ] element-plus版本
|
||||||
- [ ] soybean-admin cli工具(选择不同UI)
|
- [ ] soybean-admin cli工具(选择不同UI)
|
||||||
- [ ] 前端可视化创建路由页面
|
- [ ] 前端可视化创建路由页面
|
||||||
- [ ] soybean-admin 后台nodejs服务
|
- [ ] soybean-admin 后台服务java版: [soybean-admin-java](https://github.com/honghuangdc/soybean-admin-java)
|
||||||
|
- [ ] soybean-admin 后台服务go版: [soybean-admin-go](https://github.com/honghuangdc/soybean-admin-go)
|
||||||
|
- [ ] soybean-admin 后台服务nodejs版: [soybean-admin-nestjs](https://github.com/honghuangdc/soybean-admin-nestjs)
|
||||||
|
|
||||||
## 安装使用
|
## 安装使用
|
||||||
|
|
||||||
@@ -87,6 +94,12 @@ pnpm dev
|
|||||||
pnpm build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
::: warning 注意
|
||||||
|
|
||||||
|
**本地环境需要安装 pnpm 6.x 、Node.js 14.x 和 Git**
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## 如何贡献
|
## 如何贡献
|
||||||
|
|
||||||
非常欢迎您的加入 或者提交一个 Pull Request。
|
非常欢迎您的加入 或者提交一个 Pull Request。
|
||||||
@@ -119,10 +132,10 @@ pnpm i -g commitizen
|
|||||||
|
|
||||||
`Soybean Admin` 是完全开源免费的项目,在帮助开发者更方便地进行中大型管理系统开发,同时也提供微信和QQ交流群,使用问题欢迎在群内提问。
|
`Soybean Admin` 是完全开源免费的项目,在帮助开发者更方便地进行中大型管理系统开发,同时也提供微信和QQ交流群,使用问题欢迎在群内提问。
|
||||||
|
|
||||||
|
- 本人微信号:honghuangdc,欢迎来技术交流,业务咨询。
|
||||||
|
|
||||||
- 微信交流群:
|
- 微信交流群:
|
||||||
<div style="text-align:left">
|
**微信群的人数已经满200个了,无法扫码,可以添加本人的微信再邀请进入**
|
||||||
<img src="https://s2.loli.net/2022/03/06/4wokvQ7R5B62Ei1.jpg" style="width:200px" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
- QQ交流群 `711301266`
|
- QQ交流群 `711301266`
|
||||||
|
|
||||||
@@ -130,8 +143,6 @@ pnpm i -g commitizen
|
|||||||
<img src="https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg" style="width:200px" />
|
<img src="https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg" style="width:200px" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
- 本人微信号:honghuangdc,欢迎来技术交流,业务咨询。
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT © Soybean-2021](./LICENSE)
|
[MIT © Soybean-2021](./LICENSE)
|
||||||
|
@@ -3,6 +3,6 @@ import dayjs from 'dayjs';
|
|||||||
/** 项目构建时间 */
|
/** 项目构建时间 */
|
||||||
const PROJECT_BUILD_TIME = JSON.stringify(dayjs().format('YYYY-MM-DD HH:mm:ss'));
|
const PROJECT_BUILD_TIME = JSON.stringify(dayjs().format('YYYY-MM-DD HH:mm:ss'));
|
||||||
|
|
||||||
export const define = {
|
export const viteDefine = {
|
||||||
PROJECT_BUILD_TIME,
|
PROJECT_BUILD_TIME
|
||||||
};
|
};
|
2
build/config/index.ts
Normal file
2
build/config/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './define';
|
||||||
|
export * from './proxy';
|
20
build/config/proxy.ts
Normal file
20
build/config/proxy.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { ProxyOptions } from 'vite';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置网络代理
|
||||||
|
* @param isOpenProxy - 是否开启代理
|
||||||
|
* @param envConfig - env环境配置
|
||||||
|
*/
|
||||||
|
export function createViteProxy(isOpenProxy: boolean, envConfig: EnvConfig) {
|
||||||
|
if (!isOpenProxy) return undefined;
|
||||||
|
|
||||||
|
const proxy: Record<string, string | ProxyOptions> = {
|
||||||
|
[envConfig.proxy]: {
|
||||||
|
target: envConfig.url,
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: path => path.replace(new RegExp(`^${envConfig.proxy}`), '')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
}
|
@@ -1,2 +1,2 @@
|
|||||||
export * from './plugins';
|
export * from './plugins';
|
||||||
export * from './define';
|
export * from './config';
|
||||||
|
@@ -1,21 +0,0 @@
|
|||||||
import Icons from 'unplugin-icons/vite';
|
|
||||||
import IconsResolver from 'unplugin-icons/resolver';
|
|
||||||
import Components from 'unplugin-vue-components/vite';
|
|
||||||
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
|
|
||||||
|
|
||||||
export default (srcPath: string) => {
|
|
||||||
return [
|
|
||||||
Icons({
|
|
||||||
compiler: 'vue3',
|
|
||||||
customCollections: {
|
|
||||||
custom: FileSystemIconLoader(`${srcPath}/assets/svg`),
|
|
||||||
},
|
|
||||||
scale: 1,
|
|
||||||
defaultClass: 'inline-block',
|
|
||||||
}),
|
|
||||||
Components({
|
|
||||||
dts: true,
|
|
||||||
resolvers: [IconsResolver({ customCollections: ['custom'], componentPrefix: 'icon' })],
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
};
|
|
6
build/plugins/compress.ts
Normal file
6
build/plugins/compress.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import ViteCompression from 'vite-plugin-compression';
|
||||||
|
|
||||||
|
export default (viteEnv: ImportMetaEnv) => {
|
||||||
|
const { VITE_COMPRESS_TYPE = 'gzip' } = viteEnv;
|
||||||
|
return ViteCompression({ algorithm: VITE_COMPRESS_TYPE });
|
||||||
|
};
|
@@ -1,17 +1,14 @@
|
|||||||
import { loadEnv } from 'vite';
|
import type { PluginOption } from 'vite';
|
||||||
import type { ConfigEnv, PluginOption } from 'vite';
|
import { createHtmlPlugin } from 'vite-plugin-html';
|
||||||
import { createHtmlPlugin } from 'vite-plugin-html'; // html插件(使用变量、压缩)
|
|
||||||
|
|
||||||
export default (config: ConfigEnv): PluginOption[] => {
|
|
||||||
const viteEnv = loadEnv(config.mode, `.env.${config.mode}`);
|
|
||||||
|
|
||||||
|
export default (viteEnv: ImportMetaEnv): PluginOption[] => {
|
||||||
return createHtmlPlugin({
|
return createHtmlPlugin({
|
||||||
minify: true,
|
minify: true,
|
||||||
inject: {
|
inject: {
|
||||||
data: {
|
data: {
|
||||||
appName: viteEnv.VITE_APP_NAME,
|
appName: viteEnv.VITE_APP_NAME,
|
||||||
appTitle: viteEnv.VITE_APP_TITLE,
|
appTitle: viteEnv.VITE_APP_TITLE
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@@ -1,27 +1,26 @@
|
|||||||
import type { ConfigEnv, PluginOption } from 'vite';
|
import type { PluginOption } from 'vite';
|
||||||
import vue from './vue';
|
import vue from './vue';
|
||||||
import html from './html';
|
import html from './html';
|
||||||
import autoImport from './auto-import';
|
import unplugin from './unplugin';
|
||||||
import windicss from './windicss';
|
import unocss from './unocss';
|
||||||
import mock from './mock';
|
import mock from './mock';
|
||||||
import visualizer from './visualizer';
|
import visualizer from './visualizer';
|
||||||
|
import compress from './compress';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* vite插件
|
* vite插件
|
||||||
* @param configEnv - 环境
|
|
||||||
* @param srcPath - src路径
|
|
||||||
* @param viteEnv - 环境变量配置
|
* @param viteEnv - 环境变量配置
|
||||||
|
* @param srcPath - src路径
|
||||||
*/
|
*/
|
||||||
export function setupVitePlugins(
|
export function setupVitePlugins(viteEnv: ImportMetaEnv, srcPath: string): (PluginOption | PluginOption[])[] {
|
||||||
configEnv: ConfigEnv,
|
const plugins = [...vue, html(viteEnv), ...unplugin(srcPath), unocss, mock];
|
||||||
srcPath: string,
|
|
||||||
viteEnv: ImportMetaEnv
|
|
||||||
): (PluginOption | PluginOption[])[] {
|
|
||||||
const plugins = [vue, html(configEnv), ...autoImport(srcPath), windicss, mock];
|
|
||||||
|
|
||||||
if (configEnv.command === 'build' && viteEnv.VITE_VISUALIZER === 'true') {
|
if (viteEnv.VITE_VISUALIZER === 'true') {
|
||||||
plugins.push(visualizer);
|
plugins.push(visualizer);
|
||||||
}
|
}
|
||||||
|
if (viteEnv.VITE_COMPRESS === 'true') {
|
||||||
|
plugins.push(compress(viteEnv));
|
||||||
|
}
|
||||||
|
|
||||||
return plugins;
|
return plugins;
|
||||||
}
|
}
|
||||||
|
@@ -5,5 +5,5 @@ export default viteMockServe({
|
|||||||
injectCode: `
|
injectCode: `
|
||||||
import { setupMockServer } from '../mock';
|
import { setupMockServer } from '../mock';
|
||||||
setupMockServer();
|
setupMockServer();
|
||||||
`,
|
`
|
||||||
});
|
});
|
||||||
|
3
build/plugins/unocss.ts
Normal file
3
build/plugins/unocss.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import unocss from 'unocss/vite';
|
||||||
|
|
||||||
|
export default unocss();
|
25
build/plugins/unplugin.ts
Normal file
25
build/plugins/unplugin.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import DefineOptions from 'unplugin-vue-define-options/vite';
|
||||||
|
import Icons from 'unplugin-icons/vite';
|
||||||
|
import IconsResolver from 'unplugin-icons/resolver';
|
||||||
|
import Components from 'unplugin-vue-components/vite';
|
||||||
|
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
||||||
|
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
|
||||||
|
|
||||||
|
export default (srcPath: string) => {
|
||||||
|
return [
|
||||||
|
DefineOptions(),
|
||||||
|
Icons({
|
||||||
|
compiler: 'vue3',
|
||||||
|
customCollections: {
|
||||||
|
custom: FileSystemIconLoader(`${srcPath}/assets/svg`)
|
||||||
|
},
|
||||||
|
scale: 1,
|
||||||
|
defaultClass: 'inline-block'
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
dts: 'src/typings/components.d.ts',
|
||||||
|
types: [{ from: 'vue-router', names: ['RouterLink', 'RouterView'] }],
|
||||||
|
resolvers: [NaiveUiResolver(), IconsResolver({ customCollections: ['custom'], componentPrefix: 'icon' })]
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
@@ -3,4 +3,5 @@ import { visualizer } from 'rollup-plugin-visualizer';
|
|||||||
export default visualizer({
|
export default visualizer({
|
||||||
gzipSize: true,
|
gzipSize: true,
|
||||||
brotliSize: true,
|
brotliSize: true,
|
||||||
|
open: true
|
||||||
});
|
});
|
||||||
|
@@ -1,3 +1,6 @@
|
|||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||||
|
|
||||||
export default vue({});
|
const plugins = [vue(), vueJsx()];
|
||||||
|
|
||||||
|
export default plugins;
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
import windiCSS from 'vite-plugin-windicss';
|
|
||||||
|
|
||||||
export default windiCSS();
|
|
48
components.d.ts
vendored
48
components.d.ts
vendored
@@ -1,48 +0,0 @@
|
|||||||
// generated by unplugin-vue-components
|
|
||||||
// We suggest you to commit this file into source control
|
|
||||||
// Read more: https://github.com/vuejs/vue-next/pull/3399
|
|
||||||
|
|
||||||
declare module 'vue' {
|
|
||||||
export interface GlobalComponents {
|
|
||||||
BetterScroll: typeof import('./src/components/custom/BetterScroll.vue')['default'];
|
|
||||||
CountTo: typeof import('./src/components/custom/CountTo.vue')['default'];
|
|
||||||
DarkModeContainer: typeof import('./src/components/common/DarkModeContainer.vue')['default'];
|
|
||||||
DarkModeSwitch: typeof import('./src/components/common/DarkModeSwitch.vue')['default'];
|
|
||||||
GithubLink: typeof import('./src/components/custom/GithubLink.vue')['default'];
|
|
||||||
HoverContainer: typeof import('./src/components/common/HoverContainer.vue')['default'];
|
|
||||||
IconAntDesignCloseOutlined: typeof import('~icons/ant-design/close-outlined')['default'];
|
|
||||||
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default'];
|
|
||||||
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default'];
|
|
||||||
IconCustomActivity: typeof import('~icons/custom/activity')['default'];
|
|
||||||
IconCustomAvatar: typeof import('~icons/custom/avatar')['default'];
|
|
||||||
IconCustomCast: typeof import('~icons/custom/cast')['default'];
|
|
||||||
IconCustomLogo: typeof import('~icons/custom/logo')['default'];
|
|
||||||
IconCustomLogoFill: typeof import('~icons/custom/logo-fill')['default'];
|
|
||||||
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default'];
|
|
||||||
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default'];
|
|
||||||
IconIcOutlineCheck: typeof import('~icons/ic/outline-check')['default'];
|
|
||||||
IconLineMdMenuFoldLeft: typeof import('~icons/line-md/menu-fold-left')['default'];
|
|
||||||
IconLineMdMenuUnfoldLeft: typeof import('~icons/line-md/menu-unfold-left')['default'];
|
|
||||||
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default'];
|
|
||||||
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default'];
|
|
||||||
IconMdiClose: typeof import('~icons/mdi/close')['default'];
|
|
||||||
IconMdiGithub: typeof import('~icons/mdi/github')['default'];
|
|
||||||
IconMdiMoonWaningCrescent: typeof import('~icons/mdi/moon-waning-crescent')['default'];
|
|
||||||
IconMdiPin: typeof import('~icons/mdi/pin')['default'];
|
|
||||||
IconMdiPinOff: typeof import('~icons/mdi/pin-off')['default'];
|
|
||||||
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default'];
|
|
||||||
IconMdiWhiteBalanceSunny: typeof import('~icons/mdi/white-balance-sunny')['default'];
|
|
||||||
IconPhCaretDoubleLeftBold: typeof import('~icons/ph/caret-double-left-bold')['default'];
|
|
||||||
IconPhCaretDoubleRightBold: typeof import('~icons/ph/caret-double-right-bold')['default'];
|
|
||||||
IconSelect: typeof import('./src/components/custom/IconSelect.vue')['default'];
|
|
||||||
IconUilSearch: typeof import('~icons/uil/search')['default'];
|
|
||||||
ImageVerify: typeof import('./src/components/custom/ImageVerify.vue')['default'];
|
|
||||||
LoadingEmptyWrapper: typeof import('./src/components/business/LoadingEmptyWrapper.vue')['default'];
|
|
||||||
LoginAgreement: typeof import('./src/components/business/LoginAgreement.vue')['default'];
|
|
||||||
NaiveProvider: typeof import('./src/components/common/NaiveProvider.vue')['default'];
|
|
||||||
SystemLogo: typeof import('./src/components/common/SystemLogo.vue')['default'];
|
|
||||||
WebSiteLink: typeof import('./src/components/custom/WebSiteLink.vue')['default'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {};
|
|
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="zh-cmn-Hans">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<div class="right-0 bottom-0 loading-spin-item loading-delay-1500"></div>
|
<div class="right-0 bottom-0 loading-spin-item loading-delay-1500"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="loading-title"><%= appTitle %></h2>
|
<div class="loading-title"><%= appTitle %></div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/resource/loading.js"></script>
|
<script src="/resource/loading.js"></script>
|
||||||
</div>
|
</div>
|
||||||
|
161
mock/api/auth.ts
161
mock/api/auth.ts
@@ -1,9 +1,10 @@
|
|||||||
import type { MockMethod } from 'vite-plugin-mock';
|
import type { MockMethod } from 'vite-plugin-mock';
|
||||||
|
import { userModel } from '../model';
|
||||||
|
|
||||||
const token: ApiAuth.Token = {
|
/** 参数错误的状态码 */
|
||||||
token: '__TEMP_TOKEN__',
|
const ERROR_PARAM_CODE = 10000;
|
||||||
refreshToken: '__TEMP_REFRESH_TOKEN__',
|
|
||||||
};
|
const ERROR_PARAM_MSG = '参数校验失败!';
|
||||||
|
|
||||||
const apis: MockMethod[] = [
|
const apis: MockMethod[] = [
|
||||||
// 获取验证码
|
// 获取验证码
|
||||||
@@ -14,80 +15,114 @@ const apis: MockMethod[] = [
|
|||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
message: 'ok',
|
message: 'ok',
|
||||||
data: true,
|
data: true
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
// 密码登录
|
// 用户+密码 登录
|
||||||
{
|
{
|
||||||
url: '/mock/loginByPwd',
|
url: '/mock/login',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
response: (): Service.MockServiceResult<ApiAuth.Token> => {
|
response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.Token | null> => {
|
||||||
return {
|
const { userName = undefined, password = undefined } = options.body;
|
||||||
code: 200,
|
|
||||||
message: 'ok',
|
if (!userName || !password) {
|
||||||
data: token,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// 验证码登录
|
|
||||||
{
|
|
||||||
url: '/mock/loginByCode',
|
|
||||||
method: 'post',
|
|
||||||
response: (): Service.MockServiceResult<ApiAuth.Token> => {
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'ok',
|
|
||||||
data: token,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// 获取用户信息(请求头携带token)
|
|
||||||
{
|
|
||||||
url: '/mock/getUserInfo',
|
|
||||||
method: 'get',
|
|
||||||
response: (): Service.MockServiceResult<ApiAuth.UserInfo> => {
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'ok',
|
|
||||||
data: {
|
|
||||||
userId: '0',
|
|
||||||
userName: 'Soybean',
|
|
||||||
userPhone: '15170283876',
|
|
||||||
userRole: 'super',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/mock/testToken',
|
|
||||||
method: 'post',
|
|
||||||
response: (option: any): Service.MockServiceResult<true | null> => {
|
|
||||||
if (option.headers?.authorization !== token.token) {
|
|
||||||
return {
|
return {
|
||||||
code: 66666,
|
code: ERROR_PARAM_CODE,
|
||||||
message: 'token 失效',
|
message: ERROR_PARAM_MSG,
|
||||||
data: null,
|
data: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const findItem = userModel.find(item => item.userName === userName && item.password === password);
|
||||||
|
|
||||||
|
if (findItem) {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'ok',
|
||||||
|
data: {
|
||||||
|
token: findItem.token,
|
||||||
|
refreshToken: findItem.refreshToken
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 1000,
|
||||||
message: 'ok',
|
message: '用户名或密码错误!',
|
||||||
data: true,
|
data: null
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
|
},
|
||||||
|
// 获取用户信息(请求头携带token, 根据token获取用户信息)
|
||||||
|
{
|
||||||
|
url: '/mock/getUserInfo',
|
||||||
|
method: 'get',
|
||||||
|
response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.UserInfo | null> => {
|
||||||
|
// 这里的mock插件得到的字段是authorization, 前端传递的是Authorization字段
|
||||||
|
const { authorization = '' } = options.headers;
|
||||||
|
const REFRESH_TOKEN_CODE = 66666;
|
||||||
|
|
||||||
|
if (!authorization) {
|
||||||
|
return {
|
||||||
|
code: REFRESH_TOKEN_CODE,
|
||||||
|
message: '用户已失效或不存在!',
|
||||||
|
data: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const userInfo: Auth.UserInfo = {
|
||||||
|
userId: '',
|
||||||
|
userName: '',
|
||||||
|
userRole: 'user'
|
||||||
|
};
|
||||||
|
const isInUser = userModel.some(item => {
|
||||||
|
const flag = item.token === authorization;
|
||||||
|
if (flag) {
|
||||||
|
const { userId: itemUserId, userName, userRole } = item;
|
||||||
|
Object.assign(userInfo, { userId: itemUserId, userName, userRole });
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isInUser) {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'ok',
|
||||||
|
data: userInfo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: REFRESH_TOKEN_CODE,
|
||||||
|
message: '用户信息异常!',
|
||||||
|
data: null
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: '/mock/updateToken',
|
url: '/mock/updateToken',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
response: (): Service.MockServiceResult<ApiAuth.Token> => {
|
response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.Token | null> => {
|
||||||
|
const { refreshToken = '' } = options.body;
|
||||||
|
|
||||||
|
const findItem = userModel.find(item => item.refreshToken === refreshToken);
|
||||||
|
|
||||||
|
if (findItem) {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'ok',
|
||||||
|
data: {
|
||||||
|
token: findItem.token,
|
||||||
|
refreshToken: findItem.refreshToken
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 3000,
|
||||||
message: 'ok',
|
message: '用户已失效或不存在!',
|
||||||
data: token,
|
data: null
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export default apis;
|
export default apis;
|
||||||
|
20
mock/api/demo.ts
Normal file
20
mock/api/demo.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { MockMethod } from 'vite-plugin-mock';
|
||||||
|
|
||||||
|
const apis: MockMethod[] = [
|
||||||
|
{
|
||||||
|
url: '/mock/apiDemoWithAdapter',
|
||||||
|
method: 'post',
|
||||||
|
response: (): Service.MockServiceResult<ApiDemo.DataWithAdapter> => {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'ok',
|
||||||
|
data: {
|
||||||
|
dataId: '123',
|
||||||
|
dataName: 'demoName'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default apis;
|
@@ -1,382 +1,29 @@
|
|||||||
import type { MockMethod } from 'vite-plugin-mock';
|
import type { MockMethod } from 'vite-plugin-mock';
|
||||||
|
import { userModel, routeModel } from '../model';
|
||||||
const routes: AuthRoute.Route[] = [
|
|
||||||
{
|
|
||||||
name: 'dashboard',
|
|
||||||
path: '/dashboard',
|
|
||||||
component: 'basic',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'dashboard_analysis',
|
|
||||||
path: '/dashboard/analysis',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '分析页',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'icon-park-outline:analysis',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dashboard_workbench',
|
|
||||||
path: '/dashboard/workbench',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '工作台',
|
|
||||||
requiresAuth: true,
|
|
||||||
permissions: ['super', 'admin'],
|
|
||||||
icon: 'icon-park-outline:workbench',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
meta: {
|
|
||||||
title: '仪表盘',
|
|
||||||
icon: 'carbon:dashboard',
|
|
||||||
order: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'document',
|
|
||||||
path: '/document',
|
|
||||||
component: 'basic',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'document_vue',
|
|
||||||
path: '/document/vue',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: 'vue文档',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'mdi:vuejs',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'document_vue-new',
|
|
||||||
path: '/document/vue-new',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: 'vue文档(新版)',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'mdi:vuejs',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'document_vite',
|
|
||||||
path: '/document/vite',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: 'vite文档',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'simple-icons:vite',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'document_naive',
|
|
||||||
path: '/document/naive',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: 'naive文档',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'mdi:alpha-n-box-outline',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'document_project',
|
|
||||||
path: '/document/project',
|
|
||||||
meta: {
|
|
||||||
title: '项目文档(外链)',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'mdi:file-link-outline',
|
|
||||||
href: 'https://docs.soybean.pro/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
meta: {
|
|
||||||
title: '文档',
|
|
||||||
icon: 'carbon:document',
|
|
||||||
order: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'component',
|
|
||||||
path: '/component',
|
|
||||||
component: 'basic',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'component_button',
|
|
||||||
path: '/component/button',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '按钮',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'ic:baseline-radio-button-checked',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'component_card',
|
|
||||||
path: '/component/card',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '卡片',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'mdi:card-outline',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'component_table',
|
|
||||||
path: '/component/table',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '表格',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'mdi:table-large',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
meta: {
|
|
||||||
title: '组件示例',
|
|
||||||
icon: 'fluent:app-store-24-regular',
|
|
||||||
order: 3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin',
|
|
||||||
path: '/plugin',
|
|
||||||
component: 'basic',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'plugin_map',
|
|
||||||
path: '/plugin/map',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '地图',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'mdi:map',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_video',
|
|
||||||
path: '/plugin/video',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '视频',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'mdi:video',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_editor',
|
|
||||||
path: '/plugin/editor',
|
|
||||||
component: 'multi',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'plugin_editor_quill',
|
|
||||||
path: '/plugin/editor/quill',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '富文本编辑器',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'mdi:file-document-edit-outline',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_editor_markdown',
|
|
||||||
path: '/plugin/editor/markdown',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: 'markdown编辑器',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'ri:markdown-line',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
meta: {
|
|
||||||
title: '编辑器',
|
|
||||||
icon: 'icon-park-outline:editor',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_swiper',
|
|
||||||
path: '/plugin/swiper',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: 'Swiper插件',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'simple-icons:swiper',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_copy',
|
|
||||||
path: '/plugin/copy',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '剪贴板',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'mdi:clipboard-outline',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_icon',
|
|
||||||
path: '/plugin/icon',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '图标',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'ic:baseline-insert-emoticon',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_print',
|
|
||||||
path: '/plugin/print',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '打印',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'ic:baseline-local-printshop',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
meta: {
|
|
||||||
title: '插件示例',
|
|
||||||
icon: 'clarity:plugin-line',
|
|
||||||
order: 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'exception',
|
|
||||||
path: '/exception',
|
|
||||||
component: 'basic',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'exception_403',
|
|
||||||
path: '/exception/403',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '异常页403',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'ic:baseline-block',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'exception_404',
|
|
||||||
path: '/exception/404',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '异常页404',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'ic:baseline-web-asset-off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'exception_500',
|
|
||||||
path: '/exception/500',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '异常页500',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'ic:baseline-wifi-off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
meta: {
|
|
||||||
title: '异常页',
|
|
||||||
icon: 'ant-design:exception-outlined',
|
|
||||||
order: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'multi-menu',
|
|
||||||
path: '/multi-menu',
|
|
||||||
component: 'basic',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'multi-menu_first',
|
|
||||||
path: '/multi-menu/first',
|
|
||||||
component: 'multi',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'multi-menu_first_second',
|
|
||||||
path: '/multi-menu/first/second',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '二级菜单',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'ic:outline-menu',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'multi-menu_first_second-new',
|
|
||||||
path: '/multi-menu/first/second-new',
|
|
||||||
component: 'multi',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'multi-menu_first_second-new_third',
|
|
||||||
path: '/multi-menu/first/second-new/third',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '三级菜单',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'ic:outline-menu',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
meta: {
|
|
||||||
title: '二级菜单(有子菜单)',
|
|
||||||
icon: 'ic:outline-menu',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
meta: {
|
|
||||||
title: '一级菜单',
|
|
||||||
icon: 'ic:outline-menu',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
meta: {
|
|
||||||
title: '多级菜单',
|
|
||||||
icon: 'carbon:menu',
|
|
||||||
order: 6,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'about',
|
|
||||||
path: '/about',
|
|
||||||
component: 'self',
|
|
||||||
meta: {
|
|
||||||
title: '关于',
|
|
||||||
requiresAuth: true,
|
|
||||||
singleLayout: 'basic',
|
|
||||||
permissions: ['super', 'admin', 'test'],
|
|
||||||
icon: 'fluent:book-information-24-regular',
|
|
||||||
order: 7,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function dataMiddleware(data: AuthRoute.Route[]): ApiRoute.Route {
|
|
||||||
const routeHomeName: AuthRoute.RouteKey = 'dashboard_analysis';
|
|
||||||
|
|
||||||
function sortRoutes(sorts: AuthRoute.Route[]) {
|
|
||||||
return sorts.sort((next, pre) => Number(next.meta?.order) - Number(pre.meta?.order));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
routes: sortRoutes(data),
|
|
||||||
home: routeHomeName,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const apis: MockMethod[] = [
|
const apis: MockMethod[] = [
|
||||||
{
|
{
|
||||||
url: '/mock/getUserRoutes',
|
url: '/mock/getUserRoutes',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
response: (): Service.MockServiceResult => {
|
response: (options: Service.MockOption): Service.MockServiceResult => {
|
||||||
|
const { userId = undefined } = options.body;
|
||||||
|
|
||||||
|
const routeHomeName: AuthRoute.RouteKey = 'dashboard_analysis';
|
||||||
|
|
||||||
|
const role = userModel.find(item => item.userId === userId)?.userRole || 'user';
|
||||||
|
|
||||||
|
const filterRoutes = routeModel[role];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
message: 'ok',
|
message: 'ok',
|
||||||
data: dataMiddleware(routes),
|
data: {
|
||||||
|
routes: filterRoutes,
|
||||||
|
home: routeHomeName
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export default apis;
|
export default apis;
|
||||||
|
40
mock/model/auth.ts
Normal file
40
mock/model/auth.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
interface UserModel extends Auth.UserInfo {
|
||||||
|
token: string;
|
||||||
|
refreshToken: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const userModel: UserModel[] = [
|
||||||
|
{
|
||||||
|
token: '__TOKEN_SOYBEAN__',
|
||||||
|
refreshToken: '__REFRESH_TOKEN_SOYBEAN__',
|
||||||
|
userId: '0',
|
||||||
|
userName: 'Soybean',
|
||||||
|
userRole: 'super',
|
||||||
|
password: 'soybean123'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
token: '__TOKEN_SUPER__',
|
||||||
|
refreshToken: '__REFRESH_TOKEN_SUPER__',
|
||||||
|
userId: '1',
|
||||||
|
userName: 'Super',
|
||||||
|
userRole: 'super',
|
||||||
|
password: 'super123'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
token: '__TOKEN_ADMIN__',
|
||||||
|
refreshToken: '__REFRESH_TOKEN_ADMIN__',
|
||||||
|
userId: '2',
|
||||||
|
userName: 'Admin',
|
||||||
|
userRole: 'admin',
|
||||||
|
password: 'admin123'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
token: '__TOKEN_USER01__',
|
||||||
|
refreshToken: '__REFRESH_TOKEN_USER01__',
|
||||||
|
userId: '3',
|
||||||
|
userName: 'User01',
|
||||||
|
userRole: 'user',
|
||||||
|
password: 'user01123'
|
||||||
|
}
|
||||||
|
];
|
2
mock/model/index.ts
Normal file
2
mock/model/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './auth';
|
||||||
|
export * from './route';
|
846
mock/model/route.ts
Normal file
846
mock/model/route.ts
Normal file
@@ -0,0 +1,846 @@
|
|||||||
|
export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
|
||||||
|
super: [
|
||||||
|
{
|
||||||
|
name: 'dashboard',
|
||||||
|
path: '/dashboard',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'dashboard_analysis',
|
||||||
|
path: '/dashboard/analysis',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '分析页',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'icon-park-outline:analysis'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dashboard_workbench',
|
||||||
|
path: '/dashboard/workbench',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '工作台',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'icon-park-outline:workbench'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '仪表盘',
|
||||||
|
icon: 'carbon:dashboard',
|
||||||
|
order: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'document',
|
||||||
|
path: '/document',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'document_vue',
|
||||||
|
path: '/document/vue',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: 'vue文档',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:vuejs'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'document_vue-new',
|
||||||
|
path: '/document/vue-new',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: 'vue文档(新版)',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:vuejs'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'document_vite',
|
||||||
|
path: '/document/vite',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: 'vite文档',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'simple-icons:vite'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'document_project',
|
||||||
|
path: '/document/project',
|
||||||
|
meta: {
|
||||||
|
title: '项目文档(外链)',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:file-link-outline',
|
||||||
|
href: 'https://docs.soybean.pro/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '文档',
|
||||||
|
icon: 'carbon:document',
|
||||||
|
order: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'component',
|
||||||
|
path: '/component',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'component_button',
|
||||||
|
path: '/component/button',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '按钮',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:baseline-radio-button-checked'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'component_card',
|
||||||
|
path: '/component/card',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '卡片',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:card-outline'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'component_table',
|
||||||
|
path: '/component/table',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '表格',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:table-large'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '组件示例',
|
||||||
|
icon: 'fluent:app-store-24-regular',
|
||||||
|
order: 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin',
|
||||||
|
path: '/plugin',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'plugin_map',
|
||||||
|
path: '/plugin/map',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '地图',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:map'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin_video',
|
||||||
|
path: '/plugin/video',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '视频',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:video'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin_editor',
|
||||||
|
path: '/plugin/editor',
|
||||||
|
component: 'multi',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'plugin_editor_quill',
|
||||||
|
path: '/plugin/editor/quill',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '富文本编辑器',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:file-document-edit-outline'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin_editor_markdown',
|
||||||
|
path: '/plugin/editor/markdown',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: 'markdown编辑器',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ri:markdown-line'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '编辑器',
|
||||||
|
icon: 'icon-park-outline:editor'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin_swiper',
|
||||||
|
path: '/plugin/swiper',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: 'Swiper插件',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'simple-icons:swiper'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin_copy',
|
||||||
|
path: '/plugin/copy',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '剪贴板',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:clipboard-outline'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin_icon',
|
||||||
|
path: '/plugin/icon',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '图标',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:baseline-insert-emoticon'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin_print',
|
||||||
|
path: '/plugin/print',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '打印',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:baseline-local-printshop'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '插件示例',
|
||||||
|
icon: 'clarity:plugin-line',
|
||||||
|
order: 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'auth-demo',
|
||||||
|
path: '/auth-demo',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'auth-demo_permission',
|
||||||
|
path: '/auth-demo/permission',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '权限切换',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:round-construction'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'auth-demo_super',
|
||||||
|
path: '/auth-demo/super',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '超级管理员可见',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:round-supervisor-account'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '权限示例',
|
||||||
|
icon: 'ic:baseline-security',
|
||||||
|
order: 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'exception',
|
||||||
|
path: '/exception',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'exception_403',
|
||||||
|
path: '/exception/403',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '异常页403',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:baseline-block'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'exception_404',
|
||||||
|
path: '/exception/404',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '异常页404',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:baseline-web-asset-off'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'exception_500',
|
||||||
|
path: '/exception/500',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '异常页500',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:baseline-wifi-off'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '异常页',
|
||||||
|
icon: 'ant-design:exception-outlined',
|
||||||
|
order: 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'multi-menu',
|
||||||
|
path: '/multi-menu',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'multi-menu_first',
|
||||||
|
path: '/multi-menu/first',
|
||||||
|
component: 'multi',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'multi-menu_first_second',
|
||||||
|
path: '/multi-menu/first/second',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '二级菜单',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:outline-menu'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'multi-menu_first_second-new',
|
||||||
|
path: '/multi-menu/first/second-new',
|
||||||
|
component: 'multi',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'multi-menu_first_second-new_third',
|
||||||
|
path: '/multi-menu/first/second-new/third',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '三级菜单',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:outline-menu'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '二级菜单(有子菜单)',
|
||||||
|
icon: 'ic:outline-menu'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '一级菜单',
|
||||||
|
icon: 'ic:outline-menu'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '多级菜单',
|
||||||
|
icon: 'carbon:menu',
|
||||||
|
order: 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'about',
|
||||||
|
path: '/about',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '关于',
|
||||||
|
requiresAuth: true,
|
||||||
|
singleLayout: 'basic',
|
||||||
|
icon: 'fluent:book-information-24-regular',
|
||||||
|
order: 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
admin: [
|
||||||
|
{
|
||||||
|
name: 'dashboard',
|
||||||
|
path: '/dashboard',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'dashboard_analysis',
|
||||||
|
path: '/dashboard/analysis',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '分析页',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'icon-park-outline:analysis'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dashboard_workbench',
|
||||||
|
path: '/dashboard/workbench',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '工作台',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'icon-park-outline:workbench'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '仪表盘',
|
||||||
|
icon: 'carbon:dashboard',
|
||||||
|
order: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'document',
|
||||||
|
path: '/document',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'document_vue',
|
||||||
|
path: '/document/vue',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: 'vue文档',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:vuejs'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'document_vue-new',
|
||||||
|
path: '/document/vue-new',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: 'vue文档(新版)',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:vuejs'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'document_vite',
|
||||||
|
path: '/document/vite',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: 'vite文档',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'simple-icons:vite'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'document_project',
|
||||||
|
path: '/document/project',
|
||||||
|
meta: {
|
||||||
|
title: '项目文档(外链)',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:file-link-outline',
|
||||||
|
href: 'https://docs.soybean.pro/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '文档',
|
||||||
|
icon: 'carbon:document',
|
||||||
|
order: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'component',
|
||||||
|
path: '/component',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'component_button',
|
||||||
|
path: '/component/button',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '按钮',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:baseline-radio-button-checked'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'component_card',
|
||||||
|
path: '/component/card',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '卡片',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:card-outline'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'component_table',
|
||||||
|
path: '/component/table',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '表格',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:table-large'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '组件示例',
|
||||||
|
icon: 'fluent:app-store-24-regular',
|
||||||
|
order: 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin',
|
||||||
|
path: '/plugin',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'plugin_map',
|
||||||
|
path: '/plugin/map',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '地图',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:map'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin_video',
|
||||||
|
path: '/plugin/video',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '视频',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:video'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin_editor',
|
||||||
|
path: '/plugin/editor',
|
||||||
|
component: 'multi',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'plugin_editor_quill',
|
||||||
|
path: '/plugin/editor/quill',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '富文本编辑器',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:file-document-edit-outline'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin_editor_markdown',
|
||||||
|
path: '/plugin/editor/markdown',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: 'markdown编辑器',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ri:markdown-line'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '编辑器',
|
||||||
|
icon: 'icon-park-outline:editor'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin_swiper',
|
||||||
|
path: '/plugin/swiper',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: 'Swiper插件',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'simple-icons:swiper'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin_copy',
|
||||||
|
path: '/plugin/copy',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '剪贴板',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'mdi:clipboard-outline'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin_icon',
|
||||||
|
path: '/plugin/icon',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '图标',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:baseline-insert-emoticon'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'plugin_print',
|
||||||
|
path: '/plugin/print',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '打印',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:baseline-local-printshop'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '插件示例',
|
||||||
|
icon: 'clarity:plugin-line',
|
||||||
|
order: 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'auth-demo',
|
||||||
|
path: '/auth-demo',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'auth-demo_permission',
|
||||||
|
path: '/auth-demo/permission',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '权限切换',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:round-construction'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '权限示例',
|
||||||
|
icon: 'ic:baseline-security',
|
||||||
|
order: 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'exception',
|
||||||
|
path: '/exception',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'exception_403',
|
||||||
|
path: '/exception/403',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '异常页403',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:baseline-block'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'exception_404',
|
||||||
|
path: '/exception/404',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '异常页404',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:baseline-web-asset-off'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'exception_500',
|
||||||
|
path: '/exception/500',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '异常页500',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:baseline-wifi-off'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '异常页',
|
||||||
|
icon: 'ant-design:exception-outlined',
|
||||||
|
order: 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'multi-menu',
|
||||||
|
path: '/multi-menu',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'multi-menu_first',
|
||||||
|
path: '/multi-menu/first',
|
||||||
|
component: 'multi',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'multi-menu_first_second',
|
||||||
|
path: '/multi-menu/first/second',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '二级菜单',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:outline-menu'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'multi-menu_first_second-new',
|
||||||
|
path: '/multi-menu/first/second-new',
|
||||||
|
component: 'multi',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'multi-menu_first_second-new_third',
|
||||||
|
path: '/multi-menu/first/second-new/third',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '三级菜单',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:outline-menu'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '二级菜单(有子菜单)',
|
||||||
|
icon: 'ic:outline-menu'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '一级菜单',
|
||||||
|
icon: 'ic:outline-menu'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '多级菜单',
|
||||||
|
icon: 'carbon:menu',
|
||||||
|
order: 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'about',
|
||||||
|
path: '/about',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '关于',
|
||||||
|
requiresAuth: true,
|
||||||
|
singleLayout: 'basic',
|
||||||
|
icon: 'fluent:book-information-24-regular',
|
||||||
|
order: 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
user: [
|
||||||
|
{
|
||||||
|
name: 'dashboard',
|
||||||
|
path: '/dashboard',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'dashboard_analysis',
|
||||||
|
path: '/dashboard/analysis',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '分析页',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'icon-park-outline:analysis'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '仪表盘',
|
||||||
|
icon: 'carbon:dashboard',
|
||||||
|
order: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'auth-demo',
|
||||||
|
path: '/auth-demo',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'auth-demo_permission',
|
||||||
|
path: '/auth-demo/permission',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '权限切换',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:round-construction'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '权限示例',
|
||||||
|
icon: 'ic:baseline-security',
|
||||||
|
order: 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'multi-menu',
|
||||||
|
path: '/multi-menu',
|
||||||
|
component: 'basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'multi-menu_first',
|
||||||
|
path: '/multi-menu/first',
|
||||||
|
component: 'multi',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'multi-menu_first_second',
|
||||||
|
path: '/multi-menu/first/second',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '二级菜单',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:outline-menu'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'multi-menu_first_second-new',
|
||||||
|
path: '/multi-menu/first/second-new',
|
||||||
|
component: 'multi',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'multi-menu_first_second-new_third',
|
||||||
|
path: '/multi-menu/first/second-new/third',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '三级菜单',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'ic:outline-menu'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '二级菜单(有子菜单)',
|
||||||
|
icon: 'ic:outline-menu'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '一级菜单',
|
||||||
|
icon: 'ic:outline-menu'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
title: '多级菜单',
|
||||||
|
icon: 'carbon:menu',
|
||||||
|
order: 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'about',
|
||||||
|
path: '/about',
|
||||||
|
component: 'self',
|
||||||
|
meta: {
|
||||||
|
title: '关于',
|
||||||
|
requiresAuth: true,
|
||||||
|
singleLayout: 'basic',
|
||||||
|
icon: 'fluent:book-information-24-regular',
|
||||||
|
order: 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
80
package.json
80
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "soybean-admin",
|
"name": "soybean-admin",
|
||||||
"version": "0.9.3",
|
"version": "0.9.4",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env VITE_ENV_TYPE=dev vite",
|
"dev": "cross-env VITE_ENV_TYPE=dev vite",
|
||||||
"dev:test": "cross-env VITE_ENV_TYPE=test vite",
|
"dev:test": "cross-env VITE_ENV_TYPE=test vite",
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"build:vercel": "cross-env VITE_HASH_ROUTE=true vite build",
|
"build:vercel": "cross-env VITE_HASH_ROUTE=true vite build",
|
||||||
"preview": "vite preview --port 5050",
|
"preview": "vite preview --port 5050",
|
||||||
"typecheck": "vue-tsc --noEmit",
|
"typecheck": "vue-tsc --noEmit",
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"postinstall": "patch-package",
|
"postinstall": "patch-package",
|
||||||
"release": "standard-version",
|
"release": "standard-version",
|
||||||
@@ -26,74 +26,76 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@antv/g2plot": "^2.4.10",
|
"@antv/g2plot": "^2.4.16",
|
||||||
"@better-scroll/core": "^2.4.2",
|
"@better-scroll/core": "^2.4.2",
|
||||||
"@vueuse/core": "^8.0.0",
|
"@soybeanjs/vue-admin-layout": "^1.0.3",
|
||||||
"axios": "^0.26.1",
|
"@soybeanjs/vue-admin-tab": "^1.0.1",
|
||||||
|
"@vueuse/core": "^8.3.1",
|
||||||
|
"axios": "^0.27.2",
|
||||||
"clipboard": "^2.0.10",
|
"clipboard": "^2.0.10",
|
||||||
"colord": "^2.9.2",
|
"colord": "^2.9.2",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"dayjs": "^1.10.8",
|
"dayjs": "^1.11.1",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"naive-ui": "^2.26.4",
|
"naive-ui": "^2.28.2",
|
||||||
"pinia": "^2.0.11",
|
"pinia": "^2.0.13",
|
||||||
"print-js": "^1.6.0",
|
"print-js": "^1.6.0",
|
||||||
"qs": "^6.10.3",
|
"qs": "^6.10.3",
|
||||||
"soybean-admin-layout": "^1.0.4",
|
"swiper": "^8.1.4",
|
||||||
"soybean-admin-tab": "^1.2.3",
|
|
||||||
"swiper": "^8.0.7",
|
|
||||||
"ua-parser-js": "^1.0.2",
|
"ua-parser-js": "^1.0.2",
|
||||||
"vditor": "^3.8.12",
|
"vditor": "^3.8.13",
|
||||||
"vue": "^3.2.31",
|
"vue": "3.2.33",
|
||||||
"vue-router": "^4.0.14",
|
"vue-router": "^4.0.14",
|
||||||
"wangeditor": "^4.7.12",
|
"wangeditor": "^4.7.15",
|
||||||
"xgplayer": "^2.31.4"
|
"xgplayer": "^2.31.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@amap/amap-jsapi-types": "^0.0.8",
|
"@amap/amap-jsapi-types": "^0.0.8",
|
||||||
"@commitlint/cli": "^16.2.1",
|
"@commitlint/cli": "^16.2.4",
|
||||||
"@commitlint/config-conventional": "^16.2.1",
|
"@commitlint/config-conventional": "^16.2.4",
|
||||||
"@iconify/json": "^2.1.14",
|
"@iconify/json": "^2.1.33",
|
||||||
"@iconify/vue": "^3.1.4",
|
"@iconify/vue": "^3.2.1",
|
||||||
"@types/bmapgl": "^0.0.5",
|
"@types/bmapgl": "^0.0.5",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/node": "^17.0.21",
|
"@types/node": "^17.0.29",
|
||||||
"@types/qs": "^6.9.7",
|
"@types/qs": "^6.9.7",
|
||||||
"@types/ua-parser-js": "^0.7.36",
|
"@types/ua-parser-js": "^0.7.36",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.14.0",
|
"@typescript-eslint/eslint-plugin": "^5.21.0",
|
||||||
"@typescript-eslint/parser": "^5.14.0",
|
"@typescript-eslint/parser": "^5.21.0",
|
||||||
"@vitejs/plugin-vue": "^2.2.4",
|
"@vitejs/plugin-vue": "^2.3.1",
|
||||||
|
"@vitejs/plugin-vue-jsx": "^1.3.10",
|
||||||
"@vue/eslint-config-prettier": "^7.0.0",
|
"@vue/eslint-config-prettier": "^7.0.0",
|
||||||
"@vue/eslint-config-typescript": "^10.0.0",
|
"@vue/eslint-config-typescript": "^10.0.0",
|
||||||
"@vue/tsconfig": "^0.1.3",
|
|
||||||
"commitizen": "^4.2.4",
|
"commitizen": "^4.2.4",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cz-conventional-changelog": "^3.3.0",
|
"cz-conventional-changelog": "^3.3.0",
|
||||||
"cz-customizable": "^6.3.0",
|
"cz-customizable": "^6.3.0",
|
||||||
"eslint": "^8.11.0",
|
"eslint": "^8.14.0",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-import": "^2.25.4",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-vue": "^8.5.0",
|
"eslint-plugin-vue": "^8.7.1",
|
||||||
"husky": "^7.0.4",
|
"husky": "^7.0.4",
|
||||||
"lint-staged": "^12.3.5",
|
"lint-staged": "^12.4.1",
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
"patch-package": "^6.4.7",
|
"patch-package": "^6.4.7",
|
||||||
"postinstall-postinstall": "^2.1.0",
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.6.2",
|
||||||
"rollup-plugin-visualizer": "^5.6.0",
|
"rollup-plugin-visualizer": "^5.6.0",
|
||||||
"sass": "^1.49.9",
|
"sass": "^1.51.0",
|
||||||
"typescript": "~4.6.2",
|
"standard-version": "^9.3.2",
|
||||||
"unplugin-icons": "^0.13.3",
|
"typescript": "^4.6.3",
|
||||||
"unplugin-vue-components": "^0.18.0",
|
"unocss": "^0.32.1",
|
||||||
"vite": "2.8.6",
|
"unplugin-icons": "^0.14.1",
|
||||||
"vite-plugin-html": "^3.1.0",
|
"unplugin-vue-components": "0.19.3",
|
||||||
|
"unplugin-vue-define-options": "^0.6.1",
|
||||||
|
"vite": "^2.9.6",
|
||||||
|
"vite-plugin-compression": "^0.5.1",
|
||||||
|
"vite-plugin-html": "^3.2.0",
|
||||||
"vite-plugin-mock": "^2.9.6",
|
"vite-plugin-mock": "^2.9.6",
|
||||||
"vite-plugin-windicss": "^1.8.3",
|
"vue-tsc": "^0.34.10",
|
||||||
"vue-tsc": "^0.32.1",
|
"vueuc": "^0.4.32"
|
||||||
"vueuc": "^0.4.27",
|
|
||||||
"windicss": "^3.5.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8936
pnpm-lock.yaml
generated
8936
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* 初始化加载效果的svg格式logo
|
* 初始化加载效果的svg格式logo
|
||||||
* @param { string }id - 元素id
|
* @param {string} id - 元素id
|
||||||
*/
|
*/
|
||||||
function initSvgLogo(id) {
|
function initSvgLogo(id) {
|
||||||
const svgStr = `<svg width="128px" height="128px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
const svgStr = `<svg width="128px" height="128px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||||
@@ -22,8 +22,7 @@ function initSvgLogo(id) {
|
|||||||
<path style="fill:currentColor" d="M77.9,126.6c0,3.9-2.8,7.2-6.7,7.9c-7.8,1.5-14.8,5.9-19.7,12.2c-2.7,3.5-7.6,4.2-11.2,1.6
|
<path style="fill:currentColor" d="M77.9,126.6c0,3.9-2.8,7.2-6.7,7.9c-7.8,1.5-14.8,5.9-19.7,12.2c-2.7,3.5-7.6,4.2-11.2,1.6
|
||||||
c-3.6-2.6-4.3-7.6-1.7-11.2c0.1-0.1,0.2-0.3,0.3-0.4c4.1-5.2,9.3-9.6,15.1-12.8c4.4-2.5,9.1-4.2,14-5.1
|
c-3.6-2.6-4.3-7.6-1.7-11.2c0.1-0.1,0.2-0.3,0.3-0.4c4.1-5.2,9.3-9.6,15.1-12.8c4.4-2.5,9.1-4.2,14-5.1
|
||||||
C73.3,117.7,77.9,121.3,77.9,126.6z" />
|
C73.3,117.7,77.9,121.3,77.9,126.6z" />
|
||||||
</svg>
|
</svg>`;
|
||||||
`;
|
|
||||||
const appEl = document.querySelector(id);
|
const appEl = document.querySelector(id);
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.innerHTML = svgStr;
|
div.innerHTML = svgStr;
|
||||||
@@ -34,11 +33,12 @@ function initSvgLogo(id) {
|
|||||||
|
|
||||||
function addThemeColorCssVars() {
|
function addThemeColorCssVars() {
|
||||||
const key = '__THEME_COLOR__';
|
const key = '__THEME_COLOR__';
|
||||||
const themeColor = window.localStorage.getItem(key) || '#1890ff';
|
const defaultColor = '#1890ff';
|
||||||
|
const themeColor = window.localStorage.getItem(key) || defaultColor;
|
||||||
const cssVars = `--primary-color: ${themeColor}`;
|
const cssVars = `--primary-color: ${themeColor}`;
|
||||||
document.documentElement.style.cssText = cssVars;
|
document.documentElement.style.cssText = cssVars;
|
||||||
}
|
}
|
||||||
|
|
||||||
initSvgLogo('#loadingLogo');
|
|
||||||
|
|
||||||
addThemeColorCssVars();
|
addThemeColorCssVars();
|
||||||
|
|
||||||
|
initSvgLogo('#loadingLogo');
|
||||||
|
@@ -53,7 +53,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
emptyDesc: '暂无数据',
|
emptyDesc: '暂无数据',
|
||||||
iconClass: 'text-320px text-primary',
|
iconClass: 'text-320px text-primary',
|
||||||
descClass: 'text-16px text-[#666]',
|
descClass: 'text-16px text-[#666]',
|
||||||
showNetworkReload: false,
|
showNetworkReload: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// 网络状态
|
// 网络状态
|
||||||
@@ -79,7 +79,7 @@ function handleReload() {
|
|||||||
|
|
||||||
const stopHandle = watch(
|
const stopHandle = watch(
|
||||||
() => props.loading,
|
() => props.loading,
|
||||||
(newValue) => {
|
newValue => {
|
||||||
// 结束加载判断一下网络状态
|
// 结束加载判断一下网络状态
|
||||||
if (!newValue) {
|
if (!newValue) {
|
||||||
setNetwork(window.navigator.onLine);
|
setNetwork(window.navigator.onLine);
|
||||||
|
@@ -23,7 +23,7 @@ interface Emits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
value: true,
|
value: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
@@ -34,7 +34,7 @@ const checked = computed({
|
|||||||
},
|
},
|
||||||
set(newValue: boolean) {
|
set(newValue: boolean) {
|
||||||
emit('update:value', newValue);
|
emit('update:value', newValue);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleClickProtocol() {
|
function handleClickProtocol() {
|
||||||
|
@@ -1,10 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="bg-white text-[#333639] dark:(bg-[#18181c] text-white text-opacity-82) transition-all duration-300 ease-in-out"
|
class="dark:bg-[#18181c] dark:text-white dark:text-opacity-82 transition-all duration-300 ease-in-out"
|
||||||
|
:class="inverted ? 'bg-[#001428] text-white' : 'bg-white text-[#333639]'"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
inverted?: boolean;
|
||||||
|
}
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
inverted: false
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@@ -18,7 +18,7 @@ interface Emits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
dark: false,
|
dark: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
@@ -29,7 +29,7 @@ const darkMode = computed({
|
|||||||
},
|
},
|
||||||
set(newValue: boolean) {
|
set(newValue: boolean) {
|
||||||
emit('update:dark', newValue);
|
emit('update:dark', newValue);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleSwitch() {
|
function handleSwitch() {
|
||||||
|
@@ -29,7 +29,7 @@ interface Props {
|
|||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
tooltipContent: '',
|
tooltipContent: '',
|
||||||
placement: 'bottom',
|
placement: 'bottom',
|
||||||
contentClass: '',
|
contentClass: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
const showTooltip = computed(() => Boolean(props.tooltipContent));
|
const showTooltip = computed(() => Boolean(props.tooltipContent));
|
||||||
|
@@ -29,7 +29,7 @@ const NaiveProviderContent = defineComponent({
|
|||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
return h('div');
|
return h('div');
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@@ -10,7 +10,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {
|
withDefaults(defineProps<Props>(), {
|
||||||
fill: false,
|
fill: false
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@@ -42,7 +42,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
separator: ',',
|
separator: ',',
|
||||||
decimal: '.',
|
decimal: '.',
|
||||||
useEasing: true,
|
useEasing: true,
|
||||||
transition: 'linear',
|
transition: 'linear'
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -61,7 +61,7 @@ function run() {
|
|||||||
duration: props.duration,
|
duration: props.duration,
|
||||||
onStarted: () => emit('on-started'),
|
onStarted: () => emit('on-started'),
|
||||||
onFinished: () => emit('on-finished'),
|
onFinished: () => emit('on-finished'),
|
||||||
...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {}),
|
...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -43,7 +43,7 @@ interface Emits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
emptyIcon: 'mdi:apps',
|
emptyIcon: 'mdi:apps'
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
@@ -51,7 +51,7 @@ const emit = defineEmits<Emits>();
|
|||||||
const theme = useThemeStore();
|
const theme = useThemeStore();
|
||||||
|
|
||||||
const searchValue = ref('');
|
const searchValue = ref('');
|
||||||
const iconsList = computed(() => props.icons.filter((v) => v.includes(searchValue.value)));
|
const iconsList = computed(() => props.icons.filter(v => v.includes(searchValue.value)));
|
||||||
|
|
||||||
const modelValue = computed({
|
const modelValue = computed({
|
||||||
get() {
|
get() {
|
||||||
@@ -59,7 +59,7 @@ const modelValue = computed({
|
|||||||
},
|
},
|
||||||
set(val: string) {
|
set(val: string) {
|
||||||
emit('update:value', val);
|
emit('update:value', val);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleChange(iconItem: string) {
|
function handleChange(iconItem: string) {
|
||||||
|
@@ -17,7 +17,7 @@ interface Emits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
code: '',
|
code: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
@@ -26,11 +26,11 @@ const { domRef, imgCode, setImgCode, getImgCode } = useImageVerify();
|
|||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.code,
|
() => props.code,
|
||||||
(newValue) => {
|
newValue => {
|
||||||
setImgCode(newValue);
|
setImgCode(newValue);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
watch(imgCode, (newValue) => {
|
watch(imgCode, newValue => {
|
||||||
emit('update:code', newValue);
|
emit('update:code', newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -18,23 +18,23 @@ export function useBasicLayout() {
|
|||||||
vertical: {
|
vertical: {
|
||||||
showLogo: false,
|
showLogo: false,
|
||||||
showHeaderMenu: false,
|
showHeaderMenu: false,
|
||||||
showMenuCollape: true,
|
showMenuCollapse: true
|
||||||
},
|
},
|
||||||
'vertical-mix': {
|
'vertical-mix': {
|
||||||
showLogo: false,
|
showLogo: false,
|
||||||
showHeaderMenu: false,
|
showHeaderMenu: false,
|
||||||
showMenuCollape: false,
|
showMenuCollapse: false
|
||||||
},
|
},
|
||||||
horizontal: {
|
horizontal: {
|
||||||
showLogo: true,
|
showLogo: true,
|
||||||
showHeaderMenu: true,
|
showHeaderMenu: true,
|
||||||
showMenuCollape: false,
|
showMenuCollapse: false
|
||||||
},
|
},
|
||||||
'horizontal-mix': {
|
'horizontal-mix': {
|
||||||
showLogo: true,
|
showLogo: true,
|
||||||
showHeaderMenu: false,
|
showHeaderMenu: false,
|
||||||
showMenuCollape: true,
|
showMenuCollapse: true
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const headerProps = computed(() => layoutHeaderProps[theme.layout.mode]);
|
const headerProps = computed(() => layoutHeaderProps[theme.layout.mode]);
|
||||||
@@ -64,6 +64,6 @@ export function useBasicLayout() {
|
|||||||
headerProps,
|
headerProps,
|
||||||
siderVisible,
|
siderVisible,
|
||||||
siderWidth,
|
siderWidth,
|
||||||
siderCollapsedWidth,
|
siderCollapsedWidth
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -46,7 +46,7 @@ export function useRouterPush(inSetup = true) {
|
|||||||
const module: EnumType.LoginModuleKey = loginModule || 'pwd-login';
|
const module: EnumType.LoginModuleKey = loginModule || 'pwd-login';
|
||||||
const routeLocation: RouteLocationRaw = {
|
const routeLocation: RouteLocationRaw = {
|
||||||
name: routeName('login'),
|
name: routeName('login'),
|
||||||
params: { module },
|
params: { module }
|
||||||
};
|
};
|
||||||
const redirect = redirectUrl || route.value.fullPath;
|
const redirect = redirectUrl || route.value.fullPath;
|
||||||
Object.assign(routeLocation, { query: { redirect } });
|
Object.assign(routeLocation, { query: { redirect } });
|
||||||
@@ -80,6 +80,6 @@ export function useRouterPush(inSetup = true) {
|
|||||||
toHome,
|
toHome,
|
||||||
toLogin,
|
toLogin,
|
||||||
toLoginModule,
|
toLoginModule,
|
||||||
toLoginRedirect,
|
toLoginRedirect
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
import UAParser from 'ua-parser-js';
|
import UAParser from 'ua-parser-js';
|
||||||
|
import { useAuthStore } from '@/store';
|
||||||
|
import { isArray, isString } from '@/utils';
|
||||||
|
|
||||||
interface AppInfo {
|
interface AppInfo {
|
||||||
/** 项目名称 */
|
/** 项目名称 */
|
||||||
@@ -16,7 +18,7 @@ export function useAppInfo(): AppInfo {
|
|||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
title,
|
title,
|
||||||
desc,
|
desc
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,3 +28,27 @@ export function useDeviceInfo() {
|
|||||||
const result = parser.getResult();
|
const result = parser.getResult();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 权限判断 */
|
||||||
|
export function usePermission() {
|
||||||
|
const auth = useAuthStore();
|
||||||
|
|
||||||
|
function hasPermission(permission: Auth.RoleType | Auth.RoleType[]) {
|
||||||
|
const { userRole } = auth.userInfo;
|
||||||
|
|
||||||
|
let has = userRole === 'super';
|
||||||
|
if (!has) {
|
||||||
|
if (isArray(permission)) {
|
||||||
|
has = (permission as Auth.RoleType[]).includes(userRole);
|
||||||
|
}
|
||||||
|
if (isString(permission)) {
|
||||||
|
has = (permission as Auth.RoleType) === userRole;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return has;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasPermission
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
/** 百度地图sdk地址 */
|
/** 百度地图sdk地址 */
|
||||||
export const BAIDU_MAP_SDK_URL =
|
export const BAIDU_MAP_SDK_URL = `https://api.map.baidu.com/getscript?v=3.0&ak=KSezYymXPth1DIGILRX3oYN9PxbOQQmU&services=&t=20210201100830&s=1`;
|
||||||
'https://api.map.baidu.com/getscript?v=3.0&ak=KSezYymXPth1DIGILRX3oYN9PxbOQQmU&services=&t=20210201100830&s=1';
|
|
||||||
|
|
||||||
/** 高德地图sdk地址 */
|
/** 高德地图sdk地址 */
|
||||||
export const GAODE_MAP_SDK_URL = 'https://webapi.amap.com/maps?v=2.0&key=e7bd02bd504062087e6563daf4d6721d';
|
export const GAODE_MAP_SDK_URL = 'https://webapi.amap.com/maps?v=2.0&key=e7bd02bd504062087e6563daf4d6721d';
|
||||||
|
@@ -5,9 +5,9 @@ export const REGEXP_PHONE =
|
|||||||
/** 邮箱正则 */
|
/** 邮箱正则 */
|
||||||
export const REGEXP_EMAIL = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
|
export const REGEXP_EMAIL = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
|
||||||
|
|
||||||
/** 密码正则(密码为8-18位数字/字符/符号的组合) */
|
/** 密码正则(密码为6-18位数字/字符/符号的组合) */
|
||||||
export const REGEXP_PWD =
|
export const REGEXP_PWD =
|
||||||
/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
|
/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){6,18}$/;
|
||||||
|
|
||||||
/** 6位数字验证码正则 */
|
/** 6位数字验证码正则 */
|
||||||
export const REGEXP_CODE_SIX = /^\d{6}$/;
|
export const REGEXP_CODE_SIX = /^\d{6}$/;
|
||||||
|
@@ -36,7 +36,7 @@ export const ERROR_STATUS = {
|
|||||||
503: '503: 服务不可用~',
|
503: '503: 服务不可用~',
|
||||||
504: '504: 网关超时~',
|
504: '504: 网关超时~',
|
||||||
505: '505: http版本不支持该请求~',
|
505: '505: http版本不支持该请求~',
|
||||||
[DEFAULT_REQUEST_ERROR_CODE]: DEFAULT_REQUEST_ERROR_MSG,
|
[DEFAULT_REQUEST_ERROR_CODE]: DEFAULT_REQUEST_ERROR_MSG
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 不弹出错误信息的code */
|
/** 不弹出错误信息的code */
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
import setupNetworkDirective from './network';
|
import setupNetworkDirective from './network';
|
||||||
import setupLoginDirective from './login';
|
import setupLoginDirective from './login';
|
||||||
|
import setupPermissionDirective from './permission';
|
||||||
|
|
||||||
|
/** setup custom vue directives. - [安装自定义的vue指令] */
|
||||||
export function setupDirectives(app: App) {
|
export function setupDirectives(app: App) {
|
||||||
setupNetworkDirective(app);
|
setupNetworkDirective(app);
|
||||||
setupLoginDirective(app);
|
setupLoginDirective(app);
|
||||||
|
setupPermissionDirective(app);
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ export default function setupLoginDirective(app: App) {
|
|||||||
unmounted(el: HTMLElement, binding) {
|
unmounted(el: HTMLElement, binding) {
|
||||||
if (binding.value === false) return;
|
if (binding.value === false) return;
|
||||||
el.removeEventListener('click', listenerHandler);
|
el.removeEventListener('click', listenerHandler);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
app.directive('login', loginDirective);
|
app.directive('login', loginDirective);
|
||||||
|
@@ -18,7 +18,7 @@ export default function setupNetworkDirective(app: App) {
|
|||||||
unmounted(el: HTMLElement, binding) {
|
unmounted(el: HTMLElement, binding) {
|
||||||
if (binding.value === false) return;
|
if (binding.value === false) return;
|
||||||
el.removeEventListener('click', listenerHandler);
|
el.removeEventListener('click', listenerHandler);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
app.directive('network', networkDirective);
|
app.directive('network', networkDirective);
|
||||||
|
26
src/directives/permission.ts
Normal file
26
src/directives/permission.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { App, Directive } from 'vue';
|
||||||
|
import { usePermission } from '@/composables';
|
||||||
|
|
||||||
|
export default function setupPermissionDirective(app: App) {
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
|
function updateElVisible(el: HTMLElement, permission: Auth.RoleType | Auth.RoleType[]) {
|
||||||
|
if (!permission) {
|
||||||
|
throw new Error(`need roles: like v-permission="'admin'", v-permission="['admin', 'test]"`);
|
||||||
|
}
|
||||||
|
if (!hasPermission(permission)) {
|
||||||
|
el.parentElement?.removeChild(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissionDirective: Directive<HTMLElement, Auth.RoleType | Auth.RoleType[]> = {
|
||||||
|
mounted(el, binding) {
|
||||||
|
updateElVisible(el, binding.value);
|
||||||
|
},
|
||||||
|
beforeUpdate(el, binding) {
|
||||||
|
updateElVisible(el, binding.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
app.directive('permission', permissionDirective);
|
||||||
|
}
|
@@ -1,8 +1,16 @@
|
|||||||
|
/** 用户角色 */
|
||||||
|
export enum EnumUserRole {
|
||||||
|
super = '超级管理员',
|
||||||
|
admin = '管理员',
|
||||||
|
user = '普通用户'
|
||||||
|
// custom = '自定义角色'
|
||||||
|
}
|
||||||
|
|
||||||
/** 登录模块 */
|
/** 登录模块 */
|
||||||
export enum EnumLoginModule {
|
export enum EnumLoginModule {
|
||||||
'pwd-login' = '账密登录',
|
'pwd-login' = '账密登录',
|
||||||
'code-login' = '手机验证码登录',
|
'code-login' = '手机验证码登录',
|
||||||
'register' = '注册',
|
'register' = '注册',
|
||||||
'reset-pwd' = '重置密码',
|
'reset-pwd' = '重置密码',
|
||||||
'bind-wechat' = '微信绑定',
|
'bind-wechat' = '微信绑定'
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
export enum EnumContentType {
|
export enum EnumContentType {
|
||||||
json = 'application/json',
|
json = 'application/json',
|
||||||
formUrlencoded = 'application/x-www-form-urlencoded',
|
formUrlencoded = 'application/x-www-form-urlencoded',
|
||||||
formData = 'multipart/form-data',
|
formData = 'multipart/form-data'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 缓存的key */
|
/** 缓存的key */
|
||||||
@@ -12,11 +12,11 @@ export enum EnumStorageKey {
|
|||||||
/** 用户token */
|
/** 用户token */
|
||||||
'token' = '__TOKEN__',
|
'token' = '__TOKEN__',
|
||||||
/** 用户刷新token */
|
/** 用户刷新token */
|
||||||
'refresh-koken' = '__REFRESH_TOKEN__',
|
'refresh-token' = '__REFRESH_TOKEN__',
|
||||||
/** 用户信息 */
|
/** 用户信息 */
|
||||||
'user-info' = '__USER_INFO__',
|
'user-info' = '__USER_INFO__',
|
||||||
/** 多页签路由信息 */
|
/** 多页签路由信息 */
|
||||||
'tab-routes' = '__TAB_ROUTES__',
|
'tab-routes' = '__TAB_ROUTES__'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 数据类型 */
|
/** 数据类型 */
|
||||||
@@ -31,5 +31,5 @@ export enum EnumDataType {
|
|||||||
date = '[object Date]',
|
date = '[object Date]',
|
||||||
regexp = '[object RegExp]',
|
regexp = '[object RegExp]',
|
||||||
set = '[object Set]',
|
set = '[object Set]',
|
||||||
map = '[object Map]',
|
map = '[object Map]'
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
/** 布局组件的名称 */
|
/** 布局组件的名称 */
|
||||||
export enum EnumLayoutComponentName {
|
export enum EnumLayoutComponentName {
|
||||||
basic = 'basic-layout',
|
basic = 'basic-layout',
|
||||||
blank = 'blank-layout',
|
blank = 'blank-layout'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 布局模式 */
|
/** 布局模式 */
|
||||||
@@ -9,20 +9,20 @@ export enum EnumThemeLayoutMode {
|
|||||||
'vertical' = '左侧菜单模式',
|
'vertical' = '左侧菜单模式',
|
||||||
'horizontal' = '顶部菜单模式',
|
'horizontal' = '顶部菜单模式',
|
||||||
'vertical-mix' = '左侧菜单混合模式',
|
'vertical-mix' = '左侧菜单混合模式',
|
||||||
'horizontal-mix' = '顶部菜单混合模式',
|
'horizontal-mix' = '顶部菜单混合模式'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 多页签风格 */
|
/** 多页签风格 */
|
||||||
export enum EnumThemeTabMode {
|
export enum EnumThemeTabMode {
|
||||||
'chrome' = '谷歌风格',
|
'chrome' = '谷歌风格',
|
||||||
'button' = '按钮风格',
|
'button' = '按钮风格'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 水平模式的菜单位置 */
|
/** 水平模式的菜单位置 */
|
||||||
export enum EnumThemeHorizontalMenuPosition {
|
export enum EnumThemeHorizontalMenuPosition {
|
||||||
'flex-start' = '居左',
|
'flex-start' = '居左',
|
||||||
'center' = '居中',
|
'center' = '居中',
|
||||||
'flex-end' = '居右',
|
'flex-end' = '居右'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 过渡动画类型 */
|
/** 过渡动画类型 */
|
||||||
@@ -32,5 +32,5 @@ export enum EnumThemeAnimateMode {
|
|||||||
'fade-slide' = '滑动',
|
'fade-slide' = '滑动',
|
||||||
'fade' = '消退',
|
'fade' = '消退',
|
||||||
'fade-bottom' = '底部消退',
|
'fade-bottom' = '底部消退',
|
||||||
'fade-scale' = '缩放消退',
|
'fade-scale' = '缩放消退'
|
||||||
}
|
}
|
||||||
|
@@ -47,6 +47,6 @@ export default function useCountDown(second: number) {
|
|||||||
isCounting,
|
isCounting,
|
||||||
start,
|
start,
|
||||||
stop,
|
stop,
|
||||||
isComplete,
|
isComplete
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ export default function useImageVerify(width = 152, height = 40) {
|
|||||||
domRef,
|
domRef,
|
||||||
imgCode,
|
imgCode,
|
||||||
setImgCode,
|
setImgCode,
|
||||||
getImgCode,
|
getImgCode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -54,6 +54,6 @@ export default function useSmsCode() {
|
|||||||
start,
|
start,
|
||||||
isCounting,
|
isCounting,
|
||||||
getSmsCode,
|
getSmsCode,
|
||||||
loading,
|
loading
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,7 @@ interface ScrollBodyStyle {
|
|||||||
export default function useBodyScroll(duration = 300) {
|
export default function useBodyScroll(duration = 300) {
|
||||||
const defaultStyle: ScrollBodyStyle = {
|
const defaultStyle: ScrollBodyStyle = {
|
||||||
overflow: '',
|
overflow: '',
|
||||||
paddingRight: '',
|
paddingRight: ''
|
||||||
};
|
};
|
||||||
function getInitBodyStyle() {
|
function getInitBodyStyle() {
|
||||||
const { overflow, paddingRight } = document.body.style;
|
const { overflow, paddingRight } = document.body.style;
|
||||||
@@ -42,6 +42,6 @@ export default function useBodyScroll(duration = 300) {
|
|||||||
getInitBodyStyle();
|
getInitBodyStyle();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
scrollBodyHandler,
|
scrollBodyHandler
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,6 @@ export default function useBoolean(initValue = false) {
|
|||||||
setBool,
|
setBool,
|
||||||
setTrue,
|
setTrue,
|
||||||
setFalse,
|
setFalse,
|
||||||
toggle,
|
toggle
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,6 @@ export default function useContext<T>(contextName = 'context') {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
useProvide,
|
useProvide,
|
||||||
useInject,
|
useInject
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,6 @@ export default function useLoading(initValue = false) {
|
|||||||
return {
|
return {
|
||||||
loading,
|
loading,
|
||||||
startLoading,
|
startLoading,
|
||||||
endLoading,
|
endLoading
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,6 @@ export default function useLoadingEmpty(initLoading = false, initEmpty = false)
|
|||||||
startLoading,
|
startLoading,
|
||||||
endLoading,
|
endLoading,
|
||||||
empty,
|
empty,
|
||||||
setEmpty,
|
setEmpty
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@ export default function useModalVisible(hideScroll = true) {
|
|||||||
const { scrollBodyHandler } = useBodyScroll();
|
const { scrollBodyHandler } = useBodyScroll();
|
||||||
|
|
||||||
function modalVisibleWatcher() {
|
function modalVisibleWatcher() {
|
||||||
const stopHandle = watch(visible, async (newValue) => {
|
const stopHandle = watch(visible, async newValue => {
|
||||||
scrollBodyHandler(newValue);
|
scrollBodyHandler(newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -28,6 +28,6 @@ export default function useModalVisible(hideScroll = true) {
|
|||||||
visible,
|
visible,
|
||||||
openModal,
|
openModal,
|
||||||
closeModal,
|
closeModal,
|
||||||
toggleModal,
|
toggleModal
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -13,17 +13,13 @@ export default function useReload() {
|
|||||||
async function handleReload(duration = 0) {
|
async function handleReload(duration = 0) {
|
||||||
setFalse();
|
setFalse();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
if (duration) {
|
setTimeout(() => {
|
||||||
setTimeout(() => {
|
|
||||||
setTrue();
|
|
||||||
}, duration);
|
|
||||||
} else {
|
|
||||||
setTrue();
|
setTrue();
|
||||||
}
|
}, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
reloadFlag,
|
reloadFlag,
|
||||||
handleReload,
|
handleReload
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<soybean-admin-layout
|
<admin-layout
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:min-width="theme.layout.minWidth"
|
:min-width="theme.layout.minWidth"
|
||||||
:fixed-header-and-tab="theme.fixedHeaderAndTab"
|
:fixed-header-and-tab="theme.fixedHeaderAndTab"
|
||||||
@@ -25,12 +25,12 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<global-footer />
|
<global-footer />
|
||||||
</template>
|
</template>
|
||||||
</soybean-admin-layout>
|
</admin-layout>
|
||||||
<setting-drawer />
|
<setting-drawer />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SoybeanAdminLayout from 'soybean-admin-layout';
|
import AdminLayout from '@soybeanjs/vue-admin-layout';
|
||||||
import { useAppStore, useThemeStore } from '@/store';
|
import { useAppStore, useThemeStore } from '@/store';
|
||||||
import { useBasicLayout } from '@/composables';
|
import { useBasicLayout } from '@/composables';
|
||||||
import { SettingDrawer, GlobalHeader, GlobalTab, GlobalSider, GlobalContent, GlobalFooter } from '../common';
|
import { SettingDrawer, GlobalHeader, GlobalTab, GlobalSider, GlobalContent, GlobalFooter } from '../common';
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { RouterView } from 'vue-router';
|
||||||
import { useAppStore, useThemeStore, useRouteStore } from '@/store';
|
import { useAppStore, useThemeStore, useRouteStore } from '@/store';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -22,7 +23,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {
|
withDefaults(defineProps<Props>(), {
|
||||||
showPadding: true,
|
showPadding: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<hover-container class="w-40px h-full" tooltip-content="全屏" @click="toggle">
|
<hover-container class="w-40px h-full" tooltip-content="全屏" content-class="hover:text-primary" @click="toggle">
|
||||||
<icon-gridicons-fullscreen-exit v-if="isFullscreen" class="text-18px" />
|
<icon-gridicons-fullscreen-exit v-if="isFullscreen" class="text-18px" />
|
||||||
<icon-gridicons-fullscreen v-else class="text-18px" />
|
<icon-gridicons-fullscreen v-else class="text-18px" />
|
||||||
</hover-container>
|
</hover-container>
|
||||||
|
@@ -1,10 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<hover-container tooltip-content="github" class="w-40px h-full">
|
<hover-container
|
||||||
<a href="https://github.com/honghuangdc/soybean-admin" target="_blank" class="flex-center">
|
tooltip-content="github"
|
||||||
<icon-mdi-github class="text-20px text-[#666]" />
|
class="w-40px h-full"
|
||||||
</a>
|
content-class="hover:text-primary"
|
||||||
|
@click="handleClickLink"
|
||||||
|
>
|
||||||
|
<icon-mdi-github class="text-20px" />
|
||||||
</hover-container>
|
</hover-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup></script>
|
<script lang="ts" setup>
|
||||||
|
function handleClickLink() {
|
||||||
|
window.open('https://github.com/honghuangdc/soybean-admin', '_blank');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@@ -1,16 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-menu :value="activeKey" mode="horizontal" :options="menus" @update:value="handleUpdateMenu" />
|
<n-menu
|
||||||
|
:value="activeKey"
|
||||||
|
mode="horizontal"
|
||||||
|
:options="menus"
|
||||||
|
:inverted="theme.header.inverted"
|
||||||
|
@update:value="handleUpdateMenu"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import type { MenuOption } from 'naive-ui';
|
import type { MenuOption } from 'naive-ui';
|
||||||
import { useRouteStore } from '@/store';
|
import { useRouteStore, useThemeStore } from '@/store';
|
||||||
import { useRouterPush } from '@/composables';
|
import { useRouterPush } from '@/composables';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const routeStore = useRouteStore();
|
const routeStore = useRouteStore();
|
||||||
|
const theme = useThemeStore();
|
||||||
const { routerPush } = useRouterPush();
|
const { routerPush } = useRouterPush();
|
||||||
|
|
||||||
const menus = computed(() => routeStore.menus as GlobalMenuOption[]);
|
const menus = computed(() => routeStore.menus as GlobalMenuOption[]);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<hover-container class="w-40px h-full" @click="app.toggleSiderCollapse">
|
<hover-container class="w-40px h-full" content-class="hover:text-primary" @click="app.toggleSiderCollapse">
|
||||||
<icon-line-md-menu-unfold-left v-if="app.siderCollapse" class="text-16px" />
|
<icon-line-md-menu-unfold-left v-if="app.siderCollapse" class="text-16px" />
|
||||||
<icon-line-md-menu-fold-left v-else class="text-16px" />
|
<icon-line-md-menu-fold-left v-else class="text-16px" />
|
||||||
</hover-container>
|
</hover-container>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-dropdown :options="options" @select="handleDropdown">
|
<n-dropdown :options="options" @select="handleDropdown">
|
||||||
<hover-container class="px-12px">
|
<hover-container class="px-12px" content-class="hover:text-primary">
|
||||||
<icon-custom-avatar class="text-32px" />
|
<icon-custom-avatar class="text-32px" />
|
||||||
<span class="pl-8px text-16px font-medium">{{ auth.userInfo.userName }}</span>
|
<span class="pl-8px text-16px font-medium">{{ auth.userInfo.userName }}</span>
|
||||||
</hover-container>
|
</hover-container>
|
||||||
@@ -19,17 +19,17 @@ const options = [
|
|||||||
{
|
{
|
||||||
label: '用户中心',
|
label: '用户中心',
|
||||||
key: 'user-center',
|
key: 'user-center',
|
||||||
icon: iconifyRender('carbon:user-avatar'),
|
icon: iconifyRender('carbon:user-avatar')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'divider',
|
type: 'divider',
|
||||||
key: 'divider',
|
key: 'divider'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '退出登录',
|
label: '退出登录',
|
||||||
key: 'logout',
|
key: 'logout',
|
||||||
icon: iconifyRender('carbon:logout'),
|
icon: iconifyRender('carbon:logout')
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function handleDropdown(optionKey: string) {
|
function handleDropdown(optionKey: string) {
|
||||||
@@ -42,7 +42,7 @@ function handleDropdown(optionKey: string) {
|
|||||||
negativeText: '取消',
|
negativeText: '取消',
|
||||||
onPositiveClick: () => {
|
onPositiveClick: () => {
|
||||||
auth.resetAuthStore();
|
auth.resetAuthStore();
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<dark-mode-container class="global-header flex-y-center h-full">
|
<dark-mode-container class="global-header flex-y-center h-full" :inverted="theme.header.inverted">
|
||||||
<global-logo v-if="showLogo" :show-title="true" class="h-full" :style="{ width: theme.sider.width + 'px' }" />
|
<global-logo v-if="showLogo" :show-title="true" class="h-full" :style="{ width: theme.sider.width + 'px' }" />
|
||||||
<div v-if="!showHeaderMenu" class="flex-1-hidden flex-y-center h-full">
|
<div v-if="!showHeaderMenu" class="flex-1-hidden flex-y-center h-full">
|
||||||
<menu-collapse v-if="showMenuCollape" />
|
<menu-collapse v-if="showMenuCollapse" />
|
||||||
<global-breadcrumb v-if="theme.header.crumb.visible" />
|
<global-breadcrumb v-if="theme.header.crumb.visible" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex-1-hidden flex-y-center h-full" :style="{ justifyContent: theme.menu.horizontalPosition }">
|
<div v-else class="flex-1-hidden flex-y-center h-full" :style="{ justifyContent: theme.menu.horizontalPosition }">
|
||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
GithubSite,
|
GithubSite,
|
||||||
FullScreen,
|
FullScreen,
|
||||||
ThemeMode,
|
ThemeMode,
|
||||||
UserAvatar,
|
UserAvatar
|
||||||
} from './components';
|
} from './components';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -38,7 +38,7 @@ interface Props {
|
|||||||
/** 显示头部菜单 */
|
/** 显示头部菜单 */
|
||||||
showHeaderMenu: GlobalHeaderProps['showHeaderMenu'];
|
showHeaderMenu: GlobalHeaderProps['showHeaderMenu'];
|
||||||
/** 显示菜单折叠按钮 */
|
/** 显示菜单折叠按钮 */
|
||||||
showMenuCollape: GlobalHeaderProps['showMenuCollape'];
|
showMenuCollapse: GlobalHeaderProps['showMenuCollapse'];
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>();
|
defineProps<Props>();
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { RouterLink } from 'vue-router';
|
||||||
import { routePath } from '@/router';
|
import { routePath } from '@/router';
|
||||||
import { useAppInfo } from '@/composables';
|
import { useAppInfo } from '@/composables';
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
:closable="false"
|
:closable="false"
|
||||||
preset="card"
|
preset="card"
|
||||||
footer-style="padding: 0; margin: 0"
|
footer-style="padding: 0; margin: 0"
|
||||||
class="w-630px fixed top-50px left-1/2 transform -translate-x-1/2"
|
class="w-630px fixed left-0 right-0 top-50px"
|
||||||
@after-leave="handleClose"
|
@after-leave="handleClose"
|
||||||
>
|
>
|
||||||
<n-input ref="inputRef" v-model:value="keyword" clearable placeholder="请输入关键词搜索" @input="handleSearch">
|
<n-input ref="inputRef" v-model:value="keyword" clearable placeholder="请输入关键词搜索" @input="handleSearch">
|
||||||
@@ -60,10 +60,10 @@ const show = computed({
|
|||||||
},
|
},
|
||||||
set(val: boolean) {
|
set(val: boolean) {
|
||||||
emit('update:value', val);
|
emit('update:value', val);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(show, async (val) => {
|
watch(show, async val => {
|
||||||
if (val) {
|
if (val) {
|
||||||
/** 自动聚焦 */
|
/** 自动聚焦 */
|
||||||
await nextTick();
|
await nextTick();
|
||||||
@@ -74,7 +74,7 @@ watch(show, async (val) => {
|
|||||||
/** 查询 */
|
/** 查询 */
|
||||||
function search() {
|
function search() {
|
||||||
resultOptions.value = routeStore.searchMenus.filter(
|
resultOptions.value = routeStore.searchMenus.filter(
|
||||||
(menu) => keyword.value && menu.meta?.title.toLocaleLowerCase().includes(keyword.value.toLocaleLowerCase().trim())
|
menu => keyword.value && menu.meta?.title.toLocaleLowerCase().includes(keyword.value.toLocaleLowerCase().trim())
|
||||||
);
|
);
|
||||||
if (resultOptions.value?.length > 0) {
|
if (resultOptions.value?.length > 0) {
|
||||||
activePath.value = resultOptions.value[0].path;
|
activePath.value = resultOptions.value[0].path;
|
||||||
@@ -96,7 +96,7 @@ function handleClose() {
|
|||||||
function handleUp() {
|
function handleUp() {
|
||||||
const { length } = resultOptions.value;
|
const { length } = resultOptions.value;
|
||||||
if (length === 0) return;
|
if (length === 0) return;
|
||||||
const index = resultOptions.value.findIndex((item) => item.path === activePath.value);
|
const index = resultOptions.value.findIndex(item => item.path === activePath.value);
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
activePath.value = resultOptions.value[length - 1].path;
|
activePath.value = resultOptions.value[length - 1].path;
|
||||||
} else {
|
} else {
|
||||||
@@ -108,7 +108,7 @@ function handleUp() {
|
|||||||
function handleDown() {
|
function handleDown() {
|
||||||
const { length } = resultOptions.value;
|
const { length } = resultOptions.value;
|
||||||
if (length === 0) return;
|
if (length === 0) return;
|
||||||
const index = resultOptions.value.findIndex((item) => item.path === activePath.value);
|
const index = resultOptions.value.findIndex(item => item.path === activePath.value);
|
||||||
if (index + 1 === length) {
|
if (index + 1 === length) {
|
||||||
activePath.value = resultOptions.value[0].path;
|
activePath.value = resultOptions.value[0].path;
|
||||||
} else {
|
} else {
|
||||||
@@ -120,7 +120,7 @@ function handleDown() {
|
|||||||
function handleEnter() {
|
function handleEnter() {
|
||||||
const { length } = resultOptions.value;
|
const { length } = resultOptions.value;
|
||||||
if (length === 0 || activePath.value === '') return;
|
if (length === 0 || activePath.value === '') return;
|
||||||
const routeItem = resultOptions.value.find((item) => item.path === activePath.value);
|
const routeItem = resultOptions.value.find(item => item.path === activePath.value);
|
||||||
if (routeItem?.meta?.href) {
|
if (routeItem?.meta?.href) {
|
||||||
window.open(activePath.value, '__blank');
|
window.open(activePath.value, '__blank');
|
||||||
} else {
|
} else {
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
class="bg-[#e5e7eb] dark:bg-dark h-56px mt-8px px-14px rounded-4px cursor-pointer flex-y-center justify-between"
|
class="bg-[#e5e7eb] dark:bg-dark h-56px mt-8px px-14px rounded-4px cursor-pointer flex-y-center justify-between"
|
||||||
:style="{
|
:style="{
|
||||||
background: item.path === active ? theme.themeColor : '',
|
background: item.path === active ? theme.themeColor : '',
|
||||||
color: item.path === active ? '#fff' : '',
|
color: item.path === active ? '#fff' : ''
|
||||||
}"
|
}"
|
||||||
@click="handleTo"
|
@click="handleTo"
|
||||||
@mouseenter="handleMouse(item)"
|
@mouseenter="handleMouse(item)"
|
||||||
@@ -47,7 +47,7 @@ const active = computed({
|
|||||||
},
|
},
|
||||||
set(val: string) {
|
set(val: string) {
|
||||||
emit('update:value', val);
|
emit('update:value', val);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 鼠标移入 */
|
/** 鼠标移入 */
|
||||||
|
@@ -1,7 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<hover-container tooltip-content="搜索" class="w-40px h-full" @click="handleSearch">
|
<hover-container
|
||||||
<icon-uil-search class="text-20px text-[#666]" />
|
class="w-40px h-full"
|
||||||
|
tooltip-content="搜索"
|
||||||
|
content-class="hover:text-primary"
|
||||||
|
@click="handleSearch"
|
||||||
|
>
|
||||||
|
<icon-uil-search class="text-20px" />
|
||||||
</hover-container>
|
</hover-container>
|
||||||
<search-modal v-model:value="show" />
|
<search-modal v-model:value="show" />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,12 +2,12 @@
|
|||||||
<div class="mb-6px px-4px cursor-pointer" @mouseenter="setTrue" @mouseleave="setFalse">
|
<div class="mb-6px px-4px cursor-pointer" @mouseenter="setTrue" @mouseleave="setFalse">
|
||||||
<div
|
<div
|
||||||
class="flex-center flex-col py-12px rounded-2px bg-transparent transition-colors duration-300 ease-in-out"
|
class="flex-center flex-col py-12px rounded-2px bg-transparent transition-colors duration-300 ease-in-out"
|
||||||
:class="{ 'text-primary !bg-primary-active': isActive, 'text-primary': isHover }"
|
:class="{ 'text-primary !bg-primary_active': isActive, 'text-primary': isHover }"
|
||||||
>
|
>
|
||||||
<component :is="icon" :class="[isMini ? 'text-16px' : 'text-20px']" />
|
<component :is="icon" :class="[isMini ? 'text-16px' : 'text-20px']" />
|
||||||
<p
|
<p
|
||||||
class="pt-8px text-12px overflow-hidden transition-height duration-300 ease-in-out"
|
class="text-12px overflow-hidden transition-height duration-300 ease-in-out"
|
||||||
:class="[isMini ? 'h-0 pt-0' : 'h-20px pt-8px']"
|
:class="[isMini ? 'h-0 pt-0' : 'h-24px pt-4px']"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</p>
|
</p>
|
||||||
@@ -35,7 +35,7 @@ interface Props {
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
icon: undefined,
|
icon: undefined,
|
||||||
isMini: false,
|
isMini: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const { bool: isHover, setTrue, setFalse } = useBoolean();
|
const { bool: isHover, setTrue, setFalse } = useBoolean();
|
||||||
|
@@ -49,9 +49,9 @@ const route = useRoute();
|
|||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
const theme = useThemeStore();
|
const theme = useThemeStore();
|
||||||
const { routerPush } = useRouterPush();
|
const { routerPush } = useRouterPush();
|
||||||
|
const { title } = useAppInfo();
|
||||||
|
|
||||||
const showDrawer = computed(() => (props.visible && props.menus.length) || app.mixSiderFixed);
|
const showDrawer = computed(() => (props.visible && props.menus.length) || app.mixSiderFixed);
|
||||||
const { title } = useAppInfo();
|
|
||||||
|
|
||||||
const activeKey = computed(() => route.name as string);
|
const activeKey = computed(() => route.name as string);
|
||||||
const expandedKeys = ref<string[]>([]);
|
const expandedKeys = ref<string[]>([]);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<dark-mode-container class="flex h-full" @mouseleave="resetFirstDegreeMenus">
|
<dark-mode-container class="flex h-full" :inverted="theme.sider.inverted" @mouseleave="resetFirstDegreeMenus">
|
||||||
<div class="flex-1 flex-col-stretch h-full">
|
<div class="flex-1 flex-col-stretch h-full">
|
||||||
<global-logo :show-title="false" :style="{ height: theme.header.height + 'px' }" />
|
<global-logo :show-title="false" :style="{ height: theme.header.height + 'px' }" />
|
||||||
<n-scrollbar class="flex-1-hidden">
|
<n-scrollbar class="flex-1-hidden">
|
||||||
@@ -42,7 +42,7 @@ function setActiveParentRouteName(routeName: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const firstDegreeMenus = computed(() =>
|
const firstDegreeMenus = computed(() =>
|
||||||
routeStore.menus.map((item) => {
|
routeStore.menus.map(item => {
|
||||||
const { routeName, label } = item;
|
const { routeName, label } = item;
|
||||||
const icon = item?.icon;
|
const icon = item?.icon;
|
||||||
const hasChildren = Boolean(item.children && item.children.length);
|
const hasChildren = Boolean(item.children && item.children.length);
|
||||||
@@ -51,13 +51,13 @@ const firstDegreeMenus = computed(() =>
|
|||||||
routeName,
|
routeName,
|
||||||
label,
|
label,
|
||||||
icon,
|
icon,
|
||||||
hasChildren,
|
hasChildren
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
function getActiveParentRouteName() {
|
function getActiveParentRouteName() {
|
||||||
firstDegreeMenus.value.some((item) => {
|
firstDegreeMenus.value.some(item => {
|
||||||
const routeName = route.name as string;
|
const routeName = route.name as string;
|
||||||
const flag = routeName?.includes(item.routeName);
|
const flag = routeName?.includes(item.routeName);
|
||||||
if (flag) {
|
if (flag) {
|
||||||
@@ -83,7 +83,7 @@ function resetFirstDegreeMenus() {
|
|||||||
|
|
||||||
const activeChildMenus = computed(() => {
|
const activeChildMenus = computed(() => {
|
||||||
const menus: GlobalMenuOption[] = [];
|
const menus: GlobalMenuOption[] = [];
|
||||||
routeStore.menus.some((item) => {
|
routeStore.menus.some(item => {
|
||||||
const flag = item.routeName === activeParentRouteName.value && Boolean(item.children?.length);
|
const flag = item.routeName === activeParentRouteName.value && Boolean(item.children?.length);
|
||||||
if (flag) {
|
if (flag) {
|
||||||
menus.push(...(item.children || []));
|
menus.push(...(item.children || []));
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
:options="routeStore.menus"
|
:options="routeStore.menus"
|
||||||
:expanded-keys="expandedKeys"
|
:expanded-keys="expandedKeys"
|
||||||
:indent="18"
|
:indent="18"
|
||||||
|
:inverted="theme.sider.inverted"
|
||||||
@update:value="handleUpdateMenu"
|
@update:value="handleUpdateMenu"
|
||||||
@update:expanded-keys="handleUpdateExpandedKeys"
|
@update:expanded-keys="handleUpdateExpandedKeys"
|
||||||
/>
|
/>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<dark-mode-container class="flex-col-stretch h-full">
|
<dark-mode-container class="flex-col-stretch h-full" :inverted="theme.sider.inverted">
|
||||||
<global-logo v-if="!isHorizontalMix" :show-title="showTitle" :style="{ height: theme.header.height + 'px' }" />
|
<global-logo v-if="!isHorizontalMix" :show-title="showTitle" :style="{ height: theme.header.height + 'px' }" />
|
||||||
<vertical-menu />
|
<vertical-menu />
|
||||||
</dark-mode-container>
|
</dark-mode-container>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<vertical-sider v-if="!isVerticalMix" class="global-sider" />
|
<vertical-mix-sider v-if="isVerticalMix" class="global-sider" />
|
||||||
<vertical-mix-sider v-else class="global-sider" />
|
<vertical-sider v-else class="global-sider" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@@ -38,7 +38,7 @@ type Option = DropdownOption & {
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
visible: false,
|
visible: false,
|
||||||
currentPath: '',
|
currentPath: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
@@ -52,7 +52,7 @@ const dropdownVisible = computed({
|
|||||||
},
|
},
|
||||||
set(visible: boolean) {
|
set(visible: boolean) {
|
||||||
emit('update:visible', visible);
|
emit('update:visible', visible);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
@@ -64,29 +64,29 @@ const options = computed<Option[]>(() => [
|
|||||||
label: '重新加载',
|
label: '重新加载',
|
||||||
key: 'reload-current',
|
key: 'reload-current',
|
||||||
disabled: props.currentPath !== tab.activeTab,
|
disabled: props.currentPath !== tab.activeTab,
|
||||||
icon: iconifyRender('ant-design:reload-outlined'),
|
icon: iconifyRender('ant-design:reload-outlined')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '关闭',
|
label: '关闭',
|
||||||
key: 'close-current',
|
key: 'close-current',
|
||||||
disabled: props.currentPath === tab.homeTab.path,
|
disabled: props.currentPath === tab.homeTab.path,
|
||||||
icon: iconifyRender('ant-design:close-outlined'),
|
icon: iconifyRender('ant-design:close-outlined')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '关闭其他',
|
label: '关闭其他',
|
||||||
key: 'close-other',
|
key: 'close-other',
|
||||||
icon: iconifyRender('ant-design:column-width-outlined'),
|
icon: iconifyRender('ant-design:column-width-outlined')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '关闭左侧',
|
label: '关闭左侧',
|
||||||
key: 'close-left',
|
key: 'close-left',
|
||||||
icon: iconifyRender('mdi:format-horizontal-align-left'),
|
icon: iconifyRender('mdi:format-horizontal-align-left')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '关闭右侧',
|
label: '关闭右侧',
|
||||||
key: 'close-right',
|
key: 'close-right',
|
||||||
icon: iconifyRender('mdi:format-horizontal-align-right'),
|
icon: iconifyRender('mdi:format-horizontal-align-right')
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const actionMap = new Map<DropdownKey, () => void>([
|
const actionMap = new Map<DropdownKey, () => void>([
|
||||||
@@ -94,32 +94,32 @@ const actionMap = new Map<DropdownKey, () => void>([
|
|||||||
'reload-current',
|
'reload-current',
|
||||||
() => {
|
() => {
|
||||||
app.reloadPage();
|
app.reloadPage();
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'close-current',
|
'close-current',
|
||||||
() => {
|
() => {
|
||||||
tab.removeTab(props.currentPath);
|
tab.removeTab(props.currentPath);
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'close-other',
|
'close-other',
|
||||||
() => {
|
() => {
|
||||||
tab.clearTab([props.currentPath]);
|
tab.clearTab([props.currentPath]);
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'close-left',
|
'close-left',
|
||||||
() => {
|
() => {
|
||||||
tab.clearLeftTab(props.currentPath);
|
tab.clearLeftTab(props.currentPath);
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'close-right',
|
'close-right',
|
||||||
() => {
|
() => {
|
||||||
tab.clearRightTab(props.currentPath);
|
tab.clearRightTab(props.currentPath);
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function handleDropdown(optionKey: string) {
|
function handleDropdown(optionKey: string) {
|
||||||
|
@@ -28,7 +28,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, nextTick, watch } from 'vue';
|
import { ref, reactive, computed, nextTick, watch } from 'vue';
|
||||||
import { useEventListener } from '@vueuse/core';
|
import { useEventListener } from '@vueuse/core';
|
||||||
import { ChromeTab, ButtonTab } from 'soybean-admin-tab';
|
import { ChromeTab, ButtonTab } from '@soybeanjs/vue-admin-tab';
|
||||||
import { Icon } from '@iconify/vue';
|
import { Icon } from '@iconify/vue';
|
||||||
import { useThemeStore, useTabStore } from '@/store';
|
import { useThemeStore, useTabStore } from '@/store';
|
||||||
import { setTabRoutes } from '@/utils';
|
import { setTabRoutes } from '@/utils';
|
||||||
@@ -50,7 +50,7 @@ const activeComponent = computed(() => (isChromeMode.value ? ChromeTab : ButtonT
|
|||||||
const tabRef = ref<HTMLElement>();
|
const tabRef = ref<HTMLElement>();
|
||||||
async function getActiveTabClientX() {
|
async function getActiveTabClientX() {
|
||||||
await nextTick();
|
await nextTick();
|
||||||
if (tabRef.value) {
|
if (tabRef.value && tabRef.value.children.length && tabRef.value.children[tab.activeTabIndex]) {
|
||||||
const activeTabElement = tabRef.value.children[tab.activeTabIndex];
|
const activeTabElement = tabRef.value.children[tab.activeTabIndex];
|
||||||
const { x, width } = activeTabElement.getBoundingClientRect();
|
const { x, width } = activeTabElement.getBoundingClientRect();
|
||||||
const clientX = x + width / 2;
|
const clientX = x + width / 2;
|
||||||
@@ -64,7 +64,7 @@ const dropdown = reactive({
|
|||||||
visible: false,
|
visible: false,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
currentPath: '',
|
currentPath: ''
|
||||||
});
|
});
|
||||||
function showDropdown() {
|
function showDropdown() {
|
||||||
dropdown.visible = true;
|
dropdown.visible = true;
|
||||||
@@ -92,7 +92,7 @@ watch(
|
|||||||
getActiveTabClientX();
|
getActiveTabClientX();
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
type="primary"
|
type="primary"
|
||||||
:class="[{ '!right-330px': app.settingDrawerVisible }, app.settingDrawerVisible ? 'ease-out' : 'ease-in']"
|
:class="[{ '!right-330px': app.settingDrawerVisible }, app.settingDrawerVisible ? 'ease-out' : 'ease-in']"
|
||||||
class="fixed top-240px right-14px z-10000 w-42px h-42px !p-0 transition-all duration-300"
|
class="fixed top-240px right-14px z-10000 w-42px h-42px !p-0 transition-all duration-300"
|
||||||
@click="app.toggleSettingdrawerVisible"
|
@click="app.toggleSettingDrawerVisible"
|
||||||
>
|
>
|
||||||
<icon-ant-design-close-outlined v-if="app.settingDrawerVisible" class="text-24px" />
|
<icon-ant-design-close-outlined v-if="app.settingDrawerVisible" class="text-24px" />
|
||||||
<icon-ant-design-setting-outlined v-else class="text-24px" />
|
<icon-ant-design-setting-outlined v-else class="text-24px" />
|
||||||
|
@@ -44,23 +44,23 @@ const layoutConfig: LayoutConfig = {
|
|||||||
vertical: {
|
vertical: {
|
||||||
placement: 'bottom-start',
|
placement: 'bottom-start',
|
||||||
menuClass: 'w-1/3 h-full',
|
menuClass: 'w-1/3 h-full',
|
||||||
mainClass: 'w-2/3 h-3/4',
|
mainClass: 'w-2/3 h-3/4'
|
||||||
},
|
},
|
||||||
'vertical-mix': {
|
'vertical-mix': {
|
||||||
placement: 'bottom',
|
placement: 'bottom',
|
||||||
menuClass: 'w-1/4 h-full',
|
menuClass: 'w-1/4 h-full',
|
||||||
mainClass: 'w-2/3 h-3/4',
|
mainClass: 'w-2/3 h-3/4'
|
||||||
},
|
},
|
||||||
horizontal: {
|
horizontal: {
|
||||||
placement: 'bottom',
|
placement: 'bottom',
|
||||||
menuClass: 'w-full h-1/4',
|
menuClass: 'w-full h-1/4',
|
||||||
mainClass: 'w-full h-3/4',
|
mainClass: 'w-full h-3/4'
|
||||||
},
|
},
|
||||||
'horizontal-mix': {
|
'horizontal-mix': {
|
||||||
placement: 'bottom-end',
|
placement: 'bottom-end',
|
||||||
menuClass: 'w-full h-1/4',
|
menuClass: 'w-full h-1/4',
|
||||||
mainClass: 'w-2/3 h-3/4',
|
mainClass: 'w-2/3 h-3/4'
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const activeConfig = computed(() => layoutConfig[props.mode]);
|
const activeConfig = computed(() => layoutConfig[props.mode]);
|
||||||
|
@@ -1,6 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-divider title-placement="center">界面功能</n-divider>
|
<n-divider title-placement="center">界面功能</n-divider>
|
||||||
<n-space vertical size="large">
|
<n-space vertical size="large">
|
||||||
|
<setting-menu label="侧边栏反转色">
|
||||||
|
<n-switch :value="theme.sider.inverted" @update:value="theme.setSiderInverted" />
|
||||||
|
</setting-menu>
|
||||||
|
<setting-menu label="头部反转色">
|
||||||
|
<n-switch :value="theme.header.inverted" @update:value="theme.setHeaderInverted" />
|
||||||
|
</setting-menu>
|
||||||
<setting-menu label="固定头部和多页签">
|
<setting-menu label="固定头部和多页签">
|
||||||
<n-switch :value="theme.fixedHeaderAndTab" @update:value="theme.setIsFixedHeaderAndTab" />
|
<n-switch :value="theme.fixedHeaderAndTab" @update:value="theme.setIsFixedHeaderAndTab" />
|
||||||
</setting-menu>
|
</setting-menu>
|
||||||
|
@@ -17,7 +17,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
iconClass: 'text-14px',
|
iconClass: 'text-14px'
|
||||||
});
|
});
|
||||||
|
|
||||||
const whiteColors = ['#ffffff', '#fff', 'rgb(255,255,255)'];
|
const whiteColors = ['#ffffff', '#fff', 'rgb(255,255,255)'];
|
||||||
|
@@ -36,7 +36,7 @@ function clipboardEventListener() {
|
|||||||
window.$dialog?.success({
|
window.$dialog?.success({
|
||||||
title: '操作成功',
|
title: '操作成功',
|
||||||
content: '复制成功,请替换 src/settings/theme.json的内容!',
|
content: '复制成功,请替换 src/settings/theme.json的内容!',
|
||||||
positiveText: '确定',
|
positiveText: '确定'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
21
src/main.ts
21
src/main.ts
@@ -1,29 +1,26 @@
|
|||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import { setupAssets, setupNaiveUI } from '@/plugins';
|
import { setupAssets } from './plugins';
|
||||||
import { setupRouter } from '@/router';
|
import { setupStore } from './store';
|
||||||
import { setupStore } from '@/store';
|
import { setupDirectives } from './directives';
|
||||||
import { setupDirectives } from '@/directives';
|
import { setupRouter } from './router';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
|
|
||||||
async function setupApp() {
|
async function setupApp() {
|
||||||
// 引入静态资源
|
// import assets: js、css
|
||||||
setupAssets();
|
setupAssets();
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
// 挂载pinia状态
|
// store plugin: pinia
|
||||||
setupStore(app);
|
setupStore(app);
|
||||||
|
|
||||||
// 挂载自定义vue指令
|
// vue custom directives
|
||||||
setupDirectives(app);
|
setupDirectives(app);
|
||||||
|
|
||||||
// 按需引入naiveUI
|
// vue router
|
||||||
setupNaiveUI(app);
|
|
||||||
|
|
||||||
// 挂载路由
|
|
||||||
await setupRouter(app);
|
await setupRouter(app);
|
||||||
|
|
||||||
// 路由准备就绪后挂载 App
|
// mount app
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import 'virtual:windi.css';
|
import 'uno.css';
|
||||||
import 'swiper/css';
|
import 'swiper/css';
|
||||||
import 'swiper/css/navigation';
|
import 'swiper/css/navigation';
|
||||||
import 'swiper/css/pagination';
|
import 'swiper/css/pagination';
|
||||||
import '../styles/css/global.css';
|
import '../styles/css/global.css';
|
||||||
|
|
||||||
/** 引入静态资源(全局引入css、字体等) */
|
/** import static assets: css, js , font and so on. - [引入静态资源,css、js和字体文件等] */
|
||||||
export default function setupAssets() {
|
export default function setupAssets() {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import setupAssets from './assets';
|
import setupAssets from './assets';
|
||||||
import setupNaiveUI from './naive';
|
|
||||||
|
|
||||||
export { setupAssets, setupNaiveUI };
|
export { setupAssets };
|
||||||
|
@@ -1,103 +0,0 @@
|
|||||||
import type { App } from 'vue';
|
|
||||||
import {
|
|
||||||
create,
|
|
||||||
NBreadcrumb,
|
|
||||||
NBreadcrumbItem,
|
|
||||||
NButton,
|
|
||||||
NCard,
|
|
||||||
NCheckbox,
|
|
||||||
NColorPicker,
|
|
||||||
NConfigProvider,
|
|
||||||
NDataTable,
|
|
||||||
NDescriptions,
|
|
||||||
NDescriptionsItem,
|
|
||||||
NDialogProvider,
|
|
||||||
NDivider,
|
|
||||||
NDrawer,
|
|
||||||
NDrawerContent,
|
|
||||||
NDropdown,
|
|
||||||
NEmpty,
|
|
||||||
NForm,
|
|
||||||
NFormItem,
|
|
||||||
NGradientText,
|
|
||||||
NGrid,
|
|
||||||
NGridItem,
|
|
||||||
NInput,
|
|
||||||
NInputGroup,
|
|
||||||
NInputNumber,
|
|
||||||
NList,
|
|
||||||
NListItem,
|
|
||||||
NLoadingBarProvider,
|
|
||||||
NModal,
|
|
||||||
NMenu,
|
|
||||||
NMessageProvider,
|
|
||||||
NNotificationProvider,
|
|
||||||
NPopover,
|
|
||||||
NScrollbar,
|
|
||||||
NSelect,
|
|
||||||
NSpace,
|
|
||||||
NStatistic,
|
|
||||||
NSwitch,
|
|
||||||
NSpin,
|
|
||||||
NTabs,
|
|
||||||
NTabPane,
|
|
||||||
NTag,
|
|
||||||
NThing,
|
|
||||||
NTimeline,
|
|
||||||
NTimelineItem,
|
|
||||||
NTooltip,
|
|
||||||
} from 'naive-ui';
|
|
||||||
|
|
||||||
/** 按需引入naiveUI */
|
|
||||||
export default function setupNaiveUI(app: App) {
|
|
||||||
const naive = create({
|
|
||||||
components: [
|
|
||||||
NBreadcrumb,
|
|
||||||
NBreadcrumbItem,
|
|
||||||
NButton,
|
|
||||||
NCard,
|
|
||||||
NCheckbox,
|
|
||||||
NColorPicker,
|
|
||||||
NConfigProvider,
|
|
||||||
NDataTable,
|
|
||||||
NDescriptions,
|
|
||||||
NDescriptionsItem,
|
|
||||||
NDialogProvider,
|
|
||||||
NDivider,
|
|
||||||
NDrawer,
|
|
||||||
NDrawerContent,
|
|
||||||
NDropdown,
|
|
||||||
NEmpty,
|
|
||||||
NForm,
|
|
||||||
NFormItem,
|
|
||||||
NGradientText,
|
|
||||||
NGrid,
|
|
||||||
NGridItem,
|
|
||||||
NInput,
|
|
||||||
NInputGroup,
|
|
||||||
NInputNumber,
|
|
||||||
NList,
|
|
||||||
NListItem,
|
|
||||||
NLoadingBarProvider,
|
|
||||||
NModal,
|
|
||||||
NMenu,
|
|
||||||
NMessageProvider,
|
|
||||||
NNotificationProvider,
|
|
||||||
NPopover,
|
|
||||||
NScrollbar,
|
|
||||||
NSelect,
|
|
||||||
NSpace,
|
|
||||||
NStatistic,
|
|
||||||
NSwitch,
|
|
||||||
NSpin,
|
|
||||||
NTabs,
|
|
||||||
NTabPane,
|
|
||||||
NTag,
|
|
||||||
NThing,
|
|
||||||
NTimeline,
|
|
||||||
NTimelineItem,
|
|
||||||
NTooltip,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
app.use(naive);
|
|
||||||
}
|
|
@@ -15,9 +15,9 @@ export async function createDynamicRouteGuard(
|
|||||||
const route = useRouteStore();
|
const route = useRouteStore();
|
||||||
const isLogin = Boolean(getToken());
|
const isLogin = Boolean(getToken());
|
||||||
|
|
||||||
// 初始化动态路由
|
// 初始化权限路由
|
||||||
if (!route.isAddedDynamicRoute) {
|
if (!route.isInitAuthRoute) {
|
||||||
// 未登录情况下直接回到登录页,登录成功后再加载动态路由
|
// 未登录情况下直接回到登录页,登录成功后再加载权限路由
|
||||||
if (!isLogin) {
|
if (!isLogin) {
|
||||||
if (to.name === routeName('login')) {
|
if (to.name === routeName('login')) {
|
||||||
next();
|
next();
|
||||||
@@ -28,18 +28,19 @@ export async function createDynamicRouteGuard(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await route.initDynamicRoute(router);
|
await route.initAuthRoute(router);
|
||||||
|
|
||||||
if (to.name === routeName('not-found-page')) {
|
if (to.name === routeName('not-found-page')) {
|
||||||
// 动态路由没有加载导致被not-found-page路由捕获,等待动态路由加载好了,回到之前的路由
|
// 动态路由没有加载导致被not-found-page路由捕获,等待权限路由加载好了,回到之前的路由
|
||||||
next({ path: to.fullPath, replace: true, query: to.query });
|
next({ path: to.fullPath, replace: true, query: to.query });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// 动态路由已经加载,仍然未找到,重定向到not-found
|
}
|
||||||
if (to.name === routeName('not-found-page')) {
|
|
||||||
next({ name: routeName('not-found'), replace: true });
|
// 权限路由已经加载,仍然未找到,重定向到not-found
|
||||||
return false;
|
if (to.name === routeName('not-found-page')) {
|
||||||
}
|
next({ name: routeName('not-found'), replace: true });
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user