mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-28 05:39:34 +08:00
Compare commits
247 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b92ac5c36d | ||
![]() |
504070f3eb | ||
![]() |
feab6b3b30 | ||
![]() |
2d4ac33046 | ||
![]() |
17e2a02281 | ||
![]() |
096545c5a1 | ||
![]() |
04dff33ac5 | ||
![]() |
cfa18c2b8e | ||
![]() |
13354955db | ||
![]() |
bb683804f4 | ||
![]() |
e2a577de24 | ||
![]() |
89d963c81a | ||
![]() |
b37ed48b9d | ||
![]() |
4b9cfcb867 | ||
![]() |
f86c9f90ad | ||
![]() |
31a6ab59fb | ||
![]() |
34789645f7 | ||
![]() |
f380452ef0 | ||
![]() |
decd9c55e5 | ||
![]() |
e815f0ff89 | ||
![]() |
5ea6b4a8d8 | ||
![]() |
a53ca3faf1 | ||
![]() |
86fdd6c93b | ||
![]() |
0e0661fe02 | ||
![]() |
86ce65e0ea | ||
![]() |
c3eb4fab13 | ||
![]() |
7a476372e1 | ||
![]() |
5e421ce607 | ||
![]() |
1d8676f456 | ||
![]() |
0c3dd92592 | ||
![]() |
d33261d0c2 | ||
![]() |
7041c6a106 | ||
![]() |
12ffb310bf | ||
![]() |
d9799fec70 | ||
![]() |
4570d5b54b | ||
![]() |
d49e3e81a4 | ||
![]() |
579b1b486c | ||
![]() |
eba372062e | ||
![]() |
c9ccd2bbab | ||
![]() |
5aff8bac10 | ||
![]() |
1a12687027 | ||
![]() |
a221d2b491 | ||
![]() |
ccd99eb24d | ||
![]() |
c5c6760b5d | ||
![]() |
c07281bf41 | ||
![]() |
24bad09c74 | ||
![]() |
cddf71e600 | ||
![]() |
9f82052c71 | ||
![]() |
e0eb57d38d | ||
![]() |
b6b97accb1 | ||
![]() |
799934171a | ||
![]() |
10ebf03698 | ||
![]() |
cd258fbb52 | ||
![]() |
6cba181fad | ||
![]() |
f9504cece3 | ||
![]() |
182f1c9da8 | ||
![]() |
e7b009786b | ||
![]() |
5262233312 | ||
![]() |
a9f9031f49 | ||
![]() |
061fcf926d | ||
![]() |
7e7a5f3fd4 | ||
![]() |
f8bb396dc4 | ||
![]() |
a832edce0d | ||
![]() |
67d1f299b3 | ||
![]() |
cb7c0ecaa2 | ||
![]() |
e225159cce | ||
![]() |
195ceec9b4 | ||
![]() |
5bd73867b6 | ||
![]() |
22e6f28464 | ||
![]() |
5611f6c7f5 | ||
![]() |
3f0f4d50a1 | ||
![]() |
2d0859a727 | ||
![]() |
509b268fba | ||
![]() |
c3129663eb | ||
![]() |
816d1f5a69 | ||
![]() |
8cc903c0e1 | ||
![]() |
13087a10b7 | ||
![]() |
27a3888e35 | ||
![]() |
fb0ec05ff8 | ||
![]() |
76c4aa2c55 | ||
![]() |
e1c503e51e | ||
![]() |
5965755caa | ||
![]() |
1ad54561b0 | ||
![]() |
42e322012c | ||
![]() |
8cf6e8ec75 | ||
![]() |
79d4d2fb22 | ||
![]() |
b785bc5704 | ||
![]() |
6719e2679f | ||
![]() |
cb9c8db5ba | ||
![]() |
a2637313f8 | ||
![]() |
467689525f | ||
![]() |
1a04a05b79 | ||
![]() |
b8bffd884c | ||
![]() |
624beb6fa0 | ||
![]() |
7606b86854 | ||
![]() |
e10cbe23b9 | ||
![]() |
d34838bdd8 | ||
![]() |
c979c23e6b | ||
![]() |
516d0b8dc8 | ||
![]() |
99c7fd72f8 | ||
![]() |
2828e7a7b6 | ||
![]() |
16162c01ed | ||
![]() |
bbbdbfa912 | ||
![]() |
06cccc53fa | ||
![]() |
801c640724 | ||
![]() |
081d2aed23 | ||
![]() |
4d81b9d18d | ||
![]() |
e9dc613548 | ||
![]() |
3af22f7e91 | ||
![]() |
2135cb8ece | ||
![]() |
376aad5d26 | ||
![]() |
27ba45aa75 | ||
![]() |
de17007788 | ||
![]() |
e86a7906fe | ||
![]() |
4a8e6abc06 | ||
![]() |
2eb7fed9f4 | ||
![]() |
ff8d5ca351 | ||
![]() |
07c4ad05f4 | ||
![]() |
548c2e5500 | ||
![]() |
ec2c6eff6f | ||
![]() |
15fe82c62f | ||
![]() |
cb5ecf4a8a | ||
![]() |
68a7e790d8 | ||
![]() |
24a4935e85 | ||
![]() |
9a660827a6 | ||
![]() |
a44ff73dd3 | ||
![]() |
acd87b2250 | ||
![]() |
1853ba1d60 | ||
![]() |
85cbb3b842 | ||
![]() |
968c44572a | ||
![]() |
3201b843a8 | ||
![]() |
db5b727300 | ||
![]() |
10b3a16f79 | ||
![]() |
a97c998be5 | ||
![]() |
b22d900e27 | ||
![]() |
181e38733c | ||
![]() |
4fe44611d3 | ||
![]() |
593916d6aa | ||
![]() |
38805a0e1f | ||
![]() |
0f756503ff | ||
![]() |
f6faeb034e | ||
![]() |
2efb5b71c3 | ||
![]() |
22c1f86ca1 | ||
![]() |
ce4af37fd8 | ||
![]() |
f446cbf9e5 | ||
![]() |
7581fb381f | ||
![]() |
bedf19993d | ||
![]() |
e558087bcf | ||
![]() |
698daf46c7 | ||
![]() |
0410f1e1be | ||
![]() |
7fbf7b189a | ||
![]() |
be208fe915 | ||
![]() |
1d3729aa24 | ||
![]() |
cbca9ffd95 | ||
![]() |
ed465d2b5b | ||
![]() |
d308da6ba1 | ||
![]() |
7c4dfdc1c2 | ||
![]() |
991ada31ba | ||
![]() |
43adc943b9 | ||
![]() |
4a20156f3d | ||
![]() |
eec6f41f6a | ||
![]() |
2cc918f79d | ||
![]() |
07b1ad121c | ||
![]() |
e419b03cab | ||
![]() |
018ddc75c6 | ||
![]() |
d085736bac | ||
![]() |
305549e7f2 | ||
![]() |
958c8b4f21 | ||
![]() |
373766691f | ||
![]() |
bac0275624 | ||
![]() |
0fc0f13064 | ||
![]() |
b75a8e6a2b | ||
![]() |
68ab73bdb5 | ||
![]() |
4c1fc4a11e | ||
![]() |
03f166f8a4 | ||
![]() |
d42daf9ce0 | ||
![]() |
d1862fba27 | ||
![]() |
f0db3d6b79 | ||
![]() |
21d37a1be0 | ||
![]() |
fe236ea929 | ||
![]() |
05b4b61c6e | ||
![]() |
7ab00250bf | ||
![]() |
9896a67c21 | ||
![]() |
db38ef522f | ||
![]() |
845f2a2abd | ||
![]() |
9b73792dc9 | ||
![]() |
fccbe44cf7 | ||
![]() |
e23486dbc6 | ||
![]() |
935df713f3 | ||
![]() |
17c7ce8f21 | ||
![]() |
24b9aa44d2 | ||
![]() |
014e6d38a0 | ||
![]() |
12f216c0e7 | ||
![]() |
ae3f7cb909 | ||
![]() |
32117b73aa | ||
![]() |
e8992a1d16 | ||
![]() |
3c4af23edf | ||
![]() |
e3a93970f4 | ||
![]() |
7b9866158b | ||
![]() |
3fb286b552 | ||
![]() |
253abc5ef1 | ||
![]() |
5f55799572 | ||
![]() |
54a9ff088f | ||
![]() |
73502677ff | ||
![]() |
dedba18553 | ||
![]() |
f85badf482 | ||
![]() |
12f25cf3a2 | ||
![]() |
c8dd9bbf0b | ||
![]() |
3587ec54eb | ||
![]() |
dbcb7138f2 | ||
![]() |
fe58af2e78 | ||
![]() |
94c68c966e | ||
![]() |
77083abcc5 | ||
![]() |
1302092798 | ||
![]() |
ec53bf8084 | ||
![]() |
b87d41bada | ||
![]() |
788a29a8cb | ||
![]() |
3bd5ef4523 | ||
![]() |
86e52ce58a | ||
![]() |
9ddaba5333 | ||
![]() |
5b079471b9 | ||
![]() |
8cc73cf59c | ||
![]() |
a89711610d | ||
![]() |
67c2b13713 | ||
![]() |
1ff1e4a8d7 | ||
![]() |
ea8af98324 | ||
![]() |
dc15accd04 | ||
![]() |
94efcec7da | ||
![]() |
a3d0d2ed34 | ||
![]() |
90dc00b168 | ||
![]() |
ba36ce8836 | ||
![]() |
57d5a919d2 | ||
![]() |
546c0928fb | ||
![]() |
5e44aa9283 | ||
![]() |
26bec4222f | ||
![]() |
4005023fd4 | ||
![]() |
8617d3dd1e | ||
![]() |
632081e828 | ||
![]() |
6b9acf09dc | ||
![]() |
2c6edafeb2 | ||
![]() |
9cf0573921 | ||
![]() |
da7d61b160 | ||
![]() |
8f1e397077 | ||
![]() |
dcdebaf7ca | ||
![]() |
4e88ef0840 | ||
![]() |
33ce4d3cf3 | ||
![]() |
6b54cb7563 |
18
.github/CODEOWNERS
vendored
18
.github/CODEOWNERS
vendored
@@ -1,14 +1,14 @@
|
|||||||
# default onwer
|
# default onwer
|
||||||
* anncwb@126.com vince292007@gmail.com
|
* anncwb@126.com vince292007@gmail.com netfan@foxmail.com
|
||||||
|
|
||||||
# vben core onwer
|
# vben core onwer
|
||||||
/.github/ anncwb@126.com vince292007@gmail.com
|
/.github/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
|
||||||
/.vscode/ anncwb@126.com vince292007@gmail.com
|
/.vscode/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
|
||||||
/packages/ anncwb@126.com vince292007@gmail.com
|
/packages/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
|
||||||
/packages/@core/ anncwb@126.com vince292007@gmail.com
|
/packages/@core/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
|
||||||
/internal/ anncwb@126.com vince292007@gmail.com
|
/internal/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
|
||||||
/scripts/ anncwb@126.com vince292007@gmail.com
|
/scripts/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
|
||||||
|
|
||||||
# vben team onwer
|
# vben team onwer
|
||||||
apps/ anncwb@126.com vince292007@gmail.com @vbenjs/team-v5
|
apps/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5
|
||||||
docs/ anncwb@126.com vince292007@gmail.com @vbenjs/team-v5
|
docs/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5
|
||||||
|
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -62,7 +62,7 @@ body:
|
|||||||
description: Before submitting the issue, please make sure you do the following
|
description: Before submitting the issue, please make sure you do the following
|
||||||
# description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com).
|
# description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com).
|
||||||
options:
|
options:
|
||||||
- label: Read the [docs](https://anncwb.github.io/vue-vben-admin-doc/)
|
- label: Read the [docs](https://doc.vben.pro/)
|
||||||
required: true
|
required: true
|
||||||
- label: Ensure the code is up to date. (Some issues have been fixed in the latest version)
|
- label: Ensure the code is up to date. (Some issues have been fixed in the latest version)
|
||||||
required: true
|
required: true
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -62,7 +62,7 @@ body:
|
|||||||
label: Validations
|
label: Validations
|
||||||
description: Before submitting the issue, please make sure you do the following
|
description: Before submitting the issue, please make sure you do the following
|
||||||
options:
|
options:
|
||||||
- label: Read the [docs](https://anncwb.github.io/vue-vben-admin-doc/)
|
- label: Read the [docs](https://doc.vben.pro/)
|
||||||
required: true
|
required: true
|
||||||
- label: Ensure the code is up to date. (Some issues have been fixed in the latest version)
|
- label: Ensure the code is up to date. (Some issues have been fixed in the latest version)
|
||||||
required: true
|
required: true
|
||||||
|
17
.github/workflows/deploy.yml
vendored
17
.github/workflows/deploy.yml
vendored
@@ -153,3 +153,20 @@ jobs:
|
|||||||
username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }}
|
username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }}
|
||||||
password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }}
|
password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }}
|
||||||
local-dir: ./apps/web-naive/dist/
|
local-dir: ./apps/web-naive/dist/
|
||||||
|
|
||||||
|
rerun-on-failure:
|
||||||
|
name: Rerun on failure
|
||||||
|
needs:
|
||||||
|
- deploy-playground-ftp
|
||||||
|
- deploy-docs-ftp
|
||||||
|
- deploy-antd-ftp
|
||||||
|
- deploy-ele-ftp
|
||||||
|
- deploy-naive-ftp
|
||||||
|
if: failure() && fromJSON(github.run_attempt) < 10
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Retry ${{ fromJSON(github.run_attempt) }} of 10
|
||||||
|
env:
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: gh workflow run rerun.yml -F run_id=${{ github.run_id }}
|
||||||
|
19
.github/workflows/rerun.yml
vendored
Normal file
19
.github/workflows/rerun.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: Rerun workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
run_id:
|
||||||
|
description: The workflow id to relanch
|
||||||
|
required: true
|
||||||
|
jobs:
|
||||||
|
rerun:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: rerun ${{ inputs.run_id }}
|
||||||
|
env:
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
gh run watch ${{ inputs.run_id }} > /dev/null 2>&1
|
||||||
|
gh run rerun ${{ inputs.run_id }} --failed
|
@@ -1,4 +1,10 @@
|
|||||||
export default {
|
export default {
|
||||||
|
'*.md': ['prettier --cache --ignore-unknown --write'],
|
||||||
|
'*.vue': [
|
||||||
|
'prettier --write',
|
||||||
|
'eslint --cache --fix',
|
||||||
|
'stylelint --fix --allow-empty-input',
|
||||||
|
],
|
||||||
'*.{js,jsx,ts,tsx}': [
|
'*.{js,jsx,ts,tsx}': [
|
||||||
'prettier --cache --ignore-unknown --write',
|
'prettier --cache --ignore-unknown --write',
|
||||||
'eslint --cache --fix',
|
'eslint --cache --fix',
|
||||||
@@ -7,14 +13,8 @@ export default {
|
|||||||
'prettier --cache --ignore-unknown --write',
|
'prettier --cache --ignore-unknown --write',
|
||||||
'stylelint --fix --allow-empty-input',
|
'stylelint --fix --allow-empty-input',
|
||||||
],
|
],
|
||||||
'*.md': ['prettier --cache --ignore-unknown --write'],
|
'package.json': ['prettier --cache --write'],
|
||||||
'*.vue': [
|
|
||||||
'prettier --write',
|
|
||||||
'eslint --cache --fix',
|
|
||||||
'stylelint --fix --allow-empty-input',
|
|
||||||
],
|
|
||||||
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [
|
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [
|
||||||
'prettier --cache --write--parser json',
|
'prettier --cache --write--parser json',
|
||||||
],
|
],
|
||||||
'package.json': ['prettier --cache --write'],
|
|
||||||
};
|
};
|
||||||
|
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
@@ -9,7 +9,7 @@
|
|||||||
"url": "http://localhost:5555",
|
"url": "http://localhost:5555",
|
||||||
"env": { "NODE_ENV": "development" },
|
"env": { "NODE_ENV": "development" },
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}/playground"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"url": "http://localhost:5666",
|
"url": "http://localhost:5666",
|
||||||
"env": { "NODE_ENV": "development" },
|
"env": { "NODE_ENV": "development" },
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}/apps/web-antd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"url": "http://localhost:5777",
|
"url": "http://localhost:5777",
|
||||||
"env": { "NODE_ENV": "development" },
|
"env": { "NODE_ENV": "development" },
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}/apps/web-ele"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"url": "http://localhost:5888",
|
"url": "http://localhost:5888",
|
||||||
"env": { "NODE_ENV": "development" },
|
"env": { "NODE_ENV": "development" },
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}/apps/web-naive"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -160,6 +160,7 @@
|
|||||||
"stylelint.enable": true,
|
"stylelint.enable": true,
|
||||||
"stylelint.packageManager": "pnpm",
|
"stylelint.packageManager": "pnpm",
|
||||||
"stylelint.validate": ["css", "less", "postcss", "scss", "vue"],
|
"stylelint.validate": ["css", "less", "postcss", "scss", "vue"],
|
||||||
|
"stylelint.customSyntax": "postcss-html",
|
||||||
"stylelint.snippet": ["css", "less", "postcss", "scss", "vue"],
|
"stylelint.snippet": ["css", "less", "postcss", "scss", "vue"],
|
||||||
|
|
||||||
"typescript.inlayHints.enumMemberValues.enabled": true,
|
"typescript.inlayHints.enumMemberValues.enabled": true,
|
||||||
@@ -221,5 +222,6 @@
|
|||||||
"commentTranslate.hover.enabled": false,
|
"commentTranslate.hover.enabled": false,
|
||||||
"commentTranslate.multiLineMerge": true,
|
"commentTranslate.multiLineMerge": true,
|
||||||
"vue.server.hybridMode": true,
|
"vue.server.hybridMode": true,
|
||||||
"typescript.tsdk": "node_modules/typescript/lib"
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"oxc.enable": false
|
||||||
}
|
}
|
||||||
|
15
apps/backend-mock/api/system/dept/.post.ts
Normal file
15
apps/backend-mock/api/system/dept/.post.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||||
|
import {
|
||||||
|
sleep,
|
||||||
|
unAuthorizedResponse,
|
||||||
|
useResponseSuccess,
|
||||||
|
} from '~/utils/response';
|
||||||
|
|
||||||
|
export default eventHandler(async (event) => {
|
||||||
|
const userinfo = verifyAccessToken(event);
|
||||||
|
if (!userinfo) {
|
||||||
|
return unAuthorizedResponse(event);
|
||||||
|
}
|
||||||
|
await sleep(600);
|
||||||
|
return useResponseSuccess(null);
|
||||||
|
});
|
15
apps/backend-mock/api/system/dept/[id].delete.ts
Normal file
15
apps/backend-mock/api/system/dept/[id].delete.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||||
|
import {
|
||||||
|
sleep,
|
||||||
|
unAuthorizedResponse,
|
||||||
|
useResponseSuccess,
|
||||||
|
} from '~/utils/response';
|
||||||
|
|
||||||
|
export default eventHandler(async (event) => {
|
||||||
|
const userinfo = verifyAccessToken(event);
|
||||||
|
if (!userinfo) {
|
||||||
|
return unAuthorizedResponse(event);
|
||||||
|
}
|
||||||
|
await sleep(1000);
|
||||||
|
return useResponseSuccess(null);
|
||||||
|
});
|
15
apps/backend-mock/api/system/dept/[id].put.ts
Normal file
15
apps/backend-mock/api/system/dept/[id].put.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||||
|
import {
|
||||||
|
sleep,
|
||||||
|
unAuthorizedResponse,
|
||||||
|
useResponseSuccess,
|
||||||
|
} from '~/utils/response';
|
||||||
|
|
||||||
|
export default eventHandler(async (event) => {
|
||||||
|
const userinfo = verifyAccessToken(event);
|
||||||
|
if (!userinfo) {
|
||||||
|
return unAuthorizedResponse(event);
|
||||||
|
}
|
||||||
|
await sleep(2000);
|
||||||
|
return useResponseSuccess(null);
|
||||||
|
});
|
61
apps/backend-mock/api/system/dept/list.ts
Normal file
61
apps/backend-mock/api/system/dept/list.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||||
|
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
|
||||||
|
|
||||||
|
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
|
||||||
|
timeZone: 'Asia/Shanghai',
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
});
|
||||||
|
|
||||||
|
function generateMockDataList(count: number) {
|
||||||
|
const dataList = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const dataItem: Record<string, any> = {
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
pid: 0,
|
||||||
|
name: faker.commerce.department(),
|
||||||
|
status: faker.helpers.arrayElement([0, 1]),
|
||||||
|
createTime: formatterCN.format(
|
||||||
|
faker.date.between({ from: '2021-01-01', to: '2022-12-31' }),
|
||||||
|
),
|
||||||
|
remark: faker.lorem.sentence(),
|
||||||
|
};
|
||||||
|
if (faker.datatype.boolean()) {
|
||||||
|
dataItem.children = Array.from(
|
||||||
|
{ length: faker.number.int({ min: 1, max: 5 }) },
|
||||||
|
() => ({
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
pid: dataItem.id,
|
||||||
|
name: faker.commerce.department(),
|
||||||
|
status: faker.helpers.arrayElement([0, 1]),
|
||||||
|
createTime: formatterCN.format(
|
||||||
|
faker.date.between({ from: '2023-01-01', to: '2023-12-31' }),
|
||||||
|
),
|
||||||
|
remark: faker.lorem.sentence(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
dataList.push(dataItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataList;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockData = generateMockDataList(10);
|
||||||
|
|
||||||
|
export default eventHandler(async (event) => {
|
||||||
|
const userinfo = verifyAccessToken(event);
|
||||||
|
if (!userinfo) {
|
||||||
|
return unAuthorizedResponse(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
const listData = structuredClone(mockData);
|
||||||
|
|
||||||
|
return useResponseSuccess(listData);
|
||||||
|
});
|
12
apps/backend-mock/api/system/menu/list.ts
Normal file
12
apps/backend-mock/api/system/menu/list.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||||
|
import { MOCK_MENU_LIST } from '~/utils/mock-data';
|
||||||
|
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
|
||||||
|
|
||||||
|
export default eventHandler(async (event) => {
|
||||||
|
const userinfo = verifyAccessToken(event);
|
||||||
|
if (!userinfo) {
|
||||||
|
return unAuthorizedResponse(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return useResponseSuccess(MOCK_MENU_LIST);
|
||||||
|
});
|
28
apps/backend-mock/api/system/menu/name-exists.ts
Normal file
28
apps/backend-mock/api/system/menu/name-exists.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||||
|
import { MOCK_MENU_LIST } from '~/utils/mock-data';
|
||||||
|
import { unAuthorizedResponse } from '~/utils/response';
|
||||||
|
|
||||||
|
const namesMap: Record<string, any> = {};
|
||||||
|
|
||||||
|
function getNames(menus: any[]) {
|
||||||
|
menus.forEach((menu) => {
|
||||||
|
namesMap[menu.name] = String(menu.id);
|
||||||
|
if (menu.children) {
|
||||||
|
getNames(menu.children);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getNames(MOCK_MENU_LIST);
|
||||||
|
|
||||||
|
export default eventHandler(async (event) => {
|
||||||
|
const userinfo = verifyAccessToken(event);
|
||||||
|
if (!userinfo) {
|
||||||
|
return unAuthorizedResponse(event);
|
||||||
|
}
|
||||||
|
const { id, name } = getQuery(event);
|
||||||
|
|
||||||
|
return (name as string) in namesMap &&
|
||||||
|
(!id || namesMap[name as string] !== String(id))
|
||||||
|
? useResponseSuccess(true)
|
||||||
|
: useResponseSuccess(false);
|
||||||
|
});
|
28
apps/backend-mock/api/system/menu/path-exists.ts
Normal file
28
apps/backend-mock/api/system/menu/path-exists.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||||
|
import { MOCK_MENU_LIST } from '~/utils/mock-data';
|
||||||
|
import { unAuthorizedResponse } from '~/utils/response';
|
||||||
|
|
||||||
|
const pathMap: Record<string, any> = { '/': 0 };
|
||||||
|
|
||||||
|
function getPaths(menus: any[]) {
|
||||||
|
menus.forEach((menu) => {
|
||||||
|
pathMap[menu.path] = String(menu.id);
|
||||||
|
if (menu.children) {
|
||||||
|
getPaths(menu.children);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getPaths(MOCK_MENU_LIST);
|
||||||
|
|
||||||
|
export default eventHandler(async (event) => {
|
||||||
|
const userinfo = verifyAccessToken(event);
|
||||||
|
if (!userinfo) {
|
||||||
|
return unAuthorizedResponse(event);
|
||||||
|
}
|
||||||
|
const { id, path } = getQuery(event);
|
||||||
|
|
||||||
|
return (path as string) in pathMap &&
|
||||||
|
(!id || pathMap[path as string] !== String(id))
|
||||||
|
? useResponseSuccess(true)
|
||||||
|
: useResponseSuccess(false);
|
||||||
|
});
|
83
apps/backend-mock/api/system/role/list.ts
Normal file
83
apps/backend-mock/api/system/role/list.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||||
|
import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data';
|
||||||
|
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';
|
||||||
|
|
||||||
|
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
|
||||||
|
timeZone: 'Asia/Shanghai',
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
});
|
||||||
|
|
||||||
|
const menuIds = getMenuIds(MOCK_MENU_LIST);
|
||||||
|
|
||||||
|
function generateMockDataList(count: number) {
|
||||||
|
const dataList = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const dataItem: Record<string, any> = {
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
name: faker.commerce.product(),
|
||||||
|
status: faker.helpers.arrayElement([0, 1]),
|
||||||
|
createTime: formatterCN.format(
|
||||||
|
faker.date.between({ from: '2022-01-01', to: '2025-01-01' }),
|
||||||
|
),
|
||||||
|
permissions: faker.helpers.arrayElements(menuIds),
|
||||||
|
remark: faker.lorem.sentence(),
|
||||||
|
};
|
||||||
|
|
||||||
|
dataList.push(dataItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataList;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockData = generateMockDataList(100);
|
||||||
|
|
||||||
|
export default eventHandler(async (event) => {
|
||||||
|
const userinfo = verifyAccessToken(event);
|
||||||
|
if (!userinfo) {
|
||||||
|
return unAuthorizedResponse(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
page = 1,
|
||||||
|
pageSize = 20,
|
||||||
|
name,
|
||||||
|
id,
|
||||||
|
remark,
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
status,
|
||||||
|
} = getQuery(event);
|
||||||
|
let listData = structuredClone(mockData);
|
||||||
|
if (name) {
|
||||||
|
listData = listData.filter((item) =>
|
||||||
|
item.name.toLowerCase().includes(String(name).toLowerCase()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (id) {
|
||||||
|
listData = listData.filter((item) =>
|
||||||
|
item.id.toLowerCase().includes(String(id).toLowerCase()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (remark) {
|
||||||
|
listData = listData.filter((item) =>
|
||||||
|
item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (startTime) {
|
||||||
|
listData = listData.filter((item) => item.createTime >= startTime);
|
||||||
|
}
|
||||||
|
if (endTime) {
|
||||||
|
listData = listData.filter((item) => item.createTime <= endTime);
|
||||||
|
}
|
||||||
|
if (['0', '1'].includes(status as string)) {
|
||||||
|
listData = listData.filter((item) => item.status === Number(status));
|
||||||
|
}
|
||||||
|
return usePageResponseSuccess(page as string, pageSize as string, listData);
|
||||||
|
});
|
@@ -1,6 +1,6 @@
|
|||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||||
import { unAuthorizedResponse } from '~/utils/response';
|
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';
|
||||||
|
|
||||||
function generateMockDataList(count: number) {
|
function generateMockDataList(count: number) {
|
||||||
const dataList = [];
|
const dataList = [];
|
||||||
@@ -43,6 +43,31 @@ export default eventHandler(async (event) => {
|
|||||||
|
|
||||||
await sleep(600);
|
await sleep(600);
|
||||||
|
|
||||||
const { page, pageSize } = getQuery(event);
|
const { page, pageSize, sortBy, sortOrder } = getQuery(event);
|
||||||
return usePageResponseSuccess(page as string, pageSize as string, mockData);
|
const listData = structuredClone(mockData);
|
||||||
|
if (sortBy && Reflect.has(listData[0], sortBy as string)) {
|
||||||
|
listData.sort((a, b) => {
|
||||||
|
if (sortOrder === 'asc') {
|
||||||
|
if (sortBy === 'price') {
|
||||||
|
return (
|
||||||
|
Number.parseFloat(a[sortBy as string]) -
|
||||||
|
Number.parseFloat(b[sortBy as string])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return a[sortBy as string] > b[sortBy as string] ? 1 : -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sortBy === 'price') {
|
||||||
|
return (
|
||||||
|
Number.parseFloat(b[sortBy as string]) -
|
||||||
|
Number.parseFloat(a[sortBy as string])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return a[sortBy as string] < b[sortBy as string] ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return usePageResponseSuccess(page as string, pageSize as string, listData);
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,19 @@
|
|||||||
export default defineEventHandler((event) => {
|
import { forbiddenResponse, sleep } from '~/utils/response';
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
event.node.res.setHeader(
|
||||||
|
'Access-Control-Allow-Origin',
|
||||||
|
event.headers.get('Origin') ?? '*',
|
||||||
|
);
|
||||||
if (event.method === 'OPTIONS') {
|
if (event.method === 'OPTIONS') {
|
||||||
event.node.res.statusCode = 204;
|
event.node.res.statusCode = 204;
|
||||||
event.node.res.statusMessage = 'No Content.';
|
event.node.res.statusMessage = 'No Content.';
|
||||||
return 'OK';
|
return 'OK';
|
||||||
|
} else if (
|
||||||
|
['DELETE', 'PATCH', 'POST', 'PUT'].includes(event.method) &&
|
||||||
|
event.path.startsWith('/api/system/')
|
||||||
|
) {
|
||||||
|
await sleep(Math.floor(Math.random() * 2000));
|
||||||
|
return forbiddenResponse(event, '演示环境,禁止修改');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -9,7 +9,8 @@ export default defineNitroConfig({
|
|||||||
cors: true,
|
cors: true,
|
||||||
headers: {
|
headers: {
|
||||||
'Access-Control-Allow-Credentials': 'true',
|
'Access-Control-Allow-Credentials': 'true',
|
||||||
'Access-Control-Allow-Headers': '*',
|
'Access-Control-Allow-Headers':
|
||||||
|
'Accept, Authorization, Content-Length, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With',
|
||||||
'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
'Access-Control-Expose-Headers': '*',
|
'Access-Control-Expose-Headers': '*',
|
||||||
|
@@ -14,7 +14,7 @@ export function setRefreshTokenCookie(
|
|||||||
) {
|
) {
|
||||||
setCookie(event, 'jwt', refreshToken, {
|
setCookie(event, 'jwt', refreshToken, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
maxAge: 24 * 60 * 60 * 1000,
|
maxAge: 24 * 60 * 60, // unit: seconds
|
||||||
sameSite: 'none',
|
sameSite: 'none',
|
||||||
secure: true,
|
secure: true,
|
||||||
});
|
});
|
||||||
|
@@ -4,6 +4,7 @@ export interface UserInfo {
|
|||||||
realName: string;
|
realName: string;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
username: string;
|
username: string;
|
||||||
|
homePath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MOCK_USERS: UserInfo[] = [
|
export const MOCK_USERS: UserInfo[] = [
|
||||||
@@ -20,6 +21,7 @@ export const MOCK_USERS: UserInfo[] = [
|
|||||||
realName: 'Admin',
|
realName: 'Admin',
|
||||||
roles: ['admin'],
|
roles: ['admin'],
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
|
homePath: '/workspace',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
@@ -27,6 +29,7 @@ export const MOCK_USERS: UserInfo[] = [
|
|||||||
realName: 'Jack',
|
realName: 'Jack',
|
||||||
roles: ['user'],
|
roles: ['user'],
|
||||||
username: 'jack',
|
username: 'jack',
|
||||||
|
homePath: '/analytics',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -50,13 +53,12 @@ export const MOCK_CODES = [
|
|||||||
|
|
||||||
const dashboardMenus = [
|
const dashboardMenus = [
|
||||||
{
|
{
|
||||||
component: 'BasicLayout',
|
|
||||||
meta: {
|
meta: {
|
||||||
order: -1,
|
order: -1,
|
||||||
title: 'page.dashboard.title',
|
title: 'page.dashboard.title',
|
||||||
},
|
},
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
path: '/',
|
path: '/dashboard',
|
||||||
redirect: '/analytics',
|
redirect: '/analytics',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -113,7 +115,6 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
component: 'BasicLayout',
|
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'ic:baseline-view-in-ar',
|
icon: 'ic:baseline-view-in-ar',
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
@@ -184,3 +185,206 @@ export const MOCK_MENUS = [
|
|||||||
username: 'jack',
|
username: 'jack',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const MOCK_MENU_LIST = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Workspace',
|
||||||
|
status: 1,
|
||||||
|
type: 'menu',
|
||||||
|
icon: 'mdi:dashboard',
|
||||||
|
path: '/workspace',
|
||||||
|
component: '/dashboard/workspace/index',
|
||||||
|
meta: {
|
||||||
|
icon: 'carbon:workspace',
|
||||||
|
title: 'page.dashboard.workspace',
|
||||||
|
affixTab: true,
|
||||||
|
order: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
meta: {
|
||||||
|
icon: 'carbon:settings',
|
||||||
|
order: 9997,
|
||||||
|
title: 'system.title',
|
||||||
|
badge: 'new',
|
||||||
|
badgeType: 'normal',
|
||||||
|
badgeVariants: 'primary',
|
||||||
|
},
|
||||||
|
status: 1,
|
||||||
|
type: 'catalog',
|
||||||
|
name: 'System',
|
||||||
|
path: '/system',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 201,
|
||||||
|
pid: 2,
|
||||||
|
path: '/system/menu',
|
||||||
|
name: 'SystemMenu',
|
||||||
|
authCode: 'System:Menu:List',
|
||||||
|
status: 1,
|
||||||
|
type: 'menu',
|
||||||
|
meta: {
|
||||||
|
icon: 'carbon:menu',
|
||||||
|
title: 'system.menu.title',
|
||||||
|
},
|
||||||
|
component: '/system/menu/list',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 20_101,
|
||||||
|
pid: 201,
|
||||||
|
name: 'SystemMenuCreate',
|
||||||
|
status: 1,
|
||||||
|
type: 'button',
|
||||||
|
authCode: 'System:Menu:Create',
|
||||||
|
meta: { title: 'common.create' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 20_102,
|
||||||
|
pid: 201,
|
||||||
|
name: 'SystemMenuEdit',
|
||||||
|
status: 1,
|
||||||
|
type: 'button',
|
||||||
|
authCode: 'System:Menu:Edit',
|
||||||
|
meta: { title: 'common.edit' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 20_103,
|
||||||
|
pid: 201,
|
||||||
|
name: 'SystemMenuDelete',
|
||||||
|
status: 1,
|
||||||
|
type: 'button',
|
||||||
|
authCode: 'System:Menu:Delete',
|
||||||
|
meta: { title: 'common.delete' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 202,
|
||||||
|
pid: 2,
|
||||||
|
path: '/system/dept',
|
||||||
|
name: 'SystemDept',
|
||||||
|
status: 1,
|
||||||
|
type: 'menu',
|
||||||
|
authCode: 'System:Dept:List',
|
||||||
|
meta: {
|
||||||
|
icon: 'carbon:container-services',
|
||||||
|
title: 'system.dept.title',
|
||||||
|
},
|
||||||
|
component: '/system/dept/list',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 20_401,
|
||||||
|
pid: 201,
|
||||||
|
name: 'SystemDeptCreate',
|
||||||
|
status: 1,
|
||||||
|
type: 'button',
|
||||||
|
authCode: 'System:Dept:Create',
|
||||||
|
meta: { title: 'common.create' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 20_402,
|
||||||
|
pid: 201,
|
||||||
|
name: 'SystemDeptEdit',
|
||||||
|
status: 1,
|
||||||
|
type: 'button',
|
||||||
|
authCode: 'System:Dept:Edit',
|
||||||
|
meta: { title: 'common.edit' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 20_403,
|
||||||
|
pid: 201,
|
||||||
|
name: 'SystemDeptDelete',
|
||||||
|
status: 1,
|
||||||
|
type: 'button',
|
||||||
|
authCode: 'System:Dept:Delete',
|
||||||
|
meta: { title: 'common.delete' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
meta: {
|
||||||
|
badgeType: 'dot',
|
||||||
|
order: 9998,
|
||||||
|
title: 'demos.vben.title',
|
||||||
|
icon: 'carbon:data-center',
|
||||||
|
},
|
||||||
|
name: 'Project',
|
||||||
|
path: '/vben-admin',
|
||||||
|
type: 'catalog',
|
||||||
|
status: 1,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 901,
|
||||||
|
pid: 9,
|
||||||
|
name: 'VbenDocument',
|
||||||
|
path: '/vben-admin/document',
|
||||||
|
component: 'IFrameView',
|
||||||
|
type: 'embedded',
|
||||||
|
status: 1,
|
||||||
|
meta: {
|
||||||
|
icon: 'carbon:book',
|
||||||
|
iframeSrc: 'https://doc.vben.pro',
|
||||||
|
title: 'demos.vben.document',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 902,
|
||||||
|
pid: 9,
|
||||||
|
name: 'VbenGithub',
|
||||||
|
path: '/vben-admin/github',
|
||||||
|
component: 'IFrameView',
|
||||||
|
type: 'link',
|
||||||
|
status: 1,
|
||||||
|
meta: {
|
||||||
|
icon: 'carbon:logo-github',
|
||||||
|
link: 'https://github.com/vbenjs/vue-vben-admin',
|
||||||
|
title: 'Github',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 903,
|
||||||
|
pid: 9,
|
||||||
|
name: 'VbenAntdv',
|
||||||
|
path: '/vben-admin/antdv',
|
||||||
|
component: 'IFrameView',
|
||||||
|
type: 'link',
|
||||||
|
status: 0,
|
||||||
|
meta: {
|
||||||
|
icon: 'carbon:hexagon-vertical-solid',
|
||||||
|
badgeType: 'dot',
|
||||||
|
link: 'https://ant.vben.pro',
|
||||||
|
title: 'demos.vben.antdv',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
component: '_core/about/index',
|
||||||
|
type: 'menu',
|
||||||
|
status: 1,
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:copyright',
|
||||||
|
order: 9999,
|
||||||
|
title: 'demos.vben.about',
|
||||||
|
},
|
||||||
|
name: 'About',
|
||||||
|
path: '/about',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getMenuIds(menus: any[]) {
|
||||||
|
const ids: number[] = [];
|
||||||
|
menus.forEach((item) => {
|
||||||
|
ids.push(item.id);
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
ids.push(...getMenuIds(item.children));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vben/web-antd",
|
"name": "@vben/web-antd",
|
||||||
"version": "5.4.4",
|
"version": "5.5.4",
|
||||||
"homepage": "https://vben.pro",
|
"homepage": "https://vben.pro",
|
||||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@@ -3,12 +3,14 @@
|
|||||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
import { defineComponent, getCurrentInstance, h, ref } from 'vue';
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { globalShareState } from '@vben/common-ui';
|
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -40,20 +42,43 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
component: T,
|
component: T,
|
||||||
type: 'input' | 'select',
|
type: 'input' | 'select',
|
||||||
) => {
|
) => {
|
||||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
return defineComponent({
|
||||||
const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);
|
inheritAttrs: false,
|
||||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
name: component.name,
|
||||||
};
|
setup: (props: any, { attrs, expose, slots }) => {
|
||||||
|
const placeholder =
|
||||||
|
props?.placeholder ||
|
||||||
|
attrs?.placeholder ||
|
||||||
|
$t(`ui.placeholder.${type}`);
|
||||||
|
// 透传组件暴露的方法
|
||||||
|
const innerRef = ref();
|
||||||
|
const publicApi: Recordable<any> = {};
|
||||||
|
expose(publicApi);
|
||||||
|
const instance = getCurrentInstance();
|
||||||
|
instance?.proxy?.$nextTick(() => {
|
||||||
|
for (const key in innerRef.value) {
|
||||||
|
if (typeof innerRef.value[key] === 'function') {
|
||||||
|
publicApi[key] = innerRef.value[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () =>
|
||||||
|
h(component, { ...props, ...attrs, placeholder, ref: innerRef }, slots);
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type ComponentType =
|
export type ComponentType =
|
||||||
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect'
|
||||||
| 'AutoComplete'
|
| 'AutoComplete'
|
||||||
| 'Checkbox'
|
| 'Checkbox'
|
||||||
| 'CheckboxGroup'
|
| 'CheckboxGroup'
|
||||||
| 'DatePicker'
|
| 'DatePicker'
|
||||||
| 'DefaultButton'
|
| 'DefaultButton'
|
||||||
| 'Divider'
|
| 'Divider'
|
||||||
|
| 'IconPicker'
|
||||||
| 'Input'
|
| 'Input'
|
||||||
| 'InputNumber'
|
| 'InputNumber'
|
||||||
| 'InputPassword'
|
| 'InputPassword'
|
||||||
@@ -77,7 +102,38 @@ async function initComponentAdapter() {
|
|||||||
// 如果你的组件体积比较大,可以使用异步加载
|
// 如果你的组件体积比较大,可以使用异步加载
|
||||||
// Button: () =>
|
// Button: () =>
|
||||||
// import('xxx').then((res) => res.Button),
|
// import('xxx').then((res) => res.Button),
|
||||||
|
ApiSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiComponent,
|
||||||
|
{
|
||||||
|
placeholder: $t('ui.placeholder.select'),
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: Select,
|
||||||
|
loadingSlot: 'suffixIcon',
|
||||||
|
visibleEvent: 'onDropdownVisibleChange',
|
||||||
|
modelPropName: 'value',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ApiTreeSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiComponent,
|
||||||
|
{
|
||||||
|
placeholder: $t('ui.placeholder.select'),
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: TreeSelect,
|
||||||
|
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||||
|
loadingSlot: 'suffixIcon',
|
||||||
|
modelPropName: 'value',
|
||||||
|
optionsPropName: 'treeData',
|
||||||
|
visibleEvent: 'onVisibleChange',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
AutoComplete,
|
AutoComplete,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
CheckboxGroup,
|
CheckboxGroup,
|
||||||
@@ -87,6 +143,19 @@ async function initComponentAdapter() {
|
|||||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||||
},
|
},
|
||||||
Divider,
|
Divider,
|
||||||
|
IconPicker: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
IconPicker,
|
||||||
|
{
|
||||||
|
iconSlot: 'addonAfter',
|
||||||
|
inputComponent: Input,
|
||||||
|
modelValueProp: 'value',
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
Input: withDefaultPlaceholder(Input, 'input'),
|
Input: withDefaultPlaceholder(Input, 'input'),
|
||||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* 该文件可自行根据业务逻辑进行调整
|
* 该文件可自行根据业务逻辑进行调整
|
||||||
*/
|
*/
|
||||||
import type { HttpResponse } from '@vben/request';
|
import type { RequestClientOptions } from '@vben/request';
|
||||||
|
|
||||||
import { useAppConfig } from '@vben/hooks';
|
import { useAppConfig } from '@vben/hooks';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import {
|
import {
|
||||||
authenticateResponseInterceptor,
|
authenticateResponseInterceptor,
|
||||||
|
defaultResponseInterceptor,
|
||||||
errorMessageResponseInterceptor,
|
errorMessageResponseInterceptor,
|
||||||
RequestClient,
|
RequestClient,
|
||||||
} from '@vben/request';
|
} from '@vben/request';
|
||||||
@@ -20,8 +21,9 @@ import { refreshTokenApi } from './core';
|
|||||||
|
|
||||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||||
|
|
||||||
function createRequestClient(baseURL: string) {
|
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
const client = new RequestClient({
|
const client = new RequestClient({
|
||||||
|
...options,
|
||||||
baseURL,
|
baseURL,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -69,19 +71,14 @@ function createRequestClient(baseURL: string) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// response数据解构
|
// 处理返回的响应数据格式
|
||||||
client.addResponseInterceptor<HttpResponse>({
|
client.addResponseInterceptor(
|
||||||
fulfilled: (response) => {
|
defaultResponseInterceptor({
|
||||||
const { data: responseData, status } = response;
|
codeField: 'code',
|
||||||
|
dataField: 'data',
|
||||||
const { code, data } = responseData;
|
successCode: 0,
|
||||||
if (status >= 200 && status < 400 && code === 0) {
|
}),
|
||||||
return data;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
throw Object.assign({}, response, { response });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// token过期的处理
|
// token过期的处理
|
||||||
client.addResponseInterceptor(
|
client.addResponseInterceptor(
|
||||||
@@ -109,6 +106,8 @@ function createRequestClient(baseURL: string) {
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const requestClient = createRequestClient(apiURL);
|
export const requestClient = createRequestClient(apiURL, {
|
||||||
|
responseReturn: 'data',
|
||||||
|
});
|
||||||
|
|
||||||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import { createApp, watchEffect } from 'vue';
|
import { createApp, watchEffect } from 'vue';
|
||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
|
import { initTippy, registerLoadingDirective } from '@vben/common-ui';
|
||||||
|
import { MotionPlugin } from '@vben/plugins/motion';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { initStores } from '@vben/stores';
|
import { initStores } from '@vben/stores';
|
||||||
import '@vben/styles';
|
import '@vben/styles';
|
||||||
@@ -18,8 +20,23 @@ async function bootstrap(namespace: string) {
|
|||||||
// 初始化组件适配器
|
// 初始化组件适配器
|
||||||
await initComponentAdapter();
|
await initComponentAdapter();
|
||||||
|
|
||||||
|
// // 设置弹窗的默认配置
|
||||||
|
// setDefaultModalProps({
|
||||||
|
// fullscreenButton: false,
|
||||||
|
// });
|
||||||
|
// // 设置抽屉的默认配置
|
||||||
|
// setDefaultDrawerProps({
|
||||||
|
// zIndex: 1020,
|
||||||
|
// });
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
// 注册v-loading指令
|
||||||
|
registerLoadingDirective(app, {
|
||||||
|
loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令
|
||||||
|
spinning: 'spinning',
|
||||||
|
});
|
||||||
|
|
||||||
// 国际化 i18n 配置
|
// 国际化 i18n 配置
|
||||||
await setupI18n(app);
|
await setupI18n(app);
|
||||||
|
|
||||||
@@ -29,9 +46,15 @@ async function bootstrap(namespace: string) {
|
|||||||
// 安装权限指令
|
// 安装权限指令
|
||||||
registerAccessDirective(app);
|
registerAccessDirective(app);
|
||||||
|
|
||||||
|
// 初始化 tippy
|
||||||
|
initTippy(app);
|
||||||
|
|
||||||
// 配置路由及路由守卫
|
// 配置路由及路由守卫
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
|
// 配置Motion插件
|
||||||
|
app.use(MotionPlugin);
|
||||||
|
|
||||||
// 动态更新标题
|
// 动态更新标题
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (preferences.app.dynamicTitle) {
|
if (preferences.app.dynamicTitle) {
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
|
||||||
import type { Locale } from 'ant-design-vue/es/locale';
|
import type { Locale } from 'ant-design-vue/es/locale';
|
||||||
|
|
||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
|
|
||||||
|
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
|||||||
/**
|
/**
|
||||||
* @description 项目配置文件
|
* @description 项目配置文件
|
||||||
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
||||||
|
* !!! 更改配置后请清空缓存,否则可能不生效
|
||||||
*/
|
*/
|
||||||
export const overridesPreferences = defineOverridesPreferences({
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
// overrides
|
// overrides
|
||||||
|
@@ -54,7 +54,9 @@ function setupAccessGuard(router: Router) {
|
|||||||
if (coreRouteNames.includes(to.name as string)) {
|
if (coreRouteNames.includes(to.name as string)) {
|
||||||
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
||||||
return decodeURIComponent(
|
return decodeURIComponent(
|
||||||
(to.query?.redirect as string) || DEFAULT_HOME_PATH,
|
(to.query?.redirect as string) ||
|
||||||
|
userStore.userInfo?.homePath ||
|
||||||
|
DEFAULT_HOME_PATH,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -72,7 +74,10 @@ function setupAccessGuard(router: Router) {
|
|||||||
return {
|
return {
|
||||||
path: LOGIN_PATH,
|
path: LOGIN_PATH,
|
||||||
// 如不需要,直接删除 query
|
// 如不需要,直接删除 query
|
||||||
query: { redirect: encodeURIComponent(to.fullPath) },
|
query:
|
||||||
|
to.fullPath === DEFAULT_HOME_PATH
|
||||||
|
? {}
|
||||||
|
: { redirect: encodeURIComponent(to.fullPath) },
|
||||||
// 携带当前跳转的页面,登录后重新跳转该页面
|
// 携带当前跳转的页面,登录后重新跳转该页面
|
||||||
replace: true,
|
replace: true,
|
||||||
};
|
};
|
||||||
@@ -102,7 +107,10 @@ function setupAccessGuard(router: Router) {
|
|||||||
accessStore.setAccessMenus(accessibleMenus);
|
accessStore.setAccessMenus(accessibleMenus);
|
||||||
accessStore.setAccessRoutes(accessibleRoutes);
|
accessStore.setAccessRoutes(accessibleRoutes);
|
||||||
accessStore.setIsAccessChecked(true);
|
accessStore.setIsAccessChecked(true);
|
||||||
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
|
const redirectPath = (from.query.redirect ??
|
||||||
|
(to.path === DEFAULT_HOME_PATH
|
||||||
|
? userInfo.homePath || DEFAULT_HOME_PATH
|
||||||
|
: to.fullPath)) as string;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...router.resolve(decodeURIComponent(redirectPath)),
|
...router.resolve(decodeURIComponent(redirectPath)),
|
||||||
|
@@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router';
|
|||||||
|
|
||||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||||
|
|
||||||
import { AuthPageLayout } from '#/layouts';
|
import { AuthPageLayout, BasicLayout } from '#/layouts';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import Login from '#/views/_core/authentication/login.vue';
|
import Login from '#/views/_core/authentication/login.vue';
|
||||||
|
|
||||||
@@ -21,13 +21,21 @@ const fallbackNotFoundRoute: RouteRecordRaw = {
|
|||||||
|
|
||||||
/** 基本路由,这些路由是必须存在的 */
|
/** 基本路由,这些路由是必须存在的 */
|
||||||
const coreRoutes: RouteRecordRaw[] = [
|
const coreRoutes: RouteRecordRaw[] = [
|
||||||
|
/**
|
||||||
|
* 根路由
|
||||||
|
* 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。
|
||||||
|
* 此路由必须存在,且不应修改
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
|
component: BasicLayout,
|
||||||
meta: {
|
meta: {
|
||||||
|
hideInBreadcrumb: true,
|
||||||
title: 'Root',
|
title: 'Root',
|
||||||
},
|
},
|
||||||
name: 'Root',
|
name: 'Root',
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: DEFAULT_HOME_PATH,
|
redirect: DEFAULT_HOME_PATH,
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: AuthPageLayout,
|
component: AuthPageLayout,
|
||||||
|
@@ -17,12 +17,12 @@ const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
|
|||||||
|
|
||||||
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */
|
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */
|
||||||
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
|
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
|
||||||
/** 不需要权限的菜单列表(会显示在菜单中) */
|
|
||||||
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
|
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
|
||||||
const staticRoutes: RouteRecordRaw[] = [];
|
const staticRoutes: RouteRecordRaw[] = [];
|
||||||
const externalRoutes: RouteRecordRaw[] = [];
|
const externalRoutes: RouteRecordRaw[] = [];
|
||||||
|
|
||||||
/** 路由列表,由基本路由+静态路由组成 */
|
/** 路由列表,由基本路由、外部路由和404兜底路由组成
|
||||||
|
* 无需走权限验证(会一直显示在菜单中) */
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
...coreRoutes,
|
...coreRoutes,
|
||||||
...externalRoutes,
|
...externalRoutes,
|
||||||
@@ -32,5 +32,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
||||||
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
|
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
|
||||||
|
|
||||||
|
/** 有权限校验的路由列表,包含动态路由和静态路由 */
|
||||||
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
|
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
|
||||||
export { accessRoutes, coreRouteNames, routes };
|
export { accessRoutes, coreRouteNames, routes };
|
||||||
|
@@ -1,18 +1,16 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
import { BasicLayout } from '#/layouts';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
component: BasicLayout,
|
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'lucide:layout-dashboard',
|
icon: 'lucide:layout-dashboard',
|
||||||
order: -1,
|
order: -1,
|
||||||
title: $t('page.dashboard.title'),
|
title: $t('page.dashboard.title'),
|
||||||
},
|
},
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
path: '/',
|
path: '/dashboard',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'Analytics',
|
name: 'Analytics',
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
import { BasicLayout } from '#/layouts';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
component: BasicLayout,
|
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'ic:baseline-view-in-ar',
|
icon: 'ic:baseline-view-in-ar',
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
|
@@ -8,30 +8,20 @@ import {
|
|||||||
VBEN_NAIVE_PREVIEW_URL,
|
VBEN_NAIVE_PREVIEW_URL,
|
||||||
} from '@vben/constants';
|
} from '@vben/constants';
|
||||||
|
|
||||||
import { BasicLayout, IFrameView } from '#/layouts';
|
import { IFrameView } from '#/layouts';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
component: BasicLayout,
|
|
||||||
meta: {
|
meta: {
|
||||||
badgeType: 'dot',
|
badgeType: 'dot',
|
||||||
icon: VBEN_LOGO_URL,
|
icon: VBEN_LOGO_URL,
|
||||||
order: 9999,
|
order: 9998,
|
||||||
title: $t('demos.vben.title'),
|
title: $t('demos.vben.title'),
|
||||||
},
|
},
|
||||||
name: 'VbenProject',
|
name: 'VbenProject',
|
||||||
path: '/vben-admin',
|
path: '/vben-admin',
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
name: 'VbenAbout',
|
|
||||||
path: '/vben-admin/about',
|
|
||||||
component: () => import('#/views/_core/about/index.vue'),
|
|
||||||
meta: {
|
|
||||||
icon: 'lucide:copyright',
|
|
||||||
title: $t('demos.vben.about'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'VbenDocument',
|
name: 'VbenDocument',
|
||||||
path: '/vben-admin/document',
|
path: '/vben-admin/document',
|
||||||
@@ -76,6 +66,16 @@ const routes: RouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'VbenAbout',
|
||||||
|
path: '/vben-admin/about',
|
||||||
|
component: () => import('#/views/_core/about/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:copyright',
|
||||||
|
title: $t('demos.vben.about'),
|
||||||
|
order: 9999,
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
||||||
|
@@ -10,6 +10,7 @@ import { $t } from '@vben/locales';
|
|||||||
defineOptions({ name: 'CodeLogin' });
|
defineOptions({ name: 'CodeLogin' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const CODE_LENGTH = 6;
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
return [
|
return [
|
||||||
@@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
{
|
{
|
||||||
component: 'VbenPinInput',
|
component: 'VbenPinInput',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
codeLength: CODE_LENGTH,
|
||||||
createText: (countdown: number) => {
|
createText: (countdown: number) => {
|
||||||
const text =
|
const text =
|
||||||
countdown > 0
|
countdown > 0
|
||||||
@@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
},
|
},
|
||||||
fieldName: 'code',
|
fieldName: 'code',
|
||||||
label: $t('authentication.code'),
|
label: $t('authentication.code'),
|
||||||
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
|
rules: z.string().length(CODE_LENGTH, {
|
||||||
|
message: $t('authentication.codeTip', [CODE_LENGTH]),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -15,10 +15,10 @@ import {
|
|||||||
} from '@vben/icons';
|
} from '@vben/icons';
|
||||||
|
|
||||||
import AnalyticsTrends from './analytics-trends.vue';
|
import AnalyticsTrends from './analytics-trends.vue';
|
||||||
import AnalyticsVisits from './analytics-visits.vue';
|
|
||||||
import AnalyticsVisitsData from './analytics-visits-data.vue';
|
import AnalyticsVisitsData from './analytics-visits-data.vue';
|
||||||
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
|
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
|
||||||
import AnalyticsVisitsSource from './analytics-visits-source.vue';
|
import AnalyticsVisitsSource from './analytics-visits-source.vue';
|
||||||
|
import AnalyticsVisits from './analytics-visits.vue';
|
||||||
|
|
||||||
const overviewItems: AnalysisOverviewItem[] = [
|
const overviewItems: AnalysisOverviewItem[] = [
|
||||||
{
|
{
|
||||||
|
@@ -7,6 +7,7 @@ import type {
|
|||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AnalysisChartCard,
|
AnalysisChartCard,
|
||||||
@@ -18,11 +19,15 @@ import {
|
|||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { useUserStore } from '@vben/stores';
|
import { useUserStore } from '@vben/stores';
|
||||||
|
import { openWindow } from '@vben/utils';
|
||||||
|
|
||||||
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
|
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
|
||||||
|
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
|
||||||
|
// 例如:url: /dashboard/workspace
|
||||||
const projectItems: WorkbenchProjectItem[] = [
|
const projectItems: WorkbenchProjectItem[] = [
|
||||||
{
|
{
|
||||||
color: '',
|
color: '',
|
||||||
@@ -31,6 +36,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '开源组',
|
group: '开源组',
|
||||||
icon: 'carbon:logo-github',
|
icon: 'carbon:logo-github',
|
||||||
title: 'Github',
|
title: 'Github',
|
||||||
|
url: 'https://github.com',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#3fb27f',
|
color: '#3fb27f',
|
||||||
@@ -39,6 +45,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '算法组',
|
group: '算法组',
|
||||||
icon: 'ion:logo-vue',
|
icon: 'ion:logo-vue',
|
||||||
title: 'Vue',
|
title: 'Vue',
|
||||||
|
url: 'https://vuejs.org',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#e18525',
|
color: '#e18525',
|
||||||
@@ -47,6 +54,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '上班摸鱼',
|
group: '上班摸鱼',
|
||||||
icon: 'ion:logo-html5',
|
icon: 'ion:logo-html5',
|
||||||
title: 'Html5',
|
title: 'Html5',
|
||||||
|
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#bf0c2c',
|
color: '#bf0c2c',
|
||||||
@@ -55,6 +63,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: 'UI',
|
group: 'UI',
|
||||||
icon: 'ion:logo-angular',
|
icon: 'ion:logo-angular',
|
||||||
title: 'Angular',
|
title: 'Angular',
|
||||||
|
url: 'https://angular.io',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#00d8ff',
|
color: '#00d8ff',
|
||||||
@@ -63,6 +72,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '技术牛',
|
group: '技术牛',
|
||||||
icon: 'bx:bxl-react',
|
icon: 'bx:bxl-react',
|
||||||
title: 'React',
|
title: 'React',
|
||||||
|
url: 'https://reactjs.org',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#EBD94E',
|
color: '#EBD94E',
|
||||||
@@ -71,39 +81,47 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '架构组',
|
group: '架构组',
|
||||||
icon: 'ion:logo-javascript',
|
icon: 'ion:logo-javascript',
|
||||||
title: 'Js',
|
title: 'Js',
|
||||||
|
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 同样,这里的 url 也可以使用以 http 开头的外部链接
|
||||||
const quickNavItems: WorkbenchQuickNavItem[] = [
|
const quickNavItems: WorkbenchQuickNavItem[] = [
|
||||||
{
|
{
|
||||||
color: '#1fdaca',
|
color: '#1fdaca',
|
||||||
icon: 'ion:home-outline',
|
icon: 'ion:home-outline',
|
||||||
title: '首页',
|
title: '首页',
|
||||||
|
url: '/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#bf0c2c',
|
color: '#bf0c2c',
|
||||||
icon: 'ion:grid-outline',
|
icon: 'ion:grid-outline',
|
||||||
title: '仪表盘',
|
title: '仪表盘',
|
||||||
|
url: '/dashboard',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#e18525',
|
color: '#e18525',
|
||||||
icon: 'ion:layers-outline',
|
icon: 'ion:layers-outline',
|
||||||
title: '组件',
|
title: '组件',
|
||||||
|
url: '/demos/features/icons',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#3fb27f',
|
color: '#3fb27f',
|
||||||
icon: 'ion:settings-outline',
|
icon: 'ion:settings-outline',
|
||||||
title: '系统管理',
|
title: '系统管理',
|
||||||
|
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#4daf1bc9',
|
color: '#4daf1bc9',
|
||||||
icon: 'ion:key-outline',
|
icon: 'ion:key-outline',
|
||||||
title: '权限管理',
|
title: '权限管理',
|
||||||
|
url: '/demos/access/page-control',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#00d8ff',
|
color: '#00d8ff',
|
||||||
icon: 'ion:bar-chart-outline',
|
icon: 'ion:bar-chart-outline',
|
||||||
title: '图表',
|
title: '图表',
|
||||||
|
url: '/analytics',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -195,6 +213,24 @@ const trendItems: WorkbenchTrendItem[] = [
|
|||||||
title: 'Vben',
|
title: 'Vben',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
|
||||||
|
// This is a sample method, adjust according to the actual project requirements
|
||||||
|
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
|
||||||
|
if (nav.url?.startsWith('http')) {
|
||||||
|
openWindow(nav.url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nav.url?.startsWith('/')) {
|
||||||
|
router.push(nav.url).catch((error) => {
|
||||||
|
console.error('Navigation failed:', error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -210,7 +246,7 @@ const trendItems: WorkbenchTrendItem[] = [
|
|||||||
|
|
||||||
<div class="mt-5 flex flex-col lg:flex-row">
|
<div class="mt-5 flex flex-col lg:flex-row">
|
||||||
<div class="mr-4 w-full lg:w-3/5">
|
<div class="mr-4 w-full lg:w-3/5">
|
||||||
<WorkbenchProject :items="projectItems" title="项目" />
|
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
|
||||||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full lg:w-2/5">
|
<div class="w-full lg:w-2/5">
|
||||||
@@ -218,6 +254,7 @@ const trendItems: WorkbenchTrendItem[] = [
|
|||||||
:items="quickNavItems"
|
:items="quickNavItems"
|
||||||
class="mt-5 lg:mt-0"
|
class="mt-5 lg:mt-0"
|
||||||
title="快捷导航"
|
title="快捷导航"
|
||||||
|
@click="navTo"
|
||||||
/>
|
/>
|
||||||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
||||||
<AnalysisChartCard class="mt-5" title="访问来源">
|
<AnalysisChartCard class="mt-5" title="访问来源">
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vben/web-ele",
|
"name": "@vben/web-ele",
|
||||||
"version": "5.4.4",
|
"version": "5.5.4",
|
||||||
"homepage": "https://vben.pro",
|
"homepage": "https://vben.pro",
|
||||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@@ -3,24 +3,30 @@
|
|||||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
import { defineComponent, getCurrentInstance, h, ref } from 'vue';
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { globalShareState } from '@vben/common-ui';
|
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ElButton,
|
ElButton,
|
||||||
ElCheckbox,
|
ElCheckbox,
|
||||||
|
ElCheckboxButton,
|
||||||
ElCheckboxGroup,
|
ElCheckboxGroup,
|
||||||
|
ElDatePicker,
|
||||||
ElDivider,
|
ElDivider,
|
||||||
ElInput,
|
ElInput,
|
||||||
ElInputNumber,
|
ElInputNumber,
|
||||||
ElNotification,
|
ElNotification,
|
||||||
|
ElRadio,
|
||||||
|
ElRadioButton,
|
||||||
ElRadioGroup,
|
ElRadioGroup,
|
||||||
ElSelect,
|
ElSelectV2,
|
||||||
ElSpace,
|
ElSpace,
|
||||||
ElSwitch,
|
ElSwitch,
|
||||||
ElTimePicker,
|
ElTimePicker,
|
||||||
@@ -32,18 +38,41 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
component: T,
|
component: T,
|
||||||
type: 'input' | 'select',
|
type: 'input' | 'select',
|
||||||
) => {
|
) => {
|
||||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
return defineComponent({
|
||||||
const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);
|
inheritAttrs: false,
|
||||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
name: component.name,
|
||||||
};
|
setup: (props: any, { attrs, expose, slots }) => {
|
||||||
|
const placeholder =
|
||||||
|
props?.placeholder ||
|
||||||
|
attrs?.placeholder ||
|
||||||
|
$t(`ui.placeholder.${type}`);
|
||||||
|
// 透传组件暴露的方法
|
||||||
|
const innerRef = ref();
|
||||||
|
const publicApi: Recordable<any> = {};
|
||||||
|
expose(publicApi);
|
||||||
|
const instance = getCurrentInstance();
|
||||||
|
instance?.proxy?.$nextTick(() => {
|
||||||
|
for (const key in innerRef.value) {
|
||||||
|
if (typeof innerRef.value[key] === 'function') {
|
||||||
|
publicApi[key] = innerRef.value[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () =>
|
||||||
|
h(component, { ...props, ...attrs, placeholder, ref: innerRef }, slots);
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type ComponentType =
|
export type ComponentType =
|
||||||
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect'
|
||||||
| 'Checkbox'
|
| 'Checkbox'
|
||||||
| 'CheckboxGroup'
|
| 'CheckboxGroup'
|
||||||
| 'DatePicker'
|
| 'DatePicker'
|
||||||
| 'Divider'
|
| 'Divider'
|
||||||
|
| 'IconPicker'
|
||||||
| 'Input'
|
| 'Input'
|
||||||
| 'InputNumber'
|
| 'InputNumber'
|
||||||
| 'RadioGroup'
|
| 'RadioGroup'
|
||||||
@@ -60,11 +89,59 @@ async function initComponentAdapter() {
|
|||||||
// 如果你的组件体积比较大,可以使用异步加载
|
// 如果你的组件体积比较大,可以使用异步加载
|
||||||
// Button: () =>
|
// Button: () =>
|
||||||
// import('xxx').then((res) => res.Button),
|
// import('xxx').then((res) => res.Button),
|
||||||
|
ApiSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiComponent,
|
||||||
|
{
|
||||||
|
placeholder: $t('ui.placeholder.select'),
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: ElSelectV2,
|
||||||
|
loadingSlot: 'loading',
|
||||||
|
visibleEvent: 'onVisibleChange',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ApiTreeSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiComponent,
|
||||||
|
{
|
||||||
|
placeholder: $t('ui.placeholder.select'),
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: ElTreeSelect,
|
||||||
|
props: { label: 'label', children: 'children' },
|
||||||
|
nodeKey: 'value',
|
||||||
|
loadingSlot: 'loading',
|
||||||
|
optionsPropName: 'data',
|
||||||
|
visibleEvent: 'onVisibleChange',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
Checkbox: ElCheckbox,
|
Checkbox: ElCheckbox,
|
||||||
CheckboxGroup: ElCheckboxGroup,
|
CheckboxGroup: (props, { attrs, slots }) => {
|
||||||
|
let defaultSlot;
|
||||||
|
if (Reflect.has(slots, 'default')) {
|
||||||
|
defaultSlot = slots.default;
|
||||||
|
} else {
|
||||||
|
const { options, isButton } = attrs;
|
||||||
|
if (Array.isArray(options)) {
|
||||||
|
defaultSlot = () =>
|
||||||
|
options.map((option) =>
|
||||||
|
h(isButton ? ElCheckboxButton : ElCheckbox, option),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
ElCheckboxGroup,
|
||||||
|
{ ...props, ...attrs },
|
||||||
|
{ ...slots, default: defaultSlot },
|
||||||
|
);
|
||||||
|
},
|
||||||
// 自定义默认按钮
|
// 自定义默认按钮
|
||||||
DefaulButton: (props, { attrs, slots }) => {
|
DefaultButton: (props, { attrs, slots }) => {
|
||||||
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
|
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
|
||||||
},
|
},
|
||||||
// 自定义主要按钮
|
// 自定义主要按钮
|
||||||
@@ -72,13 +149,87 @@ async function initComponentAdapter() {
|
|||||||
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
|
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
|
||||||
},
|
},
|
||||||
Divider: ElDivider,
|
Divider: ElDivider,
|
||||||
|
IconPicker: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
IconPicker,
|
||||||
|
{
|
||||||
|
iconSlot: 'append',
|
||||||
|
modelValueProp: 'model-value',
|
||||||
|
inputComponent: ElInput,
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
Input: withDefaultPlaceholder(ElInput, 'input'),
|
Input: withDefaultPlaceholder(ElInput, 'input'),
|
||||||
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
|
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
|
||||||
RadioGroup: ElRadioGroup,
|
RadioGroup: (props, { attrs, slots }) => {
|
||||||
Select: withDefaultPlaceholder(ElSelect, 'select'),
|
let defaultSlot;
|
||||||
|
if (Reflect.has(slots, 'default')) {
|
||||||
|
defaultSlot = slots.default;
|
||||||
|
} else {
|
||||||
|
const { options } = attrs;
|
||||||
|
if (Array.isArray(options)) {
|
||||||
|
defaultSlot = () =>
|
||||||
|
options.map((option) =>
|
||||||
|
h(attrs.isButton ? ElRadioButton : ElRadio, option),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
ElRadioGroup,
|
||||||
|
{ ...props, ...attrs },
|
||||||
|
{ ...slots, default: defaultSlot },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Select: (props, { attrs, slots }) => {
|
||||||
|
return h(ElSelectV2, { ...props, attrs }, slots);
|
||||||
|
},
|
||||||
Space: ElSpace,
|
Space: ElSpace,
|
||||||
Switch: ElSwitch,
|
Switch: ElSwitch,
|
||||||
TimePicker: ElTimePicker,
|
TimePicker: (props, { attrs, slots }) => {
|
||||||
|
const { name, id, isRange } = props;
|
||||||
|
const extraProps: Recordable<any> = {};
|
||||||
|
if (isRange) {
|
||||||
|
if (name && !Array.isArray(name)) {
|
||||||
|
extraProps.name = [name, `${name}_end`];
|
||||||
|
}
|
||||||
|
if (id && !Array.isArray(id)) {
|
||||||
|
extraProps.id = [id, `${id}_end`];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
ElTimePicker,
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
...extraProps,
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
DatePicker: (props, { attrs, slots }) => {
|
||||||
|
const { name, id, type } = props;
|
||||||
|
const extraProps: Recordable<any> = {};
|
||||||
|
if (type && type.includes('range')) {
|
||||||
|
if (name && !Array.isArray(name)) {
|
||||||
|
extraProps.name = [name, `${name}_end`];
|
||||||
|
}
|
||||||
|
if (id && !Array.isArray(id)) {
|
||||||
|
extraProps.id = [id, `${id}_end`];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
ElDatePicker,
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
...extraProps,
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
|
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
|
||||||
Upload: ElUpload,
|
Upload: ElUpload,
|
||||||
};
|
};
|
||||||
|
@@ -12,6 +12,7 @@ setupVbenForm<ComponentType>({
|
|||||||
config: {
|
config: {
|
||||||
modelPropNameMap: {
|
modelPropNameMap: {
|
||||||
Upload: 'fileList',
|
Upload: 'fileList',
|
||||||
|
CheckboxGroup: 'model-value',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defineRules: {
|
defineRules: {
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* 该文件可自行根据业务逻辑进行调整
|
* 该文件可自行根据业务逻辑进行调整
|
||||||
*/
|
*/
|
||||||
import type { HttpResponse } from '@vben/request';
|
import type { RequestClientOptions } from '@vben/request';
|
||||||
|
|
||||||
import { useAppConfig } from '@vben/hooks';
|
import { useAppConfig } from '@vben/hooks';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import {
|
import {
|
||||||
authenticateResponseInterceptor,
|
authenticateResponseInterceptor,
|
||||||
|
defaultResponseInterceptor,
|
||||||
errorMessageResponseInterceptor,
|
errorMessageResponseInterceptor,
|
||||||
RequestClient,
|
RequestClient,
|
||||||
} from '@vben/request';
|
} from '@vben/request';
|
||||||
@@ -20,8 +21,9 @@ import { refreshTokenApi } from './core';
|
|||||||
|
|
||||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||||
|
|
||||||
function createRequestClient(baseURL: string) {
|
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
const client = new RequestClient({
|
const client = new RequestClient({
|
||||||
|
...options,
|
||||||
baseURL,
|
baseURL,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -69,18 +71,14 @@ function createRequestClient(baseURL: string) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// response数据解构
|
// 处理返回的响应数据格式
|
||||||
client.addResponseInterceptor<HttpResponse>({
|
client.addResponseInterceptor(
|
||||||
fulfilled: (response) => {
|
defaultResponseInterceptor({
|
||||||
const { data: responseData, status } = response;
|
codeField: 'code',
|
||||||
|
dataField: 'data',
|
||||||
const { code, data } = responseData;
|
successCode: 0,
|
||||||
if (status >= 200 && status < 400 && code === 0) {
|
}),
|
||||||
return data;
|
);
|
||||||
}
|
|
||||||
throw Object.assign({}, response, { response });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// token过期的处理
|
// token过期的处理
|
||||||
client.addResponseInterceptor(
|
client.addResponseInterceptor(
|
||||||
@@ -108,6 +106,8 @@ function createRequestClient(baseURL: string) {
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const requestClient = createRequestClient(apiURL);
|
export const requestClient = createRequestClient(apiURL, {
|
||||||
|
responseReturn: 'data',
|
||||||
|
});
|
||||||
|
|
||||||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
||||||
|
@@ -1,12 +1,15 @@
|
|||||||
import { createApp, watchEffect } from 'vue';
|
import { createApp, watchEffect } from 'vue';
|
||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
|
import { initTippy, registerLoadingDirective } from '@vben/common-ui';
|
||||||
|
import { MotionPlugin } from '@vben/plugins/motion';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { initStores } from '@vben/stores';
|
import { initStores } from '@vben/stores';
|
||||||
import '@vben/styles';
|
import '@vben/styles';
|
||||||
import '@vben/styles/ele';
|
import '@vben/styles/ele';
|
||||||
|
|
||||||
import { useTitle } from '@vueuse/core';
|
import { useTitle } from '@vueuse/core';
|
||||||
|
import { ElLoading } from 'element-plus';
|
||||||
|
|
||||||
import { $t, setupI18n } from '#/locales';
|
import { $t, setupI18n } from '#/locales';
|
||||||
|
|
||||||
@@ -17,8 +20,25 @@ import { router } from './router';
|
|||||||
async function bootstrap(namespace: string) {
|
async function bootstrap(namespace: string) {
|
||||||
// 初始化组件适配器
|
// 初始化组件适配器
|
||||||
await initComponentAdapter();
|
await initComponentAdapter();
|
||||||
|
// // 设置弹窗的默认配置
|
||||||
|
// setDefaultModalProps({
|
||||||
|
// fullscreenButton: false,
|
||||||
|
// });
|
||||||
|
// // 设置抽屉的默认配置
|
||||||
|
// setDefaultDrawerProps({
|
||||||
|
// zIndex: 2000,
|
||||||
|
// });
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
// 注册Element Plus提供的v-loading指令
|
||||||
|
app.directive('loading', ElLoading.directive);
|
||||||
|
|
||||||
|
// 注册Vben提供的v-loading和v-spinning指令
|
||||||
|
registerLoadingDirective(app, {
|
||||||
|
loading: false, // Vben提供的v-loading指令和Element Plus提供的v-loading指令二选一即可,此处false表示不注册Vben提供的v-loading指令
|
||||||
|
spinning: 'spinning',
|
||||||
|
});
|
||||||
|
|
||||||
// 国际化 i18n 配置
|
// 国际化 i18n 配置
|
||||||
await setupI18n(app);
|
await setupI18n(app);
|
||||||
|
|
||||||
@@ -28,9 +48,15 @@ async function bootstrap(namespace: string) {
|
|||||||
// 安装权限指令
|
// 安装权限指令
|
||||||
registerAccessDirective(app);
|
registerAccessDirective(app);
|
||||||
|
|
||||||
|
// 初始化 tippy
|
||||||
|
initTippy(app);
|
||||||
|
|
||||||
// 配置路由及路由守卫
|
// 配置路由及路由守卫
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
|
// 配置Motion插件
|
||||||
|
app.use(MotionPlugin);
|
||||||
|
|
||||||
// 动态更新标题
|
// 动态更新标题
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (preferences.app.dynamicTitle) {
|
if (preferences.app.dynamicTitle) {
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
|
||||||
import type { Language } from 'element-plus/es/locale';
|
import type { Language } from 'element-plus/es/locale';
|
||||||
|
|
||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
|
|
||||||
|
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"title": "Demos",
|
"title": "Demos",
|
||||||
"elementPlus": "Element Plus",
|
"elementPlus": "Element Plus",
|
||||||
|
"form": "Form",
|
||||||
"vben": {
|
"vben": {
|
||||||
"title": "Project",
|
"title": "Project",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"title": "演示",
|
"title": "演示",
|
||||||
"elementPlus": "Element Plus",
|
"elementPlus": "Element Plus",
|
||||||
|
"form": "表单演示",
|
||||||
"vben": {
|
"vben": {
|
||||||
"title": "项目",
|
"title": "项目",
|
||||||
"about": "关于",
|
"about": "关于",
|
||||||
|
@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
|||||||
/**
|
/**
|
||||||
* @description 项目配置文件
|
* @description 项目配置文件
|
||||||
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
||||||
|
* !!! 更改配置后请清空缓存,否则可能不生效
|
||||||
*/
|
*/
|
||||||
export const overridesPreferences = defineOverridesPreferences({
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
// overrides
|
// overrides
|
||||||
|
@@ -54,7 +54,9 @@ function setupAccessGuard(router: Router) {
|
|||||||
if (coreRouteNames.includes(to.name as string)) {
|
if (coreRouteNames.includes(to.name as string)) {
|
||||||
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
||||||
return decodeURIComponent(
|
return decodeURIComponent(
|
||||||
(to.query?.redirect as string) || DEFAULT_HOME_PATH,
|
(to.query?.redirect as string) ||
|
||||||
|
userStore.userInfo?.homePath ||
|
||||||
|
DEFAULT_HOME_PATH,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -72,7 +74,10 @@ function setupAccessGuard(router: Router) {
|
|||||||
return {
|
return {
|
||||||
path: LOGIN_PATH,
|
path: LOGIN_PATH,
|
||||||
// 如不需要,直接删除 query
|
// 如不需要,直接删除 query
|
||||||
query: { redirect: encodeURIComponent(to.fullPath) },
|
query:
|
||||||
|
to.fullPath === DEFAULT_HOME_PATH
|
||||||
|
? {}
|
||||||
|
: { redirect: encodeURIComponent(to.fullPath) },
|
||||||
// 携带当前跳转的页面,登录后重新跳转该页面
|
// 携带当前跳转的页面,登录后重新跳转该页面
|
||||||
replace: true,
|
replace: true,
|
||||||
};
|
};
|
||||||
@@ -102,7 +107,10 @@ function setupAccessGuard(router: Router) {
|
|||||||
accessStore.setAccessMenus(accessibleMenus);
|
accessStore.setAccessMenus(accessibleMenus);
|
||||||
accessStore.setAccessRoutes(accessibleRoutes);
|
accessStore.setAccessRoutes(accessibleRoutes);
|
||||||
accessStore.setIsAccessChecked(true);
|
accessStore.setIsAccessChecked(true);
|
||||||
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
|
const redirectPath = (from.query.redirect ??
|
||||||
|
(to.path === DEFAULT_HOME_PATH
|
||||||
|
? userInfo.homePath || DEFAULT_HOME_PATH
|
||||||
|
: to.fullPath)) as string;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...router.resolve(decodeURIComponent(redirectPath)),
|
...router.resolve(decodeURIComponent(redirectPath)),
|
||||||
|
@@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router';
|
|||||||
|
|
||||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||||
|
|
||||||
import { AuthPageLayout } from '#/layouts';
|
import { AuthPageLayout, BasicLayout } from '#/layouts';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import Login from '#/views/_core/authentication/login.vue';
|
import Login from '#/views/_core/authentication/login.vue';
|
||||||
|
|
||||||
@@ -21,13 +21,21 @@ const fallbackNotFoundRoute: RouteRecordRaw = {
|
|||||||
|
|
||||||
/** 基本路由,这些路由是必须存在的 */
|
/** 基本路由,这些路由是必须存在的 */
|
||||||
const coreRoutes: RouteRecordRaw[] = [
|
const coreRoutes: RouteRecordRaw[] = [
|
||||||
|
/**
|
||||||
|
* 根路由
|
||||||
|
* 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。
|
||||||
|
* 此路由必须存在,且不应修改
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
|
component: BasicLayout,
|
||||||
meta: {
|
meta: {
|
||||||
|
hideInBreadcrumb: true,
|
||||||
title: 'Root',
|
title: 'Root',
|
||||||
},
|
},
|
||||||
name: 'Root',
|
name: 'Root',
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: DEFAULT_HOME_PATH,
|
redirect: DEFAULT_HOME_PATH,
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: AuthPageLayout,
|
component: AuthPageLayout,
|
||||||
|
@@ -17,12 +17,12 @@ const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
|
|||||||
|
|
||||||
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */
|
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */
|
||||||
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
|
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
|
||||||
/** 不需要权限的菜单列表(会显示在菜单中) */
|
|
||||||
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
|
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
|
||||||
const staticRoutes: RouteRecordRaw[] = [];
|
const staticRoutes: RouteRecordRaw[] = [];
|
||||||
const externalRoutes: RouteRecordRaw[] = [];
|
const externalRoutes: RouteRecordRaw[] = [];
|
||||||
|
|
||||||
/** 路由列表,由基本路由+静态路由组成 */
|
/** 路由列表,由基本路由、外部路由和404兜底路由组成
|
||||||
|
* 无需走权限验证(会一直显示在菜单中) */
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
...coreRoutes,
|
...coreRoutes,
|
||||||
...externalRoutes,
|
...externalRoutes,
|
||||||
@@ -32,5 +32,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
||||||
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
|
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
|
||||||
|
|
||||||
|
/** 有权限校验的路由列表,包含动态路由和静态路由 */
|
||||||
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
|
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
|
||||||
export { accessRoutes, coreRouteNames, routes };
|
export { accessRoutes, coreRouteNames, routes };
|
||||||
|
@@ -1,18 +1,16 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
import { BasicLayout } from '#/layouts';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
component: BasicLayout,
|
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'lucide:layout-dashboard',
|
icon: 'lucide:layout-dashboard',
|
||||||
order: -1,
|
order: -1,
|
||||||
title: $t('page.dashboard.title'),
|
title: $t('page.dashboard.title'),
|
||||||
},
|
},
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
path: '/',
|
path: '/dashboard',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'Analytics',
|
name: 'Analytics',
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
import { BasicLayout } from '#/layouts';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
component: BasicLayout,
|
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'ic:baseline-view-in-ar',
|
icon: 'ic:baseline-view-in-ar',
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
@@ -23,6 +21,14 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '/demos/element',
|
path: '/demos/element',
|
||||||
component: () => import('#/views/demos/element/index.vue'),
|
component: () => import('#/views/demos/element/index.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
title: $t('demos.form'),
|
||||||
|
},
|
||||||
|
name: 'BasicForm',
|
||||||
|
path: '/demos/form',
|
||||||
|
component: () => import('#/views/demos/form/basic.vue'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@@ -9,30 +9,20 @@ import {
|
|||||||
} from '@vben/constants';
|
} from '@vben/constants';
|
||||||
import { SvgAntdvLogoIcon } from '@vben/icons';
|
import { SvgAntdvLogoIcon } from '@vben/icons';
|
||||||
|
|
||||||
import { BasicLayout, IFrameView } from '#/layouts';
|
import { IFrameView } from '#/layouts';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
component: BasicLayout,
|
|
||||||
meta: {
|
meta: {
|
||||||
badgeType: 'dot',
|
badgeType: 'dot',
|
||||||
icon: VBEN_LOGO_URL,
|
icon: VBEN_LOGO_URL,
|
||||||
order: 9999,
|
order: 9998,
|
||||||
title: $t('demos.vben.title'),
|
title: $t('demos.vben.title'),
|
||||||
},
|
},
|
||||||
name: 'VbenProject',
|
name: 'VbenProject',
|
||||||
path: '/vben-admin',
|
path: '/vben-admin',
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
name: 'VbenAbout',
|
|
||||||
path: '/vben-admin/about',
|
|
||||||
component: () => import('#/views/_core/about/index.vue'),
|
|
||||||
meta: {
|
|
||||||
icon: 'lucide:copyright',
|
|
||||||
title: $t('demos.vben.about'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'VbenDocument',
|
name: 'VbenDocument',
|
||||||
path: '/vben-admin/document',
|
path: '/vben-admin/document',
|
||||||
@@ -77,6 +67,16 @@ const routes: RouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'VbenAbout',
|
||||||
|
path: '/vben-admin/about',
|
||||||
|
component: () => import('#/views/_core/about/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:copyright',
|
||||||
|
title: $t('demos.vben.about'),
|
||||||
|
order: 9999,
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
||||||
|
@@ -10,6 +10,7 @@ import { $t } from '@vben/locales';
|
|||||||
defineOptions({ name: 'CodeLogin' });
|
defineOptions({ name: 'CodeLogin' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const CODE_LENGTH = 6;
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
return [
|
return [
|
||||||
@@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
{
|
{
|
||||||
component: 'VbenPinInput',
|
component: 'VbenPinInput',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
codeLength: CODE_LENGTH,
|
||||||
createText: (countdown: number) => {
|
createText: (countdown: number) => {
|
||||||
const text =
|
const text =
|
||||||
countdown > 0
|
countdown > 0
|
||||||
@@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
},
|
},
|
||||||
fieldName: 'code',
|
fieldName: 'code',
|
||||||
label: $t('authentication.code'),
|
label: $t('authentication.code'),
|
||||||
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
|
rules: z.string().length(CODE_LENGTH, {
|
||||||
|
message: $t('authentication.codeTip', [CODE_LENGTH]),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -15,10 +15,10 @@ import {
|
|||||||
} from '@vben/icons';
|
} from '@vben/icons';
|
||||||
|
|
||||||
import AnalyticsTrends from './analytics-trends.vue';
|
import AnalyticsTrends from './analytics-trends.vue';
|
||||||
import AnalyticsVisits from './analytics-visits.vue';
|
|
||||||
import AnalyticsVisitsData from './analytics-visits-data.vue';
|
import AnalyticsVisitsData from './analytics-visits-data.vue';
|
||||||
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
|
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
|
||||||
import AnalyticsVisitsSource from './analytics-visits-source.vue';
|
import AnalyticsVisitsSource from './analytics-visits-source.vue';
|
||||||
|
import AnalyticsVisits from './analytics-visits.vue';
|
||||||
|
|
||||||
const overviewItems: AnalysisOverviewItem[] = [
|
const overviewItems: AnalysisOverviewItem[] = [
|
||||||
{
|
{
|
||||||
|
@@ -7,6 +7,7 @@ import type {
|
|||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AnalysisChartCard,
|
AnalysisChartCard,
|
||||||
@@ -18,11 +19,15 @@ import {
|
|||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { useUserStore } from '@vben/stores';
|
import { useUserStore } from '@vben/stores';
|
||||||
|
import { openWindow } from '@vben/utils';
|
||||||
|
|
||||||
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
|
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
|
||||||
|
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
|
||||||
|
// 例如:url: /dashboard/workspace
|
||||||
const projectItems: WorkbenchProjectItem[] = [
|
const projectItems: WorkbenchProjectItem[] = [
|
||||||
{
|
{
|
||||||
color: '',
|
color: '',
|
||||||
@@ -31,6 +36,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '开源组',
|
group: '开源组',
|
||||||
icon: 'carbon:logo-github',
|
icon: 'carbon:logo-github',
|
||||||
title: 'Github',
|
title: 'Github',
|
||||||
|
url: 'https://github.com',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#3fb27f',
|
color: '#3fb27f',
|
||||||
@@ -39,6 +45,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '算法组',
|
group: '算法组',
|
||||||
icon: 'ion:logo-vue',
|
icon: 'ion:logo-vue',
|
||||||
title: 'Vue',
|
title: 'Vue',
|
||||||
|
url: 'https://vuejs.org',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#e18525',
|
color: '#e18525',
|
||||||
@@ -47,6 +54,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '上班摸鱼',
|
group: '上班摸鱼',
|
||||||
icon: 'ion:logo-html5',
|
icon: 'ion:logo-html5',
|
||||||
title: 'Html5',
|
title: 'Html5',
|
||||||
|
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#bf0c2c',
|
color: '#bf0c2c',
|
||||||
@@ -55,6 +63,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: 'UI',
|
group: 'UI',
|
||||||
icon: 'ion:logo-angular',
|
icon: 'ion:logo-angular',
|
||||||
title: 'Angular',
|
title: 'Angular',
|
||||||
|
url: 'https://angular.io',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#00d8ff',
|
color: '#00d8ff',
|
||||||
@@ -63,6 +72,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '技术牛',
|
group: '技术牛',
|
||||||
icon: 'bx:bxl-react',
|
icon: 'bx:bxl-react',
|
||||||
title: 'React',
|
title: 'React',
|
||||||
|
url: 'https://reactjs.org',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#EBD94E',
|
color: '#EBD94E',
|
||||||
@@ -71,39 +81,47 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '架构组',
|
group: '架构组',
|
||||||
icon: 'ion:logo-javascript',
|
icon: 'ion:logo-javascript',
|
||||||
title: 'Js',
|
title: 'Js',
|
||||||
|
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 同样,这里的 url 也可以使用以 http 开头的外部链接
|
||||||
const quickNavItems: WorkbenchQuickNavItem[] = [
|
const quickNavItems: WorkbenchQuickNavItem[] = [
|
||||||
{
|
{
|
||||||
color: '#1fdaca',
|
color: '#1fdaca',
|
||||||
icon: 'ion:home-outline',
|
icon: 'ion:home-outline',
|
||||||
title: '首页',
|
title: '首页',
|
||||||
|
url: '/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#bf0c2c',
|
color: '#bf0c2c',
|
||||||
icon: 'ion:grid-outline',
|
icon: 'ion:grid-outline',
|
||||||
title: '仪表盘',
|
title: '仪表盘',
|
||||||
|
url: '/dashboard',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#e18525',
|
color: '#e18525',
|
||||||
icon: 'ion:layers-outline',
|
icon: 'ion:layers-outline',
|
||||||
title: '组件',
|
title: '组件',
|
||||||
|
url: '/demos/features/icons',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#3fb27f',
|
color: '#3fb27f',
|
||||||
icon: 'ion:settings-outline',
|
icon: 'ion:settings-outline',
|
||||||
title: '系统管理',
|
title: '系统管理',
|
||||||
|
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#4daf1bc9',
|
color: '#4daf1bc9',
|
||||||
icon: 'ion:key-outline',
|
icon: 'ion:key-outline',
|
||||||
title: '权限管理',
|
title: '权限管理',
|
||||||
|
url: '/demos/access/page-control',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#00d8ff',
|
color: '#00d8ff',
|
||||||
icon: 'ion:bar-chart-outline',
|
icon: 'ion:bar-chart-outline',
|
||||||
title: '图表',
|
title: '图表',
|
||||||
|
url: '/analytics',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -195,6 +213,24 @@ const trendItems: WorkbenchTrendItem[] = [
|
|||||||
title: 'Vben',
|
title: 'Vben',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
|
||||||
|
// This is a sample method, adjust according to the actual project requirements
|
||||||
|
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
|
||||||
|
if (nav.url?.startsWith('http')) {
|
||||||
|
openWindow(nav.url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nav.url?.startsWith('/')) {
|
||||||
|
router.push(nav.url).catch((error) => {
|
||||||
|
console.error('Navigation failed:', error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -210,7 +246,7 @@ const trendItems: WorkbenchTrendItem[] = [
|
|||||||
|
|
||||||
<div class="mt-5 flex flex-col lg:flex-row">
|
<div class="mt-5 flex flex-col lg:flex-row">
|
||||||
<div class="mr-4 w-full lg:w-3/5">
|
<div class="mr-4 w-full lg:w-3/5">
|
||||||
<WorkbenchProject :items="projectItems" title="项目" />
|
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
|
||||||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full lg:w-2/5">
|
<div class="w-full lg:w-2/5">
|
||||||
@@ -218,6 +254,7 @@ const trendItems: WorkbenchTrendItem[] = [
|
|||||||
:items="quickNavItems"
|
:items="quickNavItems"
|
||||||
class="mt-5 lg:mt-0"
|
class="mt-5 lg:mt-0"
|
||||||
title="快捷导航"
|
title="快捷导航"
|
||||||
|
@click="navTo"
|
||||||
/>
|
/>
|
||||||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
||||||
<AnalysisChartCard class="mt-5" title="访问来源">
|
<AnalysisChartCard class="mt-5" title="访问来源">
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -6,6 +8,7 @@ import {
|
|||||||
ElCard,
|
ElCard,
|
||||||
ElMessage,
|
ElMessage,
|
||||||
ElNotification,
|
ElNotification,
|
||||||
|
ElSegmented,
|
||||||
ElSpace,
|
ElSpace,
|
||||||
ElTable,
|
ElTable,
|
||||||
} from 'element-plus';
|
} from 'element-plus';
|
||||||
@@ -47,6 +50,10 @@ const tableData = [
|
|||||||
{ prop1: '5', prop2: 'E' },
|
{ prop1: '5', prop2: 'E' },
|
||||||
{ prop1: '6', prop2: 'F' },
|
{ prop1: '6', prop2: 'F' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const segmentedValue = ref('Mon');
|
||||||
|
|
||||||
|
const segmentedOptions = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -54,7 +61,8 @@ const tableData = [
|
|||||||
description="支持多语言,主题功能集成切换等"
|
description="支持多语言,主题功能集成切换等"
|
||||||
title="Element Plus组件使用演示"
|
title="Element Plus组件使用演示"
|
||||||
>
|
>
|
||||||
<ElCard class="mb-5">
|
<div class="flex flex-wrap gap-5">
|
||||||
|
<ElCard class="mb-5 w-auto">
|
||||||
<template #header> 按钮 </template>
|
<template #header> 按钮 </template>
|
||||||
<ElSpace>
|
<ElSpace>
|
||||||
<ElButton text>Text</ElButton>
|
<ElButton text>Text</ElButton>
|
||||||
@@ -66,7 +74,7 @@ const tableData = [
|
|||||||
<ElButton type="danger"> Error </ElButton>
|
<ElButton type="danger"> Error </ElButton>
|
||||||
</ElSpace>
|
</ElSpace>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
<ElCard class="mb-5">
|
<ElCard class="mb-5 w-80">
|
||||||
<template #header> Message </template>
|
<template #header> Message </template>
|
||||||
<ElSpace>
|
<ElSpace>
|
||||||
<ElButton type="info" @click="info"> 信息 </ElButton>
|
<ElButton type="info" @click="info"> 信息 </ElButton>
|
||||||
@@ -75,7 +83,7 @@ const tableData = [
|
|||||||
<ElButton type="success" @click="success"> 成功 </ElButton>
|
<ElButton type="success" @click="success"> 成功 </ElButton>
|
||||||
</ElSpace>
|
</ElSpace>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
<ElCard class="mb-5">
|
<ElCard class="mb-5 w-80">
|
||||||
<template #header> Notification </template>
|
<template #header> Notification </template>
|
||||||
<ElSpace>
|
<ElSpace>
|
||||||
<ElButton type="info" @click="notify('info')"> 信息 </ElButton>
|
<ElButton type="info" @click="notify('info')"> 信息 </ElButton>
|
||||||
@@ -84,11 +92,26 @@ const tableData = [
|
|||||||
<ElButton type="success" @click="notify('success')"> 成功 </ElButton>
|
<ElButton type="success" @click="notify('success')"> 成功 </ElButton>
|
||||||
</ElSpace>
|
</ElSpace>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
<ElCard class="mb-5">
|
<ElCard class="mb-5 w-auto">
|
||||||
|
<template #header> Segmented </template>
|
||||||
|
<ElSegmented
|
||||||
|
v-model="segmentedValue"
|
||||||
|
:options="segmentedOptions"
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</ElCard>
|
||||||
|
<ElCard class="mb-5 w-80">
|
||||||
|
<template #header> V-Loading </template>
|
||||||
|
<div class="flex size-72 items-center justify-center" v-loading="true">
|
||||||
|
一些演示的内容
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
<ElCard class="mb-5 w-80">
|
||||||
<ElTable :data="tableData" stripe>
|
<ElTable :data="tableData" stripe>
|
||||||
<ElTable.TableColumn label="测试列1" prop="prop1" />
|
<ElTable.TableColumn label="测试列1" prop="prop1" />
|
||||||
<ElTable.TableColumn label="测试列2" prop="prop2" />
|
<ElTable.TableColumn label="测试列2" prop="prop2" />
|
||||||
</ElTable>
|
</ElTable>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
181
apps/web-ele/src/views/demos/form/basic.vue
Normal file
181
apps/web-ele/src/views/demos/form/basic.vue
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { ElButton, ElCard, ElCheckbox, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { getAllMenusApi } from '#/api';
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
// 所有表单项
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layout: 'horizontal',
|
||||||
|
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
||||||
|
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||||
|
handleSubmit: (values) => {
|
||||||
|
ElMessage.success(`表单数据:${JSON.stringify(values)}`);
|
||||||
|
},
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||||
|
component: 'ApiSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口转options格式
|
||||||
|
afterFetch: (data: { name: string; path: string }[]) => {
|
||||||
|
return data.map((item: any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.path,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
// 菜单接口
|
||||||
|
api: getAllMenusApi,
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'api',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'ApiSelect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口
|
||||||
|
api: getAllMenusApi,
|
||||||
|
childrenField: 'children',
|
||||||
|
// 菜单接口转options格式
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'path',
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'apiTree',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'ApiTreeSelect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'string',
|
||||||
|
label: 'String',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'number',
|
||||||
|
label: 'Number',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
fieldName: 'radio',
|
||||||
|
label: 'Radio',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ value: 'A', label: 'A' },
|
||||||
|
{ value: 'B', label: 'B' },
|
||||||
|
{ value: 'C', label: 'C' },
|
||||||
|
{ value: 'D', label: 'D' },
|
||||||
|
{ value: 'E', label: 'E' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
fieldName: 'radioButton',
|
||||||
|
label: 'RadioButton',
|
||||||
|
componentProps: {
|
||||||
|
isButton: true,
|
||||||
|
options: ['A', 'B', 'C', 'D', 'E', 'F'].map((v) => ({
|
||||||
|
value: v,
|
||||||
|
label: `选项${v}`,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'CheckboxGroup',
|
||||||
|
fieldName: 'checkbox',
|
||||||
|
label: 'Checkbox',
|
||||||
|
componentProps: {
|
||||||
|
options: ['A', 'B', 'C'].map((v) => ({ value: v, label: `选项${v}` })),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'CheckboxGroup',
|
||||||
|
fieldName: 'checkbox1',
|
||||||
|
label: 'Checkbox1',
|
||||||
|
renderComponentContent: () => {
|
||||||
|
return {
|
||||||
|
default: () => {
|
||||||
|
return ['A', 'B', 'C', 'D'].map((v) =>
|
||||||
|
h(ElCheckbox, { label: v, value: v }),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'CheckboxGroup',
|
||||||
|
fieldName: 'checkbotton',
|
||||||
|
label: 'CheckBotton',
|
||||||
|
componentProps: {
|
||||||
|
isButton: true,
|
||||||
|
options: [
|
||||||
|
{ value: 'A', label: '选项A' },
|
||||||
|
{ value: 'B', label: '选项B' },
|
||||||
|
{ value: 'C', label: '选项C' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'DatePicker',
|
||||||
|
fieldName: 'date',
|
||||||
|
label: 'Date',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'select',
|
||||||
|
label: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
filterable: true,
|
||||||
|
options: [
|
||||||
|
{ value: 'A', label: '选项A' },
|
||||||
|
{ value: 'B', label: '选项B' },
|
||||||
|
{ value: 'C', label: '选项C' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
function setFormValues() {
|
||||||
|
formApi.setValues({
|
||||||
|
string: 'string',
|
||||||
|
number: 123,
|
||||||
|
radio: 'B',
|
||||||
|
radioButton: 'C',
|
||||||
|
checkbox: ['A', 'C'],
|
||||||
|
checkbotton: ['B', 'C'],
|
||||||
|
checkbox1: ['A', 'B'],
|
||||||
|
date: new Date(),
|
||||||
|
select: 'B',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page
|
||||||
|
description="我们重新包装了CheckboxGroup、RadioGroup、Select,可以通过options属性传入选项属性数组以自动生成选项"
|
||||||
|
title="表单演示"
|
||||||
|
>
|
||||||
|
<ElCard>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="flex-auto">基础表单演示</span>
|
||||||
|
<ElButton type="primary" @click="setFormValues">设置表单值</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<Form />
|
||||||
|
</ElCard>
|
||||||
|
</Page>
|
||||||
|
</template>
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vben/web-naive",
|
"name": "@vben/web-naive",
|
||||||
"version": "5.4.4",
|
"version": "5.5.4",
|
||||||
"homepage": "https://vben.pro",
|
"homepage": "https://vben.pro",
|
||||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@@ -3,12 +3,14 @@
|
|||||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
import { defineComponent, getCurrentInstance, h, ref } from 'vue';
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { globalShareState } from '@vben/common-ui';
|
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -19,6 +21,8 @@ import {
|
|||||||
NDivider,
|
NDivider,
|
||||||
NInput,
|
NInput,
|
||||||
NInputNumber,
|
NInputNumber,
|
||||||
|
NRadio,
|
||||||
|
NRadioButton,
|
||||||
NRadioGroup,
|
NRadioGroup,
|
||||||
NSelect,
|
NSelect,
|
||||||
NSpace,
|
NSpace,
|
||||||
@@ -34,18 +38,41 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
component: T,
|
component: T,
|
||||||
type: 'input' | 'select',
|
type: 'input' | 'select',
|
||||||
) => {
|
) => {
|
||||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
return defineComponent({
|
||||||
const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);
|
inheritAttrs: false,
|
||||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
name: component.name,
|
||||||
};
|
setup: (props: any, { attrs, expose, slots }) => {
|
||||||
|
const placeholder =
|
||||||
|
props?.placeholder ||
|
||||||
|
attrs?.placeholder ||
|
||||||
|
$t(`ui.placeholder.${type}`);
|
||||||
|
// 透传组件暴露的方法
|
||||||
|
const innerRef = ref();
|
||||||
|
const publicApi: Recordable<any> = {};
|
||||||
|
expose(publicApi);
|
||||||
|
const instance = getCurrentInstance();
|
||||||
|
instance?.proxy?.$nextTick(() => {
|
||||||
|
for (const key in innerRef.value) {
|
||||||
|
if (typeof innerRef.value[key] === 'function') {
|
||||||
|
publicApi[key] = innerRef.value[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () =>
|
||||||
|
h(component, { ...props, ...attrs, placeholder, ref: innerRef }, slots);
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type ComponentType =
|
export type ComponentType =
|
||||||
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect'
|
||||||
| 'Checkbox'
|
| 'Checkbox'
|
||||||
| 'CheckboxGroup'
|
| 'CheckboxGroup'
|
||||||
| 'DatePicker'
|
| 'DatePicker'
|
||||||
| 'Divider'
|
| 'Divider'
|
||||||
|
| 'IconPicker'
|
||||||
| 'Input'
|
| 'Input'
|
||||||
| 'InputNumber'
|
| 'InputNumber'
|
||||||
| 'RadioGroup'
|
| 'RadioGroup'
|
||||||
@@ -63,8 +90,54 @@ async function initComponentAdapter() {
|
|||||||
// Button: () =>
|
// Button: () =>
|
||||||
// import('xxx').then((res) => res.Button),
|
// import('xxx').then((res) => res.Button),
|
||||||
|
|
||||||
|
ApiSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiComponent,
|
||||||
|
{
|
||||||
|
placeholder: $t('ui.placeholder.select'),
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: NSelect,
|
||||||
|
modelPropName: 'value',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ApiTreeSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiComponent,
|
||||||
|
{
|
||||||
|
placeholder: $t('ui.placeholder.select'),
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: NTreeSelect,
|
||||||
|
nodeKey: 'value',
|
||||||
|
loadingSlot: 'arrow',
|
||||||
|
keyField: 'value',
|
||||||
|
modelPropName: 'value',
|
||||||
|
optionsPropName: 'options',
|
||||||
|
visibleEvent: 'onVisibleChange',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
Checkbox: NCheckbox,
|
Checkbox: NCheckbox,
|
||||||
CheckboxGroup: NCheckboxGroup,
|
CheckboxGroup: (props, { attrs, slots }) => {
|
||||||
|
let defaultSlot;
|
||||||
|
if (Reflect.has(slots, 'default')) {
|
||||||
|
defaultSlot = slots.default;
|
||||||
|
} else {
|
||||||
|
const { options } = attrs;
|
||||||
|
if (Array.isArray(options)) {
|
||||||
|
defaultSlot = () => options.map((option) => h(NCheckbox, option));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
NCheckboxGroup,
|
||||||
|
{ ...props, ...attrs },
|
||||||
|
{ default: defaultSlot },
|
||||||
|
);
|
||||||
|
},
|
||||||
DatePicker: NDatePicker,
|
DatePicker: NDatePicker,
|
||||||
// 自定义默认按钮
|
// 自定义默认按钮
|
||||||
DefaultButton: (props, { attrs, slots }) => {
|
DefaultButton: (props, { attrs, slots }) => {
|
||||||
@@ -75,9 +148,37 @@ async function initComponentAdapter() {
|
|||||||
return h(NButton, { ...props, attrs, type: 'primary' }, slots);
|
return h(NButton, { ...props, attrs, type: 'primary' }, slots);
|
||||||
},
|
},
|
||||||
Divider: NDivider,
|
Divider: NDivider,
|
||||||
|
IconPicker: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
IconPicker,
|
||||||
|
{ iconSlot: 'suffix', inputComponent: NInput, ...props, ...attrs },
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
Input: withDefaultPlaceholder(NInput, 'input'),
|
Input: withDefaultPlaceholder(NInput, 'input'),
|
||||||
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
|
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
|
||||||
RadioGroup: NRadioGroup,
|
RadioGroup: (props, { attrs, slots }) => {
|
||||||
|
let defaultSlot;
|
||||||
|
if (Reflect.has(slots, 'default')) {
|
||||||
|
defaultSlot = slots.default;
|
||||||
|
} else {
|
||||||
|
const { options } = attrs;
|
||||||
|
if (Array.isArray(options)) {
|
||||||
|
defaultSlot = () =>
|
||||||
|
options.map((option) =>
|
||||||
|
h(attrs.isButton ? NRadioButton : NRadio, option),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const groupRender = h(
|
||||||
|
NRadioGroup,
|
||||||
|
{ ...props, ...attrs },
|
||||||
|
{ default: defaultSlot },
|
||||||
|
);
|
||||||
|
return attrs.isButton
|
||||||
|
? h(NSpace, { vertical: true }, () => groupRender)
|
||||||
|
: groupRender;
|
||||||
|
},
|
||||||
Select: withDefaultPlaceholder(NSelect, 'select'),
|
Select: withDefaultPlaceholder(NSelect, 'select'),
|
||||||
Space: NSpace,
|
Space: NSpace,
|
||||||
Switch: NSwitch,
|
Switch: NSwitch,
|
||||||
|
@@ -10,8 +10,6 @@ import { $t } from '@vben/locales';
|
|||||||
|
|
||||||
setupVbenForm<ComponentType>({
|
setupVbenForm<ComponentType>({
|
||||||
config: {
|
config: {
|
||||||
// naive-ui组件不接受onChang事件,所以需要禁用
|
|
||||||
disabledOnChangeListener: true,
|
|
||||||
// naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效
|
// naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效
|
||||||
emptyStateValue: null,
|
emptyStateValue: null,
|
||||||
baseModelPropName: 'value',
|
baseModelPropName: 'value',
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* 该文件可自行根据业务逻辑进行调整
|
* 该文件可自行根据业务逻辑进行调整
|
||||||
*/
|
*/
|
||||||
import type { HttpResponse } from '@vben/request';
|
import type { RequestClientOptions } from '@vben/request';
|
||||||
|
|
||||||
import { useAppConfig } from '@vben/hooks';
|
import { useAppConfig } from '@vben/hooks';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import {
|
import {
|
||||||
authenticateResponseInterceptor,
|
authenticateResponseInterceptor,
|
||||||
|
defaultResponseInterceptor,
|
||||||
errorMessageResponseInterceptor,
|
errorMessageResponseInterceptor,
|
||||||
RequestClient,
|
RequestClient,
|
||||||
} from '@vben/request';
|
} from '@vben/request';
|
||||||
@@ -19,8 +20,9 @@ import { refreshTokenApi } from './core';
|
|||||||
|
|
||||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||||
|
|
||||||
function createRequestClient(baseURL: string) {
|
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
const client = new RequestClient({
|
const client = new RequestClient({
|
||||||
|
...options,
|
||||||
baseURL,
|
baseURL,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -68,18 +70,14 @@ function createRequestClient(baseURL: string) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// response数据解构
|
// 处理返回的响应数据格式
|
||||||
client.addResponseInterceptor<HttpResponse>({
|
client.addResponseInterceptor(
|
||||||
fulfilled: (response) => {
|
defaultResponseInterceptor({
|
||||||
const { data: responseData, status } = response;
|
codeField: 'code',
|
||||||
|
dataField: 'data',
|
||||||
const { code, data } = responseData;
|
successCode: 0,
|
||||||
if (status >= 200 && status < 400 && code === 0) {
|
}),
|
||||||
return data;
|
);
|
||||||
}
|
|
||||||
throw Object.assign({}, response, { response });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// token过期的处理
|
// token过期的处理
|
||||||
client.addResponseInterceptor(
|
client.addResponseInterceptor(
|
||||||
@@ -107,6 +105,8 @@ function createRequestClient(baseURL: string) {
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const requestClient = createRequestClient(apiURL);
|
export const requestClient = createRequestClient(apiURL, {
|
||||||
|
responseReturn: 'data',
|
||||||
|
});
|
||||||
|
|
||||||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
import { createApp, watchEffect } from 'vue';
|
import { createApp, watchEffect } from 'vue';
|
||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
|
import { initTippy, registerLoadingDirective } from '@vben/common-ui';
|
||||||
|
import { MotionPlugin } from '@vben/plugins/motion';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { initStores } from '@vben/stores';
|
import { initStores } from '@vben/stores';
|
||||||
import '@vben/styles';
|
import '@vben/styles';
|
||||||
|
import '@vben/styles/naive';
|
||||||
|
|
||||||
import { useTitle } from '@vueuse/core';
|
import { useTitle } from '@vueuse/core';
|
||||||
|
|
||||||
@@ -16,8 +19,24 @@ import { router } from './router';
|
|||||||
async function bootstrap(namespace: string) {
|
async function bootstrap(namespace: string) {
|
||||||
// 初始化组件适配器
|
// 初始化组件适配器
|
||||||
initComponentAdapter();
|
initComponentAdapter();
|
||||||
|
|
||||||
|
// // 设置弹窗的默认配置
|
||||||
|
// setDefaultModalProps({
|
||||||
|
// fullscreenButton: false,
|
||||||
|
// });
|
||||||
|
// // 设置抽屉的默认配置
|
||||||
|
// setDefaultDrawerProps({
|
||||||
|
// // zIndex: 2000,
|
||||||
|
// });
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
// 注册v-loading指令
|
||||||
|
registerLoadingDirective(app, {
|
||||||
|
loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令
|
||||||
|
spinning: 'spinning',
|
||||||
|
});
|
||||||
|
|
||||||
// 国际化 i18n 配置
|
// 国际化 i18n 配置
|
||||||
await setupI18n(app);
|
await setupI18n(app);
|
||||||
|
|
||||||
@@ -27,9 +46,15 @@ async function bootstrap(namespace: string) {
|
|||||||
// 安装权限指令
|
// 安装权限指令
|
||||||
registerAccessDirective(app);
|
registerAccessDirective(app);
|
||||||
|
|
||||||
|
// 初始化 tippy
|
||||||
|
initTippy(app);
|
||||||
|
|
||||||
// 配置路由及路由守卫
|
// 配置路由及路由守卫
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
|
// 配置Motion插件
|
||||||
|
app.use(MotionPlugin);
|
||||||
|
|
||||||
// 动态更新标题
|
// 动态更新标题
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (preferences.app.dynamicTitle) {
|
if (preferences.app.dynamicTitle) {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
|
||||||
|
|
||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
|
|
||||||
|
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
$t,
|
$t,
|
||||||
setupI18n as coreSetup,
|
setupI18n as coreSetup,
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
"title": "Demos",
|
"title": "Demos",
|
||||||
"naive": "Naive UI",
|
"naive": "Naive UI",
|
||||||
"table": "Table",
|
"table": "Table",
|
||||||
|
"form": "Form",
|
||||||
"vben": {
|
"vben": {
|
||||||
"title": "Project",
|
"title": "Project",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
"title": "演示",
|
"title": "演示",
|
||||||
"naive": "Naive UI",
|
"naive": "Naive UI",
|
||||||
"table": "Table",
|
"table": "Table",
|
||||||
|
"form": "表单",
|
||||||
"vben": {
|
"vben": {
|
||||||
"title": "项目",
|
"title": "项目",
|
||||||
"about": "关于",
|
"about": "关于",
|
||||||
|
@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
|||||||
/**
|
/**
|
||||||
* @description 项目配置文件
|
* @description 项目配置文件
|
||||||
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
||||||
|
* !!! 更改配置后请清空缓存,否则可能不生效
|
||||||
*/
|
*/
|
||||||
export const overridesPreferences = defineOverridesPreferences({
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
// overrides
|
// overrides
|
||||||
|
@@ -54,7 +54,9 @@ function setupAccessGuard(router: Router) {
|
|||||||
if (coreRouteNames.includes(to.name as string)) {
|
if (coreRouteNames.includes(to.name as string)) {
|
||||||
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
||||||
return decodeURIComponent(
|
return decodeURIComponent(
|
||||||
(to.query?.redirect as string) || DEFAULT_HOME_PATH,
|
(to.query?.redirect as string) ||
|
||||||
|
userStore.userInfo?.homePath ||
|
||||||
|
DEFAULT_HOME_PATH,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -72,7 +74,10 @@ function setupAccessGuard(router: Router) {
|
|||||||
return {
|
return {
|
||||||
path: LOGIN_PATH,
|
path: LOGIN_PATH,
|
||||||
// 如不需要,直接删除 query
|
// 如不需要,直接删除 query
|
||||||
query: { redirect: encodeURIComponent(to.fullPath) },
|
query:
|
||||||
|
to.fullPath === DEFAULT_HOME_PATH
|
||||||
|
? {}
|
||||||
|
: { redirect: encodeURIComponent(to.fullPath) },
|
||||||
// 携带当前跳转的页面,登录后重新跳转该页面
|
// 携带当前跳转的页面,登录后重新跳转该页面
|
||||||
replace: true,
|
replace: true,
|
||||||
};
|
};
|
||||||
@@ -101,7 +106,10 @@ function setupAccessGuard(router: Router) {
|
|||||||
accessStore.setAccessMenus(accessibleMenus);
|
accessStore.setAccessMenus(accessibleMenus);
|
||||||
accessStore.setAccessRoutes(accessibleRoutes);
|
accessStore.setAccessRoutes(accessibleRoutes);
|
||||||
accessStore.setIsAccessChecked(true);
|
accessStore.setIsAccessChecked(true);
|
||||||
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
|
const redirectPath = (from.query.redirect ??
|
||||||
|
(to.path === DEFAULT_HOME_PATH
|
||||||
|
? userInfo.homePath || DEFAULT_HOME_PATH
|
||||||
|
: to.fullPath)) as string;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...router.resolve(decodeURIComponent(redirectPath)),
|
...router.resolve(decodeURIComponent(redirectPath)),
|
||||||
|
@@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router';
|
|||||||
|
|
||||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||||
|
|
||||||
import { AuthPageLayout } from '#/layouts';
|
import { AuthPageLayout, BasicLayout } from '#/layouts';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import Login from '#/views/_core/authentication/login.vue';
|
import Login from '#/views/_core/authentication/login.vue';
|
||||||
|
|
||||||
@@ -21,13 +21,21 @@ const fallbackNotFoundRoute: RouteRecordRaw = {
|
|||||||
|
|
||||||
/** 基本路由,这些路由是必须存在的 */
|
/** 基本路由,这些路由是必须存在的 */
|
||||||
const coreRoutes: RouteRecordRaw[] = [
|
const coreRoutes: RouteRecordRaw[] = [
|
||||||
|
/**
|
||||||
|
* 根路由
|
||||||
|
* 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。
|
||||||
|
* 此路由必须存在,且不应修改
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
|
component: BasicLayout,
|
||||||
meta: {
|
meta: {
|
||||||
|
hideInBreadcrumb: true,
|
||||||
title: 'Root',
|
title: 'Root',
|
||||||
},
|
},
|
||||||
name: 'Root',
|
name: 'Root',
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: DEFAULT_HOME_PATH,
|
redirect: DEFAULT_HOME_PATH,
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: AuthPageLayout,
|
component: AuthPageLayout,
|
||||||
|
@@ -17,12 +17,12 @@ const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
|
|||||||
|
|
||||||
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */
|
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */
|
||||||
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
|
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
|
||||||
/** 不需要权限的菜单列表(会显示在菜单中) */
|
|
||||||
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
|
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
|
||||||
const staticRoutes: RouteRecordRaw[] = [];
|
const staticRoutes: RouteRecordRaw[] = [];
|
||||||
const externalRoutes: RouteRecordRaw[] = [];
|
const externalRoutes: RouteRecordRaw[] = [];
|
||||||
|
|
||||||
/** 路由列表,由基本路由+静态路由组成 */
|
/** 路由列表,由基本路由、外部路由和404兜底路由组成
|
||||||
|
* 无需走权限验证(会一直显示在菜单中) */
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
...coreRoutes,
|
...coreRoutes,
|
||||||
...externalRoutes,
|
...externalRoutes,
|
||||||
@@ -32,5 +32,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
||||||
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
|
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
|
||||||
|
|
||||||
|
/** 有权限校验的路由列表,包含动态路由和静态路由 */
|
||||||
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
|
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
|
||||||
export { accessRoutes, coreRouteNames, routes };
|
export { accessRoutes, coreRouteNames, routes };
|
||||||
|
@@ -1,18 +1,16 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
import { BasicLayout } from '#/layouts';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
component: BasicLayout,
|
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'lucide:layout-dashboard',
|
icon: 'lucide:layout-dashboard',
|
||||||
order: -1,
|
order: -1,
|
||||||
title: $t('page.dashboard.title'),
|
title: $t('page.dashboard.title'),
|
||||||
},
|
},
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
path: '/',
|
path: '/dashboard',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'Analytics',
|
name: 'Analytics',
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
import { BasicLayout } from '#/layouts';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
component: BasicLayout,
|
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'ic:baseline-view-in-ar',
|
icon: 'ic:baseline-view-in-ar',
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
@@ -31,6 +29,14 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '/demos/table',
|
path: '/demos/table',
|
||||||
component: () => import('#/views/demos/table/index.vue'),
|
component: () => import('#/views/demos/table/index.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
title: $t('demos.form'),
|
||||||
|
},
|
||||||
|
name: 'Form',
|
||||||
|
path: '/demos/form',
|
||||||
|
component: () => import('#/views/demos/form/basic.vue'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@@ -9,30 +9,20 @@ import {
|
|||||||
} from '@vben/constants';
|
} from '@vben/constants';
|
||||||
import { SvgAntdvLogoIcon } from '@vben/icons';
|
import { SvgAntdvLogoIcon } from '@vben/icons';
|
||||||
|
|
||||||
import { BasicLayout, IFrameView } from '#/layouts';
|
import { IFrameView } from '#/layouts';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
component: BasicLayout,
|
|
||||||
meta: {
|
meta: {
|
||||||
badgeType: 'dot',
|
badgeType: 'dot',
|
||||||
icon: VBEN_LOGO_URL,
|
icon: VBEN_LOGO_URL,
|
||||||
order: 9999,
|
order: 9998,
|
||||||
title: $t('demos.vben.title'),
|
title: $t('demos.vben.title'),
|
||||||
},
|
},
|
||||||
name: 'VbenProject',
|
name: 'VbenProject',
|
||||||
path: '/vben-admin',
|
path: '/vben-admin',
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
name: 'VbenAbout',
|
|
||||||
path: '/vben-admin/about',
|
|
||||||
component: () => import('#/views/_core/about/index.vue'),
|
|
||||||
meta: {
|
|
||||||
icon: 'lucide:copyright',
|
|
||||||
title: $t('demos.vben.about'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'VbenDocument',
|
name: 'VbenDocument',
|
||||||
path: '/vben-admin/document',
|
path: '/vben-admin/document',
|
||||||
@@ -77,6 +67,16 @@ const routes: RouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'VbenAbout',
|
||||||
|
path: '/vben-admin/about',
|
||||||
|
component: () => import('#/views/_core/about/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:copyright',
|
||||||
|
title: $t('demos.vben.about'),
|
||||||
|
order: 9999,
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
||||||
|
@@ -10,6 +10,7 @@ import { $t } from '@vben/locales';
|
|||||||
defineOptions({ name: 'CodeLogin' });
|
defineOptions({ name: 'CodeLogin' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const CODE_LENGTH = 6;
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
return [
|
return [
|
||||||
@@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
{
|
{
|
||||||
component: 'VbenPinInput',
|
component: 'VbenPinInput',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
codeLength: CODE_LENGTH,
|
||||||
createText: (countdown: number) => {
|
createText: (countdown: number) => {
|
||||||
const text =
|
const text =
|
||||||
countdown > 0
|
countdown > 0
|
||||||
@@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
},
|
},
|
||||||
fieldName: 'code',
|
fieldName: 'code',
|
||||||
label: $t('authentication.code'),
|
label: $t('authentication.code'),
|
||||||
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
|
rules: z.string().length(CODE_LENGTH, {
|
||||||
|
message: $t('authentication.codeTip', [CODE_LENGTH]),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
@@ -15,10 +15,10 @@ import {
|
|||||||
} from '@vben/icons';
|
} from '@vben/icons';
|
||||||
|
|
||||||
import AnalyticsTrends from './analytics-trends.vue';
|
import AnalyticsTrends from './analytics-trends.vue';
|
||||||
import AnalyticsVisits from './analytics-visits.vue';
|
|
||||||
import AnalyticsVisitsData from './analytics-visits-data.vue';
|
import AnalyticsVisitsData from './analytics-visits-data.vue';
|
||||||
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
|
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
|
||||||
import AnalyticsVisitsSource from './analytics-visits-source.vue';
|
import AnalyticsVisitsSource from './analytics-visits-source.vue';
|
||||||
|
import AnalyticsVisits from './analytics-visits.vue';
|
||||||
|
|
||||||
const overviewItems: AnalysisOverviewItem[] = [
|
const overviewItems: AnalysisOverviewItem[] = [
|
||||||
{
|
{
|
||||||
|
@@ -7,6 +7,7 @@ import type {
|
|||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AnalysisChartCard,
|
AnalysisChartCard,
|
||||||
@@ -18,11 +19,15 @@ import {
|
|||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { useUserStore } from '@vben/stores';
|
import { useUserStore } from '@vben/stores';
|
||||||
|
import { openWindow } from '@vben/utils';
|
||||||
|
|
||||||
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
|
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
|
||||||
|
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
|
||||||
|
// 例如:url: /dashboard/workspace
|
||||||
const projectItems: WorkbenchProjectItem[] = [
|
const projectItems: WorkbenchProjectItem[] = [
|
||||||
{
|
{
|
||||||
color: '',
|
color: '',
|
||||||
@@ -31,6 +36,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '开源组',
|
group: '开源组',
|
||||||
icon: 'carbon:logo-github',
|
icon: 'carbon:logo-github',
|
||||||
title: 'Github',
|
title: 'Github',
|
||||||
|
url: 'https://github.com',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#3fb27f',
|
color: '#3fb27f',
|
||||||
@@ -39,6 +45,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '算法组',
|
group: '算法组',
|
||||||
icon: 'ion:logo-vue',
|
icon: 'ion:logo-vue',
|
||||||
title: 'Vue',
|
title: 'Vue',
|
||||||
|
url: 'https://vuejs.org',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#e18525',
|
color: '#e18525',
|
||||||
@@ -47,6 +54,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '上班摸鱼',
|
group: '上班摸鱼',
|
||||||
icon: 'ion:logo-html5',
|
icon: 'ion:logo-html5',
|
||||||
title: 'Html5',
|
title: 'Html5',
|
||||||
|
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#bf0c2c',
|
color: '#bf0c2c',
|
||||||
@@ -55,6 +63,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: 'UI',
|
group: 'UI',
|
||||||
icon: 'ion:logo-angular',
|
icon: 'ion:logo-angular',
|
||||||
title: 'Angular',
|
title: 'Angular',
|
||||||
|
url: 'https://angular.io',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#00d8ff',
|
color: '#00d8ff',
|
||||||
@@ -63,6 +72,7 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '技术牛',
|
group: '技术牛',
|
||||||
icon: 'bx:bxl-react',
|
icon: 'bx:bxl-react',
|
||||||
title: 'React',
|
title: 'React',
|
||||||
|
url: 'https://reactjs.org',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#EBD94E',
|
color: '#EBD94E',
|
||||||
@@ -71,39 +81,47 @@ const projectItems: WorkbenchProjectItem[] = [
|
|||||||
group: '架构组',
|
group: '架构组',
|
||||||
icon: 'ion:logo-javascript',
|
icon: 'ion:logo-javascript',
|
||||||
title: 'Js',
|
title: 'Js',
|
||||||
|
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 同样,这里的 url 也可以使用以 http 开头的外部链接
|
||||||
const quickNavItems: WorkbenchQuickNavItem[] = [
|
const quickNavItems: WorkbenchQuickNavItem[] = [
|
||||||
{
|
{
|
||||||
color: '#1fdaca',
|
color: '#1fdaca',
|
||||||
icon: 'ion:home-outline',
|
icon: 'ion:home-outline',
|
||||||
title: '首页',
|
title: '首页',
|
||||||
|
url: '/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#bf0c2c',
|
color: '#bf0c2c',
|
||||||
icon: 'ion:grid-outline',
|
icon: 'ion:grid-outline',
|
||||||
title: '仪表盘',
|
title: '仪表盘',
|
||||||
|
url: '/dashboard',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#e18525',
|
color: '#e18525',
|
||||||
icon: 'ion:layers-outline',
|
icon: 'ion:layers-outline',
|
||||||
title: '组件',
|
title: '组件',
|
||||||
|
url: '/demos/features/icons',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#3fb27f',
|
color: '#3fb27f',
|
||||||
icon: 'ion:settings-outline',
|
icon: 'ion:settings-outline',
|
||||||
title: '系统管理',
|
title: '系统管理',
|
||||||
|
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#4daf1bc9',
|
color: '#4daf1bc9',
|
||||||
icon: 'ion:key-outline',
|
icon: 'ion:key-outline',
|
||||||
title: '权限管理',
|
title: '权限管理',
|
||||||
|
url: '/demos/access/page-control',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: '#00d8ff',
|
color: '#00d8ff',
|
||||||
icon: 'ion:bar-chart-outline',
|
icon: 'ion:bar-chart-outline',
|
||||||
title: '图表',
|
title: '图表',
|
||||||
|
url: '/analytics',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -195,6 +213,24 @@ const trendItems: WorkbenchTrendItem[] = [
|
|||||||
title: 'Vben',
|
title: 'Vben',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
|
||||||
|
// This is a sample method, adjust according to the actual project requirements
|
||||||
|
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
|
||||||
|
if (nav.url?.startsWith('http')) {
|
||||||
|
openWindow(nav.url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nav.url?.startsWith('/')) {
|
||||||
|
router.push(nav.url).catch((error) => {
|
||||||
|
console.error('Navigation failed:', error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -210,7 +246,7 @@ const trendItems: WorkbenchTrendItem[] = [
|
|||||||
|
|
||||||
<div class="mt-5 flex flex-col lg:flex-row">
|
<div class="mt-5 flex flex-col lg:flex-row">
|
||||||
<div class="mr-4 w-full lg:w-3/5">
|
<div class="mr-4 w-full lg:w-3/5">
|
||||||
<WorkbenchProject :items="projectItems" title="项目" />
|
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
|
||||||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full lg:w-2/5">
|
<div class="w-full lg:w-2/5">
|
||||||
@@ -218,6 +254,7 @@ const trendItems: WorkbenchTrendItem[] = [
|
|||||||
:items="quickNavItems"
|
:items="quickNavItems"
|
||||||
class="mt-5 lg:mt-0"
|
class="mt-5 lg:mt-0"
|
||||||
title="快捷导航"
|
title="快捷导航"
|
||||||
|
@click="navTo"
|
||||||
/>
|
/>
|
||||||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
||||||
<AnalysisChartCard class="mt-5" title="访问来源">
|
<AnalysisChartCard class="mt-5" title="访问来源">
|
||||||
|
159
apps/web-naive/src/views/demos/form/basic.vue
Normal file
159
apps/web-naive/src/views/demos/form/basic.vue
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { NButton, NCard, useMessage } from 'naive-ui';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { getAllMenusApi } from '#/api';
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
// 所有表单项
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layout: 'horizontal',
|
||||||
|
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
||||||
|
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||||
|
handleSubmit: (values) => {
|
||||||
|
message.success(`表单数据:${JSON.stringify(values)}`);
|
||||||
|
},
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||||
|
component: 'ApiSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口转options格式
|
||||||
|
afterFetch: (data: { name: string; path: string }[]) => {
|
||||||
|
return data.map((item: any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.path,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
// 菜单接口
|
||||||
|
api: getAllMenusApi,
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'api',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'ApiSelect',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口
|
||||||
|
api: getAllMenusApi,
|
||||||
|
childrenField: 'children',
|
||||||
|
// 菜单接口转options格式
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'path',
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'apiTree',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'ApiTreeSelect',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'string',
|
||||||
|
label: 'String',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'number',
|
||||||
|
label: 'Number',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
fieldName: 'radio',
|
||||||
|
label: 'Radio',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ value: 'A', label: 'A' },
|
||||||
|
{ value: 'B', label: 'B' },
|
||||||
|
{ value: 'C', label: 'C' },
|
||||||
|
{ value: 'D', label: 'D' },
|
||||||
|
{ value: 'E', label: 'E' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
rules: 'selectRequired',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
fieldName: 'radioButton',
|
||||||
|
label: 'RadioButton',
|
||||||
|
componentProps: {
|
||||||
|
isButton: true,
|
||||||
|
class: 'flex flex-wrap', // 如果选项过多,可以添加class来自动折叠
|
||||||
|
options: [
|
||||||
|
{ value: 'A', label: '选项A' },
|
||||||
|
{ value: 'B', label: '选项B' },
|
||||||
|
{ value: 'C', label: '选项C' },
|
||||||
|
{ value: 'D', label: '选项D' },
|
||||||
|
{ value: 'E', label: '选项E' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
rules: 'selectRequired',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'CheckboxGroup',
|
||||||
|
fieldName: 'checkbox',
|
||||||
|
label: 'Checkbox',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ value: 'A', label: '选项A' },
|
||||||
|
{ value: 'B', label: '选项B' },
|
||||||
|
{ value: 'C', label: '选项C' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
rules: 'selectRequired',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'DatePicker',
|
||||||
|
fieldName: 'date',
|
||||||
|
label: 'Date',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'textArea',
|
||||||
|
label: 'TextArea',
|
||||||
|
componentProps: {
|
||||||
|
type: 'textarea',
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
function setFormValues() {
|
||||||
|
formApi.setValues({
|
||||||
|
string: 'string',
|
||||||
|
number: 123,
|
||||||
|
radio: 'B',
|
||||||
|
radioButton: 'C',
|
||||||
|
checkbox: ['A', 'C'],
|
||||||
|
date: Date.now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page
|
||||||
|
description="表单适配器重新包装了CheckboxGroup和RadioGroup,可以通过options属性传递选项数据(选项数据将作为子组件的属性)"
|
||||||
|
title="表单演示"
|
||||||
|
>
|
||||||
|
<NCard title="基础表单">
|
||||||
|
<template #header-extra>
|
||||||
|
<NButton type="primary" @click="setFormValues">设置表单值</NButton>
|
||||||
|
</template>
|
||||||
|
<Form />
|
||||||
|
</NCard>
|
||||||
|
</Page>
|
||||||
|
</template>
|
@@ -1,7 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { NotificationType } from 'naive-ui';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
import { type NotificationType } from 'naive-ui';
|
|
||||||
import { NButton, NCard, NSpace, useMessage, useNotification } from 'naive-ui';
|
import { NButton, NCard, NSpace, useMessage, useNotification } from 'naive-ui';
|
||||||
|
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
54
cspell.json
54
cspell.json
@@ -4,53 +4,55 @@
|
|||||||
"language": "en,en-US",
|
"language": "en,en-US",
|
||||||
"allowCompoundWords": true,
|
"allowCompoundWords": true,
|
||||||
"words": [
|
"words": [
|
||||||
"clsx",
|
|
||||||
"esno",
|
|
||||||
"demi",
|
|
||||||
"unref",
|
|
||||||
"taze",
|
|
||||||
"acmr",
|
"acmr",
|
||||||
"antd",
|
"antd",
|
||||||
"lucide",
|
"antdv",
|
||||||
|
"astro",
|
||||||
"brotli",
|
"brotli",
|
||||||
|
"clsx",
|
||||||
"defu",
|
"defu",
|
||||||
|
"demi",
|
||||||
|
"echarts",
|
||||||
|
"ependencies",
|
||||||
|
"esno",
|
||||||
|
"etag",
|
||||||
"execa",
|
"execa",
|
||||||
"iconify",
|
"iconify",
|
||||||
|
"iconoir",
|
||||||
"intlify",
|
"intlify",
|
||||||
|
"lockb",
|
||||||
|
"lucide",
|
||||||
|
"minh",
|
||||||
|
"minw",
|
||||||
"mkdist",
|
"mkdist",
|
||||||
"mockjs",
|
"mockjs",
|
||||||
"vitejs",
|
"naiveui",
|
||||||
|
"nocheck",
|
||||||
"noopener",
|
"noopener",
|
||||||
"noreferrer",
|
"noreferrer",
|
||||||
"nprogress",
|
"nprogress",
|
||||||
|
"nuxt",
|
||||||
"pinia",
|
"pinia",
|
||||||
|
"prefixs",
|
||||||
"publint",
|
"publint",
|
||||||
"qrcode",
|
"qrcode",
|
||||||
"shadcn",
|
"shadcn",
|
||||||
"sonner",
|
"sonner",
|
||||||
|
"sortablejs",
|
||||||
|
"styl",
|
||||||
|
"taze",
|
||||||
|
"ui-kit",
|
||||||
|
"uicons",
|
||||||
"unplugin",
|
"unplugin",
|
||||||
|
"unref",
|
||||||
"vben",
|
"vben",
|
||||||
"vbenjs",
|
"vbenjs",
|
||||||
"vueuse",
|
|
||||||
"yxxx",
|
|
||||||
"nuxt",
|
|
||||||
"lockb",
|
|
||||||
"astro",
|
|
||||||
"ui-kit",
|
|
||||||
"styl",
|
|
||||||
"vnode",
|
|
||||||
"nocheck",
|
|
||||||
"prefixs",
|
|
||||||
"vitepress",
|
|
||||||
"antdv",
|
|
||||||
"ependencies",
|
|
||||||
"vite",
|
"vite",
|
||||||
"echarts",
|
"vitejs",
|
||||||
"sortablejs",
|
"vitepress",
|
||||||
"etag",
|
"vnode",
|
||||||
"naiveui",
|
"vueuse",
|
||||||
"uicons",
|
"yxxx"
|
||||||
"iconoir"
|
|
||||||
],
|
],
|
||||||
"ignorePaths": [
|
"ignorePaths": [
|
||||||
"**/node_modules/**",
|
"**/node_modules/**",
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { SetupContext } from 'vue';
|
||||||
|
|
||||||
import { computed, ref, useSlots } from 'vue';
|
import { computed, ref, useSlots } from 'vue';
|
||||||
|
|
||||||
import { VbenTooltip } from '@vben-core/shadcn-ui';
|
import { VbenTooltip } from '@vben-core/shadcn-ui';
|
||||||
@@ -25,7 +27,7 @@ const props = withDefaults(
|
|||||||
|
|
||||||
const open = ref(false);
|
const open = ref(false);
|
||||||
|
|
||||||
const slots = useSlots();
|
const slots: SetupContext['slots'] = useSlots();
|
||||||
|
|
||||||
const tabs = computed(() => {
|
const tabs = computed(() => {
|
||||||
return props.files.map((file) => {
|
return props.files.map((file) => {
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
import { type DefaultTheme, defineConfig } from 'vitepress';
|
import type { DefaultTheme } from 'vitepress';
|
||||||
|
|
||||||
|
import { defineConfig } from 'vitepress';
|
||||||
|
|
||||||
import { version } from '../../../package.json';
|
import { version } from '../../../package.json';
|
||||||
|
|
||||||
@@ -221,9 +223,9 @@ function nav(): DefaultTheme.NavItem[] {
|
|||||||
link: '/commercial/community',
|
link: '/commercial/community',
|
||||||
text: '👨👦👦 Community',
|
text: '👨👦👦 Community',
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
link: '/friend-links/',
|
// link: '/friend-links/',
|
||||||
text: '🤝 Friend Links',
|
// text: '🤝 Friend Links',
|
||||||
},
|
// },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,10 @@ import type { HeadConfig } from 'vitepress';
|
|||||||
|
|
||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
import { viteArchiverPlugin } from '@vben/vite-config';
|
import {
|
||||||
|
viteArchiverPlugin,
|
||||||
|
viteVxeTableImportsPlugin,
|
||||||
|
} from '@vben/vite-config';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GitChangelog,
|
GitChangelog,
|
||||||
@@ -59,6 +62,11 @@ export const shared = defineConfig({
|
|||||||
postcssIsolateStyles({ includeFiles: [/vp-doc\.css/] }),
|
postcssIsolateStyles({ includeFiles: [/vp-doc\.css/] }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
api: 'modern',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
json: {
|
json: {
|
||||||
stringify: true,
|
stringify: true,
|
||||||
@@ -85,6 +93,7 @@ export const shared = defineConfig({
|
|||||||
GitChangelogMarkdownSection(),
|
GitChangelogMarkdownSection(),
|
||||||
viteArchiverPlugin({ outputDir: '.vitepress' }),
|
viteArchiverPlugin({ outputDir: '.vitepress' }),
|
||||||
groupIconVitePlugin(),
|
groupIconVitePlugin(),
|
||||||
|
await viteVxeTableImportsPlugin(),
|
||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
fs: {
|
fs: {
|
||||||
@@ -93,6 +102,7 @@ export const shared = defineConfig({
|
|||||||
host: true,
|
host: true,
|
||||||
port: 6173,
|
port: 6173,
|
||||||
},
|
},
|
||||||
|
|
||||||
ssr: {
|
ssr: {
|
||||||
external: ['@vue/repl'],
|
external: ['@vue/repl'],
|
||||||
},
|
},
|
||||||
@@ -156,6 +166,7 @@ function pwa(): PwaOptions {
|
|||||||
registerType: 'autoUpdate',
|
registerType: 'autoUpdate',
|
||||||
workbox: {
|
workbox: {
|
||||||
globPatterns: ['**/*.{css,js,html,svg,png,ico,txt,woff2}'],
|
globPatterns: ['**/*.{css,js,html,svg,png,ico,txt,woff2}'],
|
||||||
|
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
import { type DefaultTheme, defineConfig } from 'vitepress';
|
import type { DefaultTheme } from 'vitepress';
|
||||||
|
|
||||||
|
import { defineConfig } from 'vitepress';
|
||||||
|
|
||||||
import { version } from '../../../package.json';
|
import { version } from '../../../package.json';
|
||||||
|
|
||||||
@@ -124,7 +126,7 @@ function sidebarCommercial(): DefaultTheme.SidebarItem[] {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
link: 'community',
|
link: 'community',
|
||||||
text: '社区',
|
text: '交流群',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: 'technical-support',
|
link: 'technical-support',
|
||||||
@@ -148,10 +150,24 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
collapsed: false,
|
||||||
|
text: '布局组件',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
link: 'layout-ui/page',
|
||||||
|
text: 'Page 页面',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
text: '通用组件',
|
text: '通用组件',
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
link: 'common-ui/vben-api-component',
|
||||||
|
text: 'ApiComponent Api组件包装器',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
link: 'common-ui/vben-modal',
|
link: 'common-ui/vben-modal',
|
||||||
text: 'Modal 模态框',
|
text: 'Modal 模态框',
|
||||||
@@ -172,6 +188,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
|
|||||||
link: 'common-ui/vben-count-to-animator',
|
link: 'common-ui/vben-count-to-animator',
|
||||||
text: 'CountToAnimator 数字动画',
|
text: 'CountToAnimator 数字动画',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
link: 'common-ui/vben-ellipsis-text',
|
||||||
|
text: 'EllipsisText 省略文本',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -266,7 +286,7 @@ function nav(): DefaultTheme.NavItem[] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: '/commercial/community',
|
link: '/commercial/community',
|
||||||
text: '👨👦👦 社区',
|
text: '👨👦👦 交流群',
|
||||||
// items: [
|
// items: [
|
||||||
// {
|
// {
|
||||||
// link: 'https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=22ySzj7pKiw&businessType=9&from=246610&biz=ka&mainSourceId=share&subSourceId=others&jumpsource=shorturl#/pc',
|
// link: 'https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=22ySzj7pKiw&businessType=9&from=246610&biz=ka&mainSourceId=share&subSourceId=others&jumpsource=shorturl#/pc',
|
||||||
@@ -282,10 +302,10 @@ function nav(): DefaultTheme.NavItem[] {
|
|||||||
// },
|
// },
|
||||||
// ],
|
// ],
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
link: '/friend-links/',
|
// link: '/friend-links/',
|
||||||
text: '🤝 友情链接',
|
// text: '🤝 友情链接',
|
||||||
},
|
// },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ import {
|
|||||||
|
|
||||||
// import { useAntdDesignTokens } from '@vben/hooks';
|
// import { useAntdDesignTokens } from '@vben/hooks';
|
||||||
// import { initPreferences } from '@vben/preferences';
|
// import { initPreferences } from '@vben/preferences';
|
||||||
|
|
||||||
import { ConfigProvider, theme } from 'ant-design-vue';
|
import { ConfigProvider, theme } from 'ant-design-vue';
|
||||||
import mediumZoom from 'medium-zoom';
|
import mediumZoom from 'medium-zoom';
|
||||||
import { useRoute } from 'vitepress';
|
import { useRoute } from 'vitepress';
|
||||||
|
@@ -15,11 +15,12 @@ import 'virtual:group-icons.css';
|
|||||||
import '@nolebase/vitepress-plugin-git-changelog/client/style.css';
|
import '@nolebase/vitepress-plugin-git-changelog/client/style.css';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
enhanceApp(ctx: EnhanceAppContext) {
|
async enhanceApp(ctx: EnhanceAppContext) {
|
||||||
const { app } = ctx;
|
const { app } = ctx;
|
||||||
app.component('VbenContributors', VbenContributors);
|
app.component('VbenContributors', VbenContributors);
|
||||||
app.component('DemoPreview', DemoPreview);
|
app.component('DemoPreview', DemoPreview);
|
||||||
app.use(NolebaseGitChangelogPlugin);
|
app.use(NolebaseGitChangelogPlugin);
|
||||||
|
|
||||||
// 百度统计
|
// 百度统计
|
||||||
initHmPlugin();
|
initHmPlugin();
|
||||||
},
|
},
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vben/docs",
|
"name": "@vben/docs",
|
||||||
"version": "5.4.4",
|
"version": "5.5.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vitepress build",
|
"build": "vitepress build",
|
||||||
@@ -8,12 +8,16 @@
|
|||||||
"docs:preview": "vitepress preview"
|
"docs:preview": "vitepress preview"
|
||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
"#/*": "./src/_env/*"
|
"#/*": {
|
||||||
|
"node": "./src/_env/node/*",
|
||||||
|
"default": "./src/_env/*"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vben-core/shadcn-ui": "workspace:*",
|
"@vben-core/shadcn-ui": "workspace:*",
|
||||||
"@vben/common-ui": "workspace:*",
|
"@vben/common-ui": "workspace:*",
|
||||||
"@vben/locales": "workspace:*",
|
"@vben/locales": "workspace:*",
|
||||||
|
"@vben/plugins": "workspace:*",
|
||||||
"@vben/styles": "workspace:*",
|
"@vben/styles": "workspace:*",
|
||||||
"ant-design-vue": "catalog:",
|
"ant-design-vue": "catalog:",
|
||||||
"lucide-vue-next": "catalog:",
|
"lucide-vue-next": "catalog:",
|
||||||
|
@@ -3,9 +3,10 @@
|
|||||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { Component, SetupContext } from 'vue';
|
||||||
|
|
||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { globalShareState } from '@vben/common-ui';
|
import { globalShareState } from '@vben/common-ui';
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user