mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-26 00:26:20 +08:00
Compare commits
369 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
488ccb5976 | ||
![]() |
bbc426caa0 | ||
![]() |
44440d0951 | ||
![]() |
5999a862b6 | ||
![]() |
d31535cd98 | ||
![]() |
35eef33779 | ||
![]() |
422936a981 | ||
![]() |
a64a06bf59 | ||
![]() |
43d95cc587 | ||
![]() |
faf18d0143 | ||
![]() |
4b94d62145 | ||
![]() |
cf130b293d | ||
![]() |
566f357dfa | ||
![]() |
f78cc319ab | ||
![]() |
06ba7cb224 | ||
![]() |
353e8be289 | ||
![]() |
c2d59dea69 | ||
![]() |
e600d78cb9 | ||
![]() |
d23db32f04 | ||
![]() |
1180291c1a | ||
![]() |
0cf4509c70 | ||
![]() |
ad3963ff70 | ||
![]() |
fe7df5ad3b | ||
![]() |
d7d7466524 | ||
![]() |
6688a6b3c2 | ||
![]() |
39e41d05be | ||
![]() |
862bbd8344 | ||
![]() |
23768ea620 | ||
![]() |
f60796f961 | ||
![]() |
7f4c733fa3 | ||
![]() |
1b172b0329 | ||
![]() |
88d2b3e569 | ||
![]() |
625862e082 | ||
![]() |
bfaa2780ab | ||
![]() |
d262b7b6c0 | ||
![]() |
93b48ef244 | ||
![]() |
860fc15ce6 | ||
![]() |
646598afba | ||
![]() |
234544c40d | ||
![]() |
307a71fb35 | ||
![]() |
477a05c26c | ||
![]() |
0df8c5c02c | ||
![]() |
d1ca09c7bb | ||
![]() |
ff3c5f8581 | ||
![]() |
240f0b5f8d | ||
![]() |
6f3d50984f | ||
![]() |
c491b9e021 | ||
![]() |
6cd9937c03 | ||
![]() |
f89f4f32c7 | ||
![]() |
c432e0ac33 | ||
![]() |
719c9a8f2d | ||
![]() |
a0fbe0b21a | ||
![]() |
f7fa69d33b | ||
![]() |
7c45aeb868 | ||
![]() |
850a6af1e0 | ||
![]() |
d5a210f53f | ||
![]() |
6c4a742627 | ||
![]() |
45987fc1e3 | ||
![]() |
ea962e75d0 | ||
![]() |
24d14c2841 | ||
![]() |
8b961a9d7f | ||
![]() |
9856bc88d2 | ||
![]() |
68465b5fbf | ||
![]() |
0ea0f204cb | ||
![]() |
1b65254383 | ||
![]() |
0a99f27127 | ||
![]() |
304b1b2efc | ||
![]() |
f923f59070 | ||
![]() |
437cb02e11 | ||
![]() |
ba539f6793 | ||
![]() |
078f255e1a | ||
![]() |
ba4662522e | ||
![]() |
8fe87b10dc | ||
![]() |
2dbd323b2a | ||
![]() |
8ad2b8665d | ||
![]() |
518b869f9d | ||
![]() |
2d019b3c8a | ||
![]() |
ab44926ec8 | ||
![]() |
f0edad8a51 | ||
![]() |
f8ce3fdf1f | ||
![]() |
60c615ce8a | ||
![]() |
324cdd8259 | ||
![]() |
9ad4f96e38 | ||
![]() |
47d162e6e4 | ||
![]() |
d37e2f599c | ||
![]() |
402eaf4275 | ||
![]() |
0fcc42a2fb | ||
![]() |
28b54b587a | ||
![]() |
4173264805 | ||
![]() |
46540a7329 | ||
![]() |
13fd0ea16c | ||
![]() |
f7016466ee | ||
![]() |
0cd865e211 | ||
![]() |
64428b9b11 | ||
![]() |
aed31a5a4e | ||
![]() |
b3e196f001 | ||
![]() |
b2c117f544 | ||
![]() |
01391ee5a1 | ||
![]() |
3572ce1538 | ||
![]() |
d1e1256202 | ||
![]() |
b7776c5148 | ||
![]() |
2d1519eca7 | ||
![]() |
93b5618b52 | ||
![]() |
639d2e1525 | ||
![]() |
26646d42f7 | ||
![]() |
17fa8eb93b | ||
![]() |
8250894a50 | ||
![]() |
a72b8acaf9 | ||
![]() |
a46c85d77d | ||
![]() |
fdc5b02c30 | ||
![]() |
476aa068d7 | ||
![]() |
bb6057cac3 | ||
![]() |
abbbbfb955 | ||
![]() |
79c87c9f46 | ||
![]() |
f815dcf3ae | ||
![]() |
1197efea26 | ||
![]() |
2a83f1d666 | ||
![]() |
4b3d2d21ed | ||
![]() |
31f6cc6416 | ||
![]() |
5ce3a18785 | ||
![]() |
dac80703d9 | ||
![]() |
000172e482 | ||
![]() |
dbe5b33db6 | ||
![]() |
81a9accafd | ||
![]() |
bc625ee710 | ||
![]() |
d34f1fbf2f | ||
![]() |
60cffb0dec | ||
![]() |
68dbe04bef | ||
![]() |
37645f08be | ||
![]() |
ace942e2df | ||
![]() |
f9359ed0f9 | ||
![]() |
b12ff2d766 | ||
![]() |
c72747c649 | ||
![]() |
fbd23701de | ||
![]() |
a634ec3692 | ||
![]() |
df48409814 | ||
![]() |
4765158510 | ||
![]() |
161820dbc1 | ||
![]() |
56bdb8f606 | ||
![]() |
f25783933d | ||
![]() |
d574fb8b6d | ||
![]() |
0c73cf8d3f | ||
![]() |
834cb4c470 | ||
![]() |
2c22825546 | ||
![]() |
26d43ef822 | ||
![]() |
bfae9626dd | ||
![]() |
02c4014ae3 | ||
![]() |
c3d0102cda | ||
![]() |
38fe6426a2 | ||
![]() |
b8a4fba78c | ||
![]() |
d99cad35d7 | ||
![]() |
5ba3a9dec2 | ||
![]() |
10b90eae5d | ||
![]() |
29f572abd3 | ||
![]() |
d27e5eeef7 | ||
![]() |
bd6b724aaf | ||
![]() |
7bcb973d65 | ||
![]() |
9e88b8004f | ||
![]() |
76a879d4d8 | ||
![]() |
537a4b0ecb | ||
![]() |
855ac02622 | ||
![]() |
61faa1895a | ||
![]() |
8f6bf6add3 | ||
![]() |
ceb52aad7f | ||
![]() |
66c732fdee | ||
![]() |
6e67fb5fe7 | ||
![]() |
3697f6bc5a | ||
![]() |
978edb1e02 | ||
![]() |
b417ac2469 | ||
![]() |
524b9badf2 | ||
![]() |
86ed732ca8 | ||
![]() |
56e66193fc | ||
![]() |
b1636405fc | ||
![]() |
ad89ea7a75 | ||
![]() |
7b3c9d56be | ||
![]() |
1cff0b9753 | ||
![]() |
31d5f03b45 | ||
![]() |
2b65e935c1 | ||
![]() |
58f2b17bde | ||
![]() |
46a9fac38e | ||
![]() |
41612f7723 | ||
![]() |
83ecae7c4e | ||
![]() |
3332b20fd0 | ||
![]() |
2d0153a841 | ||
![]() |
95a4a85c3b | ||
![]() |
3f2dcb8281 | ||
![]() |
67f3d63066 | ||
![]() |
277e98c42c |
@@ -3,3 +3,5 @@ node_modules
|
|||||||
.gitignore
|
.gitignore
|
||||||
*.md
|
*.md
|
||||||
dist
|
dist
|
||||||
|
.turbo
|
||||||
|
dist.zip
|
||||||
|
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
|
||||||
|
6
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
name: 🐞 Bug Report
|
name: 🐞 Bug Report
|
||||||
description: Report an issue with Vben Admin to help us make it better.
|
description: Report an issue with Vben Admin to help us make it better.
|
||||||
title: "Bug: "
|
title: 'Bug: '
|
||||||
labels: ["bug: pending triage"]
|
labels: ['bug: pending triage']
|
||||||
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
@@ -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/docs.yml
vendored
2
.github/ISSUE_TEMPLATE/docs.yml
vendored
@@ -1,6 +1,6 @@
|
|||||||
name: 📚 Documentation
|
name: 📚 Documentation
|
||||||
description: Report an issue with Vben Admin Website to help us make it better.
|
description: Report an issue with Vben Admin Website to help us make it better.
|
||||||
title: "Docs: "
|
title: 'Docs: '
|
||||||
labels: [documentation]
|
labels: [documentation]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
|
6
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
6
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
name: ✨ New Feature Proposal
|
name: ✨ New Feature Proposal
|
||||||
description: Propose a new feature to be added to Vben Admin
|
description: Propose a new feature to be added to Vben Admin
|
||||||
title: "FEATURE: "
|
title: 'FEATURE: '
|
||||||
labels: ["enhancement: pending triage"]
|
labels: ['enhancement: pending triage']
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
@@ -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
|
||||||
|
8
.github/actions/setup-node/action.yml
vendored
8
.github/actions/setup-node/action.yml
vendored
@@ -1,9 +1,9 @@
|
|||||||
name: "Setup Node"
|
name: 'Setup Node'
|
||||||
|
|
||||||
description: "Setup node and pnpm"
|
description: 'Setup node and pnpm'
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: 'composite'
|
||||||
steps:
|
steps:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -12,7 +12,7 @@ runs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: .node-version
|
node-version-file: .node-version
|
||||||
cache: "pnpm"
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/"
|
directory: '/'
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
groups:
|
groups:
|
||||||
@@ -9,7 +9,7 @@ updates:
|
|||||||
update-types: [minor, patch]
|
update-types: [minor, patch]
|
||||||
|
|
||||||
- package-ecosystem: github-actions
|
- package-ecosystem: github-actions
|
||||||
directory: "/"
|
directory: '/'
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
groups:
|
groups:
|
||||||
|
64
.github/release-drafter.yml
vendored
64
.github/release-drafter.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
name-template: "v$RESOLVED_VERSION"
|
name-template: 'v$RESOLVED_VERSION'
|
||||||
tag-template: "v$RESOLVED_VERSION"
|
tag-template: 'v$RESOLVED_VERSION'
|
||||||
version-template: $MAJOR.$MINOR.$PATCH
|
version-template: $MAJOR.$MINOR.$PATCH
|
||||||
change-template: "* $TITLE (#$NUMBER) @$AUTHOR"
|
change-template: '* $TITLE (#$NUMBER) @$AUTHOR'
|
||||||
template: |
|
template: |
|
||||||
# What's Changed
|
# What's Changed
|
||||||
|
|
||||||
@@ -10,52 +10,52 @@ template: |
|
|||||||
**Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
|
**Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
|
||||||
|
|
||||||
categories:
|
categories:
|
||||||
- title: "🚀 Features"
|
- title: '🚀 Features'
|
||||||
labels:
|
labels:
|
||||||
- "feature"
|
- 'feature'
|
||||||
- title: "🐞 Bug Fixes"
|
- title: '🐞 Bug Fixes'
|
||||||
labels:
|
labels:
|
||||||
- "bug"
|
- 'bug'
|
||||||
- title: "📈 Performance"
|
- title: '📈 Performance & Enhancement'
|
||||||
labels:
|
labels:
|
||||||
- "perf"
|
- 'perf'
|
||||||
- "enhancement"
|
- 'enhancement'
|
||||||
- title: 📝 Documentation
|
- title: 📝 Documentation
|
||||||
labels:
|
labels:
|
||||||
- "documentation"
|
- 'documentation'
|
||||||
- title: 👻 Maintenance
|
- title: 👻 Maintenance
|
||||||
labels:
|
labels:
|
||||||
- "chore"
|
- 'chore'
|
||||||
- "dependencies"
|
- 'dependencies'
|
||||||
# collapse-after: 12
|
# collapse-after: 12
|
||||||
- title: 🚦 Tests
|
- title: 🚦 Tests
|
||||||
labels:
|
labels:
|
||||||
- "tests"
|
- 'tests'
|
||||||
- title: "Breaking"
|
- title: 'Breaking'
|
||||||
label: "breaking"
|
label: 'breaking'
|
||||||
|
|
||||||
version-resolver:
|
version-resolver:
|
||||||
major:
|
major:
|
||||||
labels:
|
labels:
|
||||||
- "major"
|
- 'major'
|
||||||
- "breaking"
|
- 'breaking'
|
||||||
minor:
|
minor:
|
||||||
labels:
|
labels:
|
||||||
- "minor"
|
- 'minor'
|
||||||
patch:
|
patch:
|
||||||
labels:
|
labels:
|
||||||
- "feature"
|
- 'feature'
|
||||||
- "patch"
|
- 'patch'
|
||||||
- "bug"
|
- 'bug'
|
||||||
- "maintenance"
|
- 'maintenance'
|
||||||
- "docs"
|
- 'docs'
|
||||||
- "dependencies"
|
- 'dependencies'
|
||||||
- "security"
|
- 'security'
|
||||||
|
|
||||||
exclude-labels:
|
exclude-labels:
|
||||||
- "skip-changelog"
|
- 'skip-changelog'
|
||||||
- "no-changelog"
|
- 'no-changelog'
|
||||||
- "changelog"
|
- 'changelog'
|
||||||
- "bump versions"
|
- 'bump versions'
|
||||||
- "reverted"
|
- 'reverted'
|
||||||
- "invalid"
|
- 'invalid'
|
||||||
|
11
.github/workflows/build.yml
vendored
11
.github/workflows/build.yml
vendored
@@ -7,7 +7,7 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
env:
|
env:
|
||||||
HUSKY: "0"
|
HUSKY: '0'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||||
@@ -19,8 +19,15 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
post-update:
|
post-update:
|
||||||
|
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
# if: ${{ github.actor == 'dependabot[bot]' }}
|
# if: ${{ github.actor == 'dependabot[bot]' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
# - macos-latest
|
||||||
|
- windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
6
.github/workflows/changeset-version.yml
vendored
6
.github/workflows/changeset-version.yml
vendored
@@ -18,7 +18,7 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
version:
|
version:
|
||||||
if: (github.event.pull_request.merged || github.event_name == 'workflow_dispatch') && github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
if: (github.event.pull_request.merged || github.event_name == 'workflow_dispatch') && github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
||||||
# if: github.repository == 'vbenjs/vue-vben-admin'
|
# if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
uses: changesets/action@v1
|
uses: changesets/action@v1
|
||||||
with:
|
with:
|
||||||
version: pnpm run version
|
version: pnpm run version
|
||||||
commit: "chore: bump versions"
|
commit: 'chore: bump versions'
|
||||||
title: "chore: bump versions"
|
title: 'chore: bump versions'
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -5,7 +5,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- "releases/*"
|
- 'releases/*'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -17,7 +17,7 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -79,6 +79,7 @@ jobs:
|
|||||||
|
|
||||||
check:
|
check:
|
||||||
name: Check
|
name: Check
|
||||||
|
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
strategy:
|
strategy:
|
||||||
@@ -108,8 +109,8 @@ jobs:
|
|||||||
|
|
||||||
ci-ok:
|
ci-ok:
|
||||||
name: CI OK
|
name: CI OK
|
||||||
|
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && always()
|
|
||||||
needs: [test, check, lint]
|
needs: [test, check, lint]
|
||||||
env:
|
env:
|
||||||
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
|
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
|
||||||
|
11
.github/workflows/codeql.yml
vendored
11
.github/workflows/codeql.yml
vendored
@@ -9,19 +9,20 @@
|
|||||||
# the `language` matrix defined below to confirm you have the correct set of
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
# supported CodeQL languages.
|
# supported CodeQL languages.
|
||||||
#
|
#
|
||||||
name: "CodeQL"
|
name: 'CodeQL'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ['main']
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["main"]
|
branches: ['main']
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "35 0 * * 0"
|
- cron: '35 0 * * 0'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
name: Analyze (${{ matrix.language }})
|
name: Analyze (${{ matrix.language }})
|
||||||
|
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||||
# - https://gh.io/supported-runners-and-hardware-resources
|
# - https://gh.io/supported-runners-and-hardware-resources
|
||||||
@@ -90,4 +91,4 @@ jobs:
|
|||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@v3
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: '/language:${{matrix.language}}'
|
||||||
|
27
.github/workflows/deploy.yml
vendored
27
.github/workflows/deploy.yml
vendored
@@ -8,7 +8,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
deploy-playground-ftp:
|
deploy-playground-ftp:
|
||||||
name: Deploy Push Playground Ftp
|
name: Deploy Push Playground Ftp
|
||||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
|
|
||||||
deploy-docs-ftp:
|
deploy-docs-ftp:
|
||||||
name: Deploy Push Docs Ftp
|
name: Deploy Push Docs Ftp
|
||||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
|
|
||||||
deploy-antd-ftp:
|
deploy-antd-ftp:
|
||||||
name: Deploy Push Antd Ftp
|
name: Deploy Push Antd Ftp
|
||||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -94,7 +94,7 @@ jobs:
|
|||||||
|
|
||||||
deploy-ele-ftp:
|
deploy-ele-ftp:
|
||||||
name: Deploy Push Element Ftp
|
name: Deploy Push Element Ftp
|
||||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -125,7 +125,7 @@ jobs:
|
|||||||
|
|
||||||
deploy-naive-ftp:
|
deploy-naive-ftp:
|
||||||
name: Deploy Push Naive Ftp
|
name: Deploy Push Naive Ftp
|
||||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -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 }}
|
||||||
|
1
.github/workflows/draft.yml
vendored
1
.github/workflows/draft.yml
vendored
@@ -17,6 +17,7 @@ jobs:
|
|||||||
# write permission is required for autolabeler
|
# write permission is required for autolabeler
|
||||||
# otherwise, read permission is required at least
|
# otherwise, read permission is required at least
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: release-drafter/release-drafter@v6
|
- uses: release-drafter/release-drafter@v6
|
||||||
|
22
.github/workflows/issue-close-require.yml
vendored
22
.github/workflows/issue-close-require.yml
vendored
@@ -3,23 +3,29 @@ name: Issue Close Require
|
|||||||
|
|
||||||
# 触发条件:每天零点
|
# 触发条件:每天零点
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * *"
|
- cron: '0 0 * * *'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
contents: write
|
contents: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
close-issues:
|
close-issues:
|
||||||
|
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# 步骤1:关闭未活动的 Issues
|
# 关闭未活动的 Issues
|
||||||
- name: Close Inactive Issues
|
- name: Close Inactive Issues
|
||||||
uses: actions-cool/issues-helper@v3
|
uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
actions: "close-issues" # 执行动作:关闭 Issues
|
days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
|
||||||
token: ${{ secrets.GITHUB_TOKEN }} # GitHub Token,用于认证
|
stale-issue-label: needs-reproduction # Label that flags an issue as stale.
|
||||||
labels: "needs reproduction" # 目标标签
|
only-labels: needs-reproduction # Only process these issues
|
||||||
inactive-day: 3 # 未活动天数阈值
|
days-before-issue-close: 3
|
||||||
|
ignore-updates: true
|
||||||
|
remove-stale-when-updated: false
|
||||||
|
close-issue-message: This issue was closed because it was open for 3 days without a valid reproduction.
|
||||||
|
close-issue-label: closed-by-action
|
||||||
|
13
.github/workflows/issue-labeled.yml
vendored
13
.github/workflows/issue-labeled.yml
vendored
@@ -13,33 +13,34 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
reply-labeled:
|
reply-labeled:
|
||||||
|
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: remove enhancement pending
|
- name: remove enhancement pending
|
||||||
if: github.event.label.name == 'enhancement'
|
if: github.event.label.name == 'enhancement'
|
||||||
uses: actions-cool/issues-helper@v3
|
uses: actions-cool/issues-helper@v3
|
||||||
with:
|
with:
|
||||||
actions: "remove-labels"
|
actions: 'remove-labels'
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
labels: "enhancement: pending triage"
|
labels: 'enhancement: pending triage'
|
||||||
|
|
||||||
- name: remove bug pending
|
- name: remove bug pending
|
||||||
if: github.event.label.name == 'bug'
|
if: github.event.label.name == 'bug'
|
||||||
uses: actions-cool/issues-helper@v3
|
uses: actions-cool/issues-helper@v3
|
||||||
with:
|
with:
|
||||||
actions: "remove-labels"
|
actions: 'remove-labels'
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
labels: "bug: pending triage"
|
labels: 'bug: pending triage'
|
||||||
|
|
||||||
- name: needs reproduction
|
- name: needs reproduction
|
||||||
if: github.event.label.name == 'needs reproduction'
|
if: github.event.label.name == 'needs reproduction'
|
||||||
uses: actions-cool/issues-helper@v3
|
uses: actions-cool/issues-helper@v3
|
||||||
with:
|
with:
|
||||||
actions: "create-comment, remove-labels"
|
actions: 'create-comment, remove-labels'
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
body: |
|
body: |
|
||||||
Hello @${{ github.event.issue.user.login }}. Please provide the complete reproduction steps and code. Issues labeled by `needs reproduction` will be closed if no activities in 3 days.
|
Hello @${{ github.event.issue.user.login }}. Please provide the complete reproduction steps and code. Issues labeled by `needs reproduction` will be closed if no activities in 3 days.
|
||||||
labels: "bug: pending triage"
|
labels: 'bug: pending triage'
|
||||||
|
13
.github/workflows/lock.yml
vendored
13
.github/workflows/lock.yml
vendored
@@ -2,7 +2,7 @@ name: Lock Threads
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * *"
|
- cron: '0 0 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -11,13 +11,14 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
action:
|
action:
|
||||||
|
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v5
|
- uses: dessant/lock-threads@v5
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
issue-inactive-days: "30"
|
issue-inactive-days: '14'
|
||||||
issue-lock-reason: ""
|
issue-lock-reason: ''
|
||||||
pr-inactive-days: "30"
|
pr-inactive-days: '30'
|
||||||
pr-lock-reason: ""
|
pr-lock-reason: ''
|
||||||
process-only: "issues, prs"
|
process-only: 'issues, prs'
|
||||||
|
5
.github/workflows/release-tag.yml
vendored
5
.github/workflows/release-tag.yml
vendored
@@ -3,10 +3,10 @@ name: Create Release Tag
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "v*.*.*" # Push events to matching v*, i.e. v1.0, v20.15.10
|
- 'v*.*.*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||||
|
|
||||||
env:
|
env:
|
||||||
HUSKY: "0"
|
HUSKY: '0'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
@@ -15,6 +15,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Create Release
|
name: Create Release
|
||||||
|
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
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
|
3
.github/workflows/semantic-pull-request.yml
vendored
3
.github/workflows/semantic-pull-request.yml
vendored
@@ -9,8 +9,9 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Semantic Pull Request
|
name: Semantic Pull Request
|
||||||
|
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Validate PR title
|
- name: Validate PR title
|
||||||
uses: amannn/action-semantic-pull-request@v5
|
uses: amannn/action-semantic-pull-request@v5
|
||||||
|
11
.github/workflows/stale.yml
vendored
11
.github/workflows/stale.yml
vendored
@@ -1,18 +1,19 @@
|
|||||||
name: "Close stale issues"
|
name: 'Close stale issues'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 1 * * *"
|
- cron: '0 1 * * *'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
|
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
stale-issue-message: "This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days"
|
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
|
||||||
stale-pr-message: "This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days"
|
stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
|
||||||
exempt-issue-labels: "bug,enhancement"
|
exempt-issue-labels: 'bug,enhancement'
|
||||||
days-before-stale: 60
|
days-before-stale: 60
|
||||||
days-before-close: 7
|
days-before-close: 7
|
||||||
|
@@ -3,4 +3,4 @@ ports:
|
|||||||
onOpen: open-preview
|
onOpen: open-preview
|
||||||
tasks:
|
tasks:
|
||||||
- init: corepack enable && pnpm install
|
- init: corepack enable && pnpm install
|
||||||
command: pnpm run dev
|
command: pnpm run dev:play
|
||||||
|
@@ -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'],
|
|
||||||
};
|
};
|
||||||
|
4
.vscode/extensions.json
vendored
4
.vscode/extensions.json
vendored
@@ -19,7 +19,9 @@
|
|||||||
// i18n 插件
|
// i18n 插件
|
||||||
"Lokalise.i18n-ally",
|
"Lokalise.i18n-ally",
|
||||||
// CSS 变量提示
|
// CSS 变量提示
|
||||||
"vunguyentuan.vscode-css-variables"
|
"vunguyentuan.vscode-css-variables",
|
||||||
|
// 在 package.json 中显示 PNPM catalog 的版本
|
||||||
|
"antfu.pnpm-catalog-lens"
|
||||||
],
|
],
|
||||||
"unwantedRecommendations": [
|
"unwantedRecommendations": [
|
||||||
// 和 volar 冲突
|
// 和 volar 冲突
|
||||||
|
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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
13
.vscode/settings.json
vendored
13
.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,
|
||||||
@@ -197,11 +198,14 @@
|
|||||||
"playground/src/locales/langs",
|
"playground/src/locales/langs",
|
||||||
"apps/*/src/locales/langs"
|
"apps/*/src/locales/langs"
|
||||||
],
|
],
|
||||||
"i18n-ally.pathMatcher": "{locale}.json",
|
"i18n-ally.pathMatcher": "{locale}/{namespace}.{ext}",
|
||||||
"i18n-ally.enabledParsers": ["json", "ts", "js", "yaml"],
|
"i18n-ally.enabledParsers": ["json"],
|
||||||
"i18n-ally.sourceLanguage": "en",
|
"i18n-ally.sourceLanguage": "en",
|
||||||
"i18n-ally.displayLanguage": "zh-CN",
|
"i18n-ally.displayLanguage": "zh-CN",
|
||||||
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
||||||
|
"i18n-ally.keystyle": "nested",
|
||||||
|
"i18n-ally.sortKeys": true,
|
||||||
|
"i18n-ally.namespace": true,
|
||||||
|
|
||||||
// 控制相关文件嵌套展示
|
// 控制相关文件嵌套展示
|
||||||
"explorer.fileNesting.enabled": true,
|
"explorer.fileNesting.enabled": true,
|
||||||
@@ -212,13 +216,12 @@
|
|||||||
"*.env": "$(capture).env.*",
|
"*.env": "$(capture).env.*",
|
||||||
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
|
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
|
||||||
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
|
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
|
||||||
"Dockerfile": "Dockerfile,.docker*,docker-entrypoint.sh,build-local-docker*,nginx.conf",
|
|
||||||
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json",
|
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json",
|
||||||
"tailwind.config.mjs": "postcss.*"
|
"tailwind.config.mjs": "postcss.*"
|
||||||
},
|
},
|
||||||
"commentTranslate.hover.enabled": false,
|
"commentTranslate.hover.enabled": false,
|
||||||
"i18n-ally.keystyle": "nested",
|
|
||||||
"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
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.6/source/logo-v1.webp"> </a> <br> <br>
|
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> </a> <br> <br>
|
||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
@@ -125,11 +125,15 @@ pnpm build
|
|||||||
|
|
||||||
[@Vben](https://github.com/anncwb)
|
[@Vben](https://github.com/anncwb)
|
||||||
|
|
||||||
|
## スター歴史
|
||||||
|
|
||||||
|
[](https://star-history.com/#vbenjs/vue-vben-admin&Date)
|
||||||
|
|
||||||
## 寄付
|
## 寄付
|
||||||
|
|
||||||
このプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます!
|
このプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます!
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
||||||
|
|
||||||
|
10
README.md
10
README.md
@@ -1,4 +1,4 @@
|
|||||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.6/source/logo-v1.webp"> </a> <br> <br>
|
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> </a> <br> <br>
|
||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
@@ -124,13 +124,17 @@ Support modern browsers, not IE
|
|||||||
|
|
||||||
[@Vben](https://github.com/anncwb)
|
[@Vben](https://github.com/anncwb)
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
[](https://star-history.com/#vbenjs/vue-vben-admin&Date)
|
||||||
|
|
||||||
## Donate
|
## Donate
|
||||||
|
|
||||||
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support!
|
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support!
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aee;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
||||||
|
|
||||||
## Contributor
|
## Contributor
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.6/source/logo-v1.webp"> </a> <br> <br>
|
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> </a> <br> <br>
|
||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
@@ -77,6 +77,10 @@ pnpm dev
|
|||||||
pnpm build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
|
||||||
|
|
||||||
## 如何贡献
|
## 如何贡献
|
||||||
|
|
||||||
非常欢迎你的加入 或者提交一个 Pull Request。
|
非常欢迎你的加入 或者提交一个 Pull Request。
|
||||||
@@ -120,18 +124,18 @@ pnpm build
|
|||||||
|
|
||||||
[@Vben](https://github.com/anncwb)
|
[@Vben](https://github.com/anncwb)
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
[](https://star-history.com/#vbenjs/vue-vben-admin&Date)
|
||||||
|
|
||||||
## 捐赠
|
## 捐赠
|
||||||
|
|
||||||
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
|
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
||||||
|
|
||||||
## 更新日志
|
|
||||||
|
|
||||||
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
|
|
||||||
|
|
||||||
## Contributor
|
## Contributor
|
||||||
|
|
||||||
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
|
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
|
||||||
|
@@ -21,7 +21,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
|
|
||||||
if (!findUser) {
|
if (!findUser) {
|
||||||
clearRefreshTokenCookie(event);
|
clearRefreshTokenCookie(event);
|
||||||
return forbiddenResponse(event);
|
return forbiddenResponse(event, 'Username or password is incorrect.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessToken = generateAccessToken(findUser);
|
const accessToken = generateAccessToken(findUser);
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||||
import { unAuthorizedResponse } from '~/utils/response';
|
import { unAuthorizedResponse } from '~/utils/response';
|
||||||
|
|
||||||
export default eventHandler((event) => {
|
export default eventHandler(async (event) => {
|
||||||
const userinfo = verifyAccessToken(event);
|
const userinfo = verifyAccessToken(event);
|
||||||
if (!userinfo) {
|
if (!userinfo) {
|
||||||
return unAuthorizedResponse(event);
|
return unAuthorizedResponse(event);
|
||||||
|
73
apps/backend-mock/api/table/list.ts
Normal file
73
apps/backend-mock/api/table/list.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||||
|
import { unAuthorizedResponse } from '~/utils/response';
|
||||||
|
|
||||||
|
function generateMockDataList(count: number) {
|
||||||
|
const dataList = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const dataItem = {
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
imageUrl: faker.image.avatar(),
|
||||||
|
imageUrl2: faker.image.avatar(),
|
||||||
|
open: faker.datatype.boolean(),
|
||||||
|
status: faker.helpers.arrayElement(['success', 'error', 'warning']),
|
||||||
|
productName: faker.commerce.productName(),
|
||||||
|
price: faker.commerce.price(),
|
||||||
|
currency: faker.finance.currencyCode(),
|
||||||
|
quantity: faker.number.int({ min: 1, max: 100 }),
|
||||||
|
available: faker.datatype.boolean(),
|
||||||
|
category: faker.commerce.department(),
|
||||||
|
releaseDate: faker.date.past(),
|
||||||
|
rating: faker.number.float({ min: 1, max: 5 }),
|
||||||
|
description: faker.commerce.productDescription(),
|
||||||
|
weight: faker.number.float({ min: 0.1, max: 10 }),
|
||||||
|
color: faker.color.human(),
|
||||||
|
inProduction: faker.datatype.boolean(),
|
||||||
|
tags: Array.from({ length: 3 }, () => faker.commerce.productAdjective()),
|
||||||
|
};
|
||||||
|
|
||||||
|
dataList.push(dataItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataList;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockData = generateMockDataList(100);
|
||||||
|
|
||||||
|
export default eventHandler(async (event) => {
|
||||||
|
const userinfo = verifyAccessToken(event);
|
||||||
|
if (!userinfo) {
|
||||||
|
return unAuthorizedResponse(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(600);
|
||||||
|
|
||||||
|
const { page, pageSize, sortBy, sortOrder } = getQuery(event);
|
||||||
|
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);
|
||||||
|
});
|
@@ -6,6 +6,5 @@ export default eventHandler((event) => {
|
|||||||
if (!userinfo) {
|
if (!userinfo) {
|
||||||
return unAuthorizedResponse(event);
|
return unAuthorizedResponse(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
return useResponseSuccess(userinfo);
|
return useResponseSuccess(userinfo);
|
||||||
});
|
});
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import errorHandler from './error';
|
import errorHandler from './error';
|
||||||
|
|
||||||
|
process.env.COMPATIBILITY_DATE = new Date().toISOString();
|
||||||
export default defineNitroConfig({
|
export default defineNitroConfig({
|
||||||
devErrorHandler: errorHandler,
|
devErrorHandler: errorHandler,
|
||||||
errorHandler: '~/error',
|
errorHandler: '~/error',
|
||||||
|
@@ -10,11 +10,12 @@
|
|||||||
"start": "nitro dev"
|
"start": "nitro dev"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsonwebtoken": "^9.0.2",
|
"@faker-js/faker": "catalog:",
|
||||||
"nitropack": "^2.9.7"
|
"jsonwebtoken": "catalog:",
|
||||||
|
"nitropack": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jsonwebtoken": "^9.0.6",
|
"@types/jsonwebtoken": "catalog:",
|
||||||
"h3": "^1.12.0"
|
"h3": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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,7 +53,6 @@ export const MOCK_CODES = [
|
|||||||
|
|
||||||
const dashboardMenus = [
|
const dashboardMenus = [
|
||||||
{
|
{
|
||||||
component: 'BasicLayout',
|
|
||||||
meta: {
|
meta: {
|
||||||
order: -1,
|
order: -1,
|
||||||
title: 'page.dashboard.title',
|
title: 'page.dashboard.title',
|
||||||
@@ -86,7 +88,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
|||||||
component: '/demos/access/admin-visible',
|
component: '/demos/access/admin-visible',
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'mdi:button-cursor',
|
icon: 'mdi:button-cursor',
|
||||||
title: 'page.demos.access.adminVisible',
|
title: 'demos.access.adminVisible',
|
||||||
},
|
},
|
||||||
name: 'AccessAdminVisibleDemo',
|
name: 'AccessAdminVisibleDemo',
|
||||||
path: '/demos/access/admin-visible',
|
path: '/demos/access/admin-visible',
|
||||||
@@ -95,7 +97,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
|||||||
component: '/demos/access/super-visible',
|
component: '/demos/access/super-visible',
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'mdi:button-cursor',
|
icon: 'mdi:button-cursor',
|
||||||
title: 'page.demos.access.superVisible',
|
title: 'demos.access.superVisible',
|
||||||
},
|
},
|
||||||
name: 'AccessSuperVisibleDemo',
|
name: 'AccessSuperVisibleDemo',
|
||||||
path: '/demos/access/super-visible',
|
path: '/demos/access/super-visible',
|
||||||
@@ -104,7 +106,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
|||||||
component: '/demos/access/user-visible',
|
component: '/demos/access/user-visible',
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'mdi:button-cursor',
|
icon: 'mdi:button-cursor',
|
||||||
title: 'page.demos.access.userVisible',
|
title: 'demos.access.userVisible',
|
||||||
},
|
},
|
||||||
name: 'AccessUserVisibleDemo',
|
name: 'AccessUserVisibleDemo',
|
||||||
path: '/demos/access/user-visible',
|
path: '/demos/access/user-visible',
|
||||||
@@ -113,12 +115,11 @@ 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,
|
||||||
order: 1000,
|
order: 1000,
|
||||||
title: 'page.demos.title',
|
title: 'demos.title',
|
||||||
},
|
},
|
||||||
name: 'Demos',
|
name: 'Demos',
|
||||||
path: '/demos',
|
path: '/demos',
|
||||||
@@ -129,7 +130,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
|||||||
path: '/demosaccess',
|
path: '/demosaccess',
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'mdi:cloud-key-outline',
|
icon: 'mdi:cloud-key-outline',
|
||||||
title: 'page.demos.access.backendPermissions',
|
title: 'demos.access.backendPermissions',
|
||||||
},
|
},
|
||||||
redirect: '/demos/access/page-control',
|
redirect: '/demos/access/page-control',
|
||||||
children: [
|
children: [
|
||||||
@@ -139,7 +140,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
|||||||
component: '/demos/access/index',
|
component: '/demos/access/index',
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'mdi:page-previous-outline',
|
icon: 'mdi:page-previous-outline',
|
||||||
title: 'page.demos.access.pageAccess',
|
title: 'demos.access.pageAccess',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -148,7 +149,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
|||||||
component: '/demos/access/button-control',
|
component: '/demos/access/button-control',
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'mdi:button-cursor',
|
icon: 'mdi:button-cursor',
|
||||||
title: 'page.demos.access.buttonControl',
|
title: 'demos.access.buttonControl',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -159,7 +160,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
|||||||
authority: ['no-body'],
|
authority: ['no-body'],
|
||||||
icon: 'mdi:button-cursor',
|
icon: 'mdi:button-cursor',
|
||||||
menuVisibleWithForbidden: true,
|
menuVisibleWithForbidden: true,
|
||||||
title: 'page.demos.access.menuVisible403',
|
title: 'demos.access.menuVisible403',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
roleWithMenus[role],
|
roleWithMenus[role],
|
||||||
|
@@ -9,6 +9,27 @@ export function useResponseSuccess<T = any>(data: T) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function usePageResponseSuccess<T = any>(
|
||||||
|
page: number | string,
|
||||||
|
pageSize: number | string,
|
||||||
|
list: T[],
|
||||||
|
{ message = 'ok' } = {},
|
||||||
|
) {
|
||||||
|
const pageData = pagination(
|
||||||
|
Number.parseInt(`${page}`),
|
||||||
|
Number.parseInt(`${pageSize}`),
|
||||||
|
list,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...useResponseSuccess({
|
||||||
|
items: pageData,
|
||||||
|
total: list.length,
|
||||||
|
}),
|
||||||
|
message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function useResponseError(message: string, error: any = null) {
|
export function useResponseError(message: string, error: any = null) {
|
||||||
return {
|
return {
|
||||||
code: -1,
|
code: -1,
|
||||||
@@ -18,12 +39,30 @@ export function useResponseError(message: string, error: any = null) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function forbiddenResponse(event: H3Event<EventHandlerRequest>) {
|
export function forbiddenResponse(
|
||||||
|
event: H3Event<EventHandlerRequest>,
|
||||||
|
message = 'Forbidden Exception',
|
||||||
|
) {
|
||||||
setResponseStatus(event, 403);
|
setResponseStatus(event, 403);
|
||||||
return useResponseError('ForbiddenException', 'Forbidden Exception');
|
return useResponseError(message, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {
|
export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {
|
||||||
setResponseStatus(event, 401);
|
setResponseStatus(event, 401);
|
||||||
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
return useResponseError('Unauthorized Exception', 'Unauthorized Exception');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sleep(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pagination<T = any>(
|
||||||
|
pageNo: number,
|
||||||
|
pageSize: number,
|
||||||
|
array: T[],
|
||||||
|
): T[] {
|
||||||
|
const offset = (pageNo - 1) * Number(pageSize);
|
||||||
|
return offset + Number(pageSize) >= array.length
|
||||||
|
? array.slice(offset)
|
||||||
|
: array.slice(offset, offset + Number(pageSize));
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vben/web-antd",
|
"name": "@vben/web-antd",
|
||||||
"version": "5.2.1",
|
"version": "5.5.3",
|
||||||
"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": {
|
||||||
@@ -40,11 +40,11 @@
|
|||||||
"@vben/styles": "workspace:*",
|
"@vben/styles": "workspace:*",
|
||||||
"@vben/types": "workspace:*",
|
"@vben/types": "workspace:*",
|
||||||
"@vben/utils": "workspace:*",
|
"@vben/utils": "workspace:*",
|
||||||
"@vueuse/core": "^11.0.3",
|
"@vueuse/core": "catalog:",
|
||||||
"ant-design-vue": "^4.2.3",
|
"ant-design-vue": "catalog:",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "catalog:",
|
||||||
"pinia": "2.2.2",
|
"pinia": "catalog:",
|
||||||
"vue": "^3.4.38",
|
"vue": "catalog:",
|
||||||
"vue-router": "^4.4.3"
|
"vue-router": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
169
apps/web-antd/src/adapter/component/index.ts
Normal file
169
apps/web-antd/src/adapter/component/index.ts
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
/**
|
||||||
|
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||||
|
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Component, SetupContext } from 'vue';
|
||||||
|
|
||||||
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AutoComplete,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
CheckboxGroup,
|
||||||
|
DatePicker,
|
||||||
|
Divider,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
InputPassword,
|
||||||
|
Mentions,
|
||||||
|
notification,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
RangePicker,
|
||||||
|
Rate,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Switch,
|
||||||
|
Textarea,
|
||||||
|
TimePicker,
|
||||||
|
TreeSelect,
|
||||||
|
Upload,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
|
component: T,
|
||||||
|
type: 'input' | 'select',
|
||||||
|
) => {
|
||||||
|
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||||
|
const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);
|
||||||
|
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
|
export type ComponentType =
|
||||||
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect'
|
||||||
|
| 'AutoComplete'
|
||||||
|
| 'Checkbox'
|
||||||
|
| 'CheckboxGroup'
|
||||||
|
| 'DatePicker'
|
||||||
|
| 'DefaultButton'
|
||||||
|
| 'Divider'
|
||||||
|
| 'IconPicker'
|
||||||
|
| 'Input'
|
||||||
|
| 'InputNumber'
|
||||||
|
| 'InputPassword'
|
||||||
|
| 'Mentions'
|
||||||
|
| 'PrimaryButton'
|
||||||
|
| 'Radio'
|
||||||
|
| 'RadioGroup'
|
||||||
|
| 'RangePicker'
|
||||||
|
| 'Rate'
|
||||||
|
| 'Select'
|
||||||
|
| 'Space'
|
||||||
|
| 'Switch'
|
||||||
|
| 'Textarea'
|
||||||
|
| 'TimePicker'
|
||||||
|
| 'TreeSelect'
|
||||||
|
| 'Upload'
|
||||||
|
| BaseFormComponentType;
|
||||||
|
|
||||||
|
async function initComponentAdapter() {
|
||||||
|
const components: Partial<Record<ComponentType, Component>> = {
|
||||||
|
// 如果你的组件体积比较大,可以使用异步加载
|
||||||
|
// 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,
|
||||||
|
Checkbox,
|
||||||
|
CheckboxGroup,
|
||||||
|
DatePicker,
|
||||||
|
// 自定义默认按钮
|
||||||
|
DefaultButton: (props, { attrs, slots }) => {
|
||||||
|
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||||
|
},
|
||||||
|
Divider,
|
||||||
|
IconPicker: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
IconPicker,
|
||||||
|
{ iconSlot: 'addonAfter', inputComponent: Input, ...props, ...attrs },
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Input: withDefaultPlaceholder(Input, 'input'),
|
||||||
|
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||||
|
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||||
|
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||||
|
// 自定义主要按钮
|
||||||
|
PrimaryButton: (props, { attrs, slots }) => {
|
||||||
|
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||||
|
},
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
RangePicker,
|
||||||
|
Rate,
|
||||||
|
Select: withDefaultPlaceholder(Select, 'select'),
|
||||||
|
Space,
|
||||||
|
Switch,
|
||||||
|
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||||
|
TimePicker,
|
||||||
|
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||||
|
Upload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将组件注册到全局共享状态中
|
||||||
|
globalShareState.setComponents(components);
|
||||||
|
|
||||||
|
// 定义全局共享状态中的消息提示
|
||||||
|
globalShareState.defineMessage({
|
||||||
|
// 复制成功消息提示
|
||||||
|
copyPreferencesSuccess: (title, content) => {
|
||||||
|
notification.success({
|
||||||
|
description: content,
|
||||||
|
message: title,
|
||||||
|
placement: 'bottomRight',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { initComponentAdapter };
|
47
apps/web-antd/src/adapter/form.ts
Normal file
47
apps/web-antd/src/adapter/form.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import type {
|
||||||
|
VbenFormSchema as FormSchema,
|
||||||
|
VbenFormProps,
|
||||||
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
|
import type { ComponentType } from './component';
|
||||||
|
|
||||||
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
|
setupVbenForm<ComponentType>({
|
||||||
|
config: {
|
||||||
|
// ant design vue组件库默认都是 v-model:value
|
||||||
|
baseModelPropName: 'value',
|
||||||
|
|
||||||
|
// 一些组件是 v-model:checked 或者 v-model:fileList
|
||||||
|
modelPropNameMap: {
|
||||||
|
Checkbox: 'checked',
|
||||||
|
Radio: 'checked',
|
||||||
|
Switch: 'checked',
|
||||||
|
Upload: 'fileList',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defineRules: {
|
||||||
|
// 输入项目必填国际化适配
|
||||||
|
required: (value, _params, ctx) => {
|
||||||
|
if (value === undefined || value === null || value.length === 0) {
|
||||||
|
return $t('ui.formRules.required', [ctx.label]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
// 选择项目必填国际化适配
|
||||||
|
selectRequired: (value, _params, ctx) => {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return $t('ui.formRules.selectRequired', [ctx.label]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const useVbenForm = useForm<ComponentType>;
|
||||||
|
|
||||||
|
export { useVbenForm, z };
|
||||||
|
|
||||||
|
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||||
|
export type { VbenFormProps };
|
67
apps/web-antd/src/adapter/vxe-table.ts
Normal file
67
apps/web-antd/src/adapter/vxe-table.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||||
|
|
||||||
|
import { Button, Image } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from './form';
|
||||||
|
|
||||||
|
setupVbenVxeTable({
|
||||||
|
configVxeTable: (vxeUI) => {
|
||||||
|
vxeUI.setConfig({
|
||||||
|
grid: {
|
||||||
|
align: 'center',
|
||||||
|
border: false,
|
||||||
|
columnConfig: {
|
||||||
|
resizable: true,
|
||||||
|
},
|
||||||
|
minHeight: 180,
|
||||||
|
formConfig: {
|
||||||
|
// 全局禁用vxe-table的表单配置,使用formOptions
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
autoLoad: true,
|
||||||
|
response: {
|
||||||
|
result: 'items',
|
||||||
|
total: 'total',
|
||||||
|
list: 'items',
|
||||||
|
},
|
||||||
|
showActiveMsg: true,
|
||||||
|
showResponseMsg: false,
|
||||||
|
},
|
||||||
|
round: true,
|
||||||
|
showOverflow: true,
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
||||||
|
vxeUI.renderer.add('CellImage', {
|
||||||
|
renderTableDefault(_renderOpts, params) {
|
||||||
|
const { column, row } = params;
|
||||||
|
return h(Image, { src: row[column.field] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
||||||
|
vxeUI.renderer.add('CellLink', {
|
||||||
|
renderTableDefault(renderOpts) {
|
||||||
|
const { props } = renderOpts;
|
||||||
|
return h(
|
||||||
|
Button,
|
||||||
|
{ size: 'small', type: 'link' },
|
||||||
|
{ default: () => props?.text },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
||||||
|
// vxeUI.formats.add
|
||||||
|
},
|
||||||
|
useVbenForm,
|
||||||
|
});
|
||||||
|
|
||||||
|
export { useVbenVxeGrid };
|
||||||
|
|
||||||
|
export type * from '@vben/plugins/vxe-table';
|
@@ -3,17 +3,13 @@ import { baseRequestClient, requestClient } from '#/api/request';
|
|||||||
export namespace AuthApi {
|
export namespace AuthApi {
|
||||||
/** 登录接口参数 */
|
/** 登录接口参数 */
|
||||||
export interface LoginParams {
|
export interface LoginParams {
|
||||||
password: string;
|
password?: string;
|
||||||
username: string;
|
username?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 登录接口返回值 */
|
/** 登录接口返回值 */
|
||||||
export interface LoginResult {
|
export interface LoginResult {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
desc: string;
|
|
||||||
realName: string;
|
|
||||||
userId: string;
|
|
||||||
username: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RefreshTokenResult {
|
export interface RefreshTokenResult {
|
||||||
|
@@ -1,10 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* 该文件可自行根据业务逻辑进行调整
|
* 该文件可自行根据业务逻辑进行调整
|
||||||
*/
|
*/
|
||||||
|
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';
|
||||||
@@ -18,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,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -67,18 +71,14 @@ function createRequestClient(baseURL: string) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// response数据解构
|
// 处理返回的响应数据格式
|
||||||
client.addResponseInterceptor({
|
client.addResponseInterceptor(
|
||||||
fulfilled: (response) => {
|
defaultResponseInterceptor({
|
||||||
const { data: responseData, status } = response;
|
codeField: 'code',
|
||||||
|
dataField: 'data',
|
||||||
const { code, data, message: msg } = responseData;
|
successCode: 0,
|
||||||
if (status >= 200 && status < 400 && code === 0) {
|
}),
|
||||||
return data;
|
);
|
||||||
}
|
|
||||||
throw new Error(`Error ${status}: ${msg}`);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// token过期的处理
|
// token过期的处理
|
||||||
client.addResponseInterceptor(
|
client.addResponseInterceptor(
|
||||||
@@ -93,12 +93,21 @@ function createRequestClient(baseURL: string) {
|
|||||||
|
|
||||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||||||
client.addResponseInterceptor(
|
client.addResponseInterceptor(
|
||||||
errorMessageResponseInterceptor((msg: string) => message.error(msg)),
|
errorMessageResponseInterceptor((msg: string, error) => {
|
||||||
|
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||||||
|
// 当前mock接口返回的错误字段是 error 或者 message
|
||||||
|
const responseData = error?.response?.data ?? {};
|
||||||
|
const errorMessage = responseData?.error ?? responseData?.message ?? '';
|
||||||
|
// 如果没有错误信息,则会根据状态码进行提示
|
||||||
|
message.error(errorMessage || msg);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
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,16 +1,33 @@
|
|||||||
import { createApp } from 'vue';
|
import { createApp, watchEffect } from 'vue';
|
||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
|
import { initTippy } from '@vben/common-ui';
|
||||||
|
import { preferences } from '@vben/preferences';
|
||||||
import { initStores } from '@vben/stores';
|
import { initStores } from '@vben/stores';
|
||||||
import '@vben/styles';
|
import '@vben/styles';
|
||||||
import '@vben/styles/antd';
|
import '@vben/styles/antd';
|
||||||
|
|
||||||
import { setupI18n } from '#/locales';
|
import { useTitle } from '@vueuse/core';
|
||||||
|
|
||||||
|
import { $t, setupI18n } from '#/locales';
|
||||||
|
|
||||||
|
import { initComponentAdapter } from './adapter/component';
|
||||||
import App from './app.vue';
|
import App from './app.vue';
|
||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
|
|
||||||
async function bootstrap(namespace: string) {
|
async function bootstrap(namespace: string) {
|
||||||
|
// 初始化组件适配器
|
||||||
|
await initComponentAdapter();
|
||||||
|
|
||||||
|
// // 设置弹窗的默认配置
|
||||||
|
// setDefaultModalProps({
|
||||||
|
// fullscreenButton: false,
|
||||||
|
// });
|
||||||
|
// // 设置抽屉的默认配置
|
||||||
|
// setDefaultDrawerProps({
|
||||||
|
// zIndex: 1020,
|
||||||
|
// });
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
// 国际化 i18n 配置
|
// 国际化 i18n 配置
|
||||||
@@ -22,9 +39,22 @@ async function bootstrap(namespace: string) {
|
|||||||
// 安装权限指令
|
// 安装权限指令
|
||||||
registerAccessDirective(app);
|
registerAccessDirective(app);
|
||||||
|
|
||||||
|
// 初始化 tippy
|
||||||
|
initTippy(app);
|
||||||
|
|
||||||
// 配置路由及路由守卫
|
// 配置路由及路由守卫
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
|
// 动态更新标题
|
||||||
|
watchEffect(() => {
|
||||||
|
if (preferences.app.dynamicTitle) {
|
||||||
|
const routeTitle = router.currentRoute.value.meta?.title;
|
||||||
|
const pageTitle =
|
||||||
|
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
|
||||||
|
useTitle(pageTitle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { NotificationItem } from '@vben/layouts';
|
import type { NotificationItem } from '@vben/layouts';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
||||||
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||||
|
import { useWatermark } from '@vben/hooks';
|
||||||
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
||||||
import {
|
import {
|
||||||
BasicLayout,
|
BasicLayout,
|
||||||
@@ -13,11 +14,12 @@ import {
|
|||||||
UserDropdown,
|
UserDropdown,
|
||||||
} from '@vben/layouts';
|
} from '@vben/layouts';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { storeToRefs, useAccessStore, useUserStore } from '@vben/stores';
|
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||||
import { openWindow } from '@vben/utils';
|
import { openWindow } from '@vben/utils';
|
||||||
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
|
import LoginForm from '#/views/_core/authentication/login.vue';
|
||||||
|
|
||||||
const notifications = ref<NotificationItem[]>([
|
const notifications = ref<NotificationItem[]>([
|
||||||
{
|
{
|
||||||
@@ -53,6 +55,7 @@ const notifications = ref<NotificationItem[]>([
|
|||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
|
const { destroyWatermark, updateWatermark } = useWatermark();
|
||||||
const showDot = computed(() =>
|
const showDot = computed(() =>
|
||||||
notifications.value.some((item) => !item.isRead),
|
notifications.value.some((item) => !item.isRead),
|
||||||
);
|
);
|
||||||
@@ -65,7 +68,7 @@ const menus = computed(() => [
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: BookOpenText,
|
icon: BookOpenText,
|
||||||
text: $t('widgets.document'),
|
text: $t('ui.widgets.document'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
handler: () => {
|
handler: () => {
|
||||||
@@ -83,12 +86,10 @@ const menus = computed(() => [
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: CircleHelp,
|
icon: CircleHelp,
|
||||||
text: $t('widgets.qa'),
|
text: $t('ui.widgets.qa'),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { loginLoading } = storeToRefs(authStore);
|
|
||||||
|
|
||||||
const avatar = computed(() => {
|
const avatar = computed(() => {
|
||||||
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
||||||
});
|
});
|
||||||
@@ -104,6 +105,21 @@ function handleNoticeClear() {
|
|||||||
function handleMakeAll() {
|
function handleMakeAll() {
|
||||||
notifications.value.forEach((item) => (item.isRead = true));
|
notifications.value.forEach((item) => (item.isRead = true));
|
||||||
}
|
}
|
||||||
|
watch(
|
||||||
|
() => preferences.app.watermark,
|
||||||
|
async (enable) => {
|
||||||
|
if (enable) {
|
||||||
|
await updateWatermark({
|
||||||
|
content: `${userStore.userInfo?.username}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
destroyWatermark();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -130,11 +146,9 @@ function handleMakeAll() {
|
|||||||
<AuthenticationLoginExpiredModal
|
<AuthenticationLoginExpiredModal
|
||||||
v-model:open="accessStore.loginExpired"
|
v-model:open="accessStore.loginExpired"
|
||||||
:avatar
|
:avatar
|
||||||
:loading="loginLoading"
|
>
|
||||||
password-placeholder="123456"
|
<LoginForm />
|
||||||
username-placeholder="vben"
|
</AuthenticationLoginExpiredModal>
|
||||||
@submit="authStore.authLogin"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<template #lock-screen>
|
<template #lock-screen>
|
||||||
<LockScreen :avatar @to-login="handleLogout" />
|
<LockScreen :avatar @to-login="handleLogout" />
|
||||||
|
@@ -1,10 +1,16 @@
|
|||||||
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 { $t, setupI18n as coreSetup, loadLocalesMap } from '@vben/locales';
|
import {
|
||||||
|
$t,
|
||||||
|
setupI18n as coreSetup,
|
||||||
|
loadLocalesMapFromDir,
|
||||||
|
} from '@vben/locales';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
|
|
||||||
import antdEnLocale from 'ant-design-vue/es/locale/en_US';
|
import antdEnLocale from 'ant-design-vue/es/locale/en_US';
|
||||||
@@ -13,10 +19,12 @@ import dayjs from 'dayjs';
|
|||||||
|
|
||||||
const antdLocale = ref<Locale>(antdDefaultLocale);
|
const antdLocale = ref<Locale>(antdDefaultLocale);
|
||||||
|
|
||||||
const modules = import.meta.glob('./langs/*.json');
|
const modules = import.meta.glob('./langs/**/*.json');
|
||||||
|
|
||||||
const localesMap = loadLocalesMap(modules);
|
|
||||||
|
|
||||||
|
const localesMap = loadLocalesMapFromDir(
|
||||||
|
/\.\/langs\/([^/]+)\/(.*)\.json$/,
|
||||||
|
modules,
|
||||||
|
);
|
||||||
/**
|
/**
|
||||||
* 加载应用特有的语言包
|
* 加载应用特有的语言包
|
||||||
* 这里也可以改造为从服务端获取翻译数据
|
* 这里也可以改造为从服务端获取翻译数据
|
||||||
@@ -45,14 +53,14 @@ async function loadThirdPartyMessage(lang: SupportedLanguagesType) {
|
|||||||
async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||||
let locale;
|
let locale;
|
||||||
switch (lang) {
|
switch (lang) {
|
||||||
case 'zh-CN': {
|
|
||||||
locale = await import('dayjs/locale/zh-cn');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'en-US': {
|
case 'en-US': {
|
||||||
locale = await import('dayjs/locale/en');
|
locale = await import('dayjs/locale/en');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'zh-CN': {
|
||||||
|
locale = await import('dayjs/locale/zh-cn');
|
||||||
|
break;
|
||||||
|
}
|
||||||
// 默认使用英语
|
// 默认使用英语
|
||||||
default: {
|
default: {
|
||||||
locale = await import('dayjs/locale/en');
|
locale = await import('dayjs/locale/en');
|
||||||
@@ -71,14 +79,14 @@ async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
|||||||
*/
|
*/
|
||||||
async function loadAntdLocale(lang: SupportedLanguagesType) {
|
async function loadAntdLocale(lang: SupportedLanguagesType) {
|
||||||
switch (lang) {
|
switch (lang) {
|
||||||
case 'zh-CN': {
|
|
||||||
antdLocale.value = antdDefaultLocale;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'en-US': {
|
case 'en-US': {
|
||||||
antdLocale.value = antdEnLocale;
|
antdLocale.value = antdEnLocale;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'zh-CN': {
|
||||||
|
antdLocale.value = antdDefaultLocale;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"page": {
|
|
||||||
"demos": {
|
|
||||||
"title": "Demos",
|
|
||||||
"antd": "Ant Design Vue"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
12
apps/web-antd/src/locales/langs/en-US/demos.json
Normal file
12
apps/web-antd/src/locales/langs/en-US/demos.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"title": "Demos",
|
||||||
|
"antd": "Ant Design Vue",
|
||||||
|
"vben": {
|
||||||
|
"title": "Project",
|
||||||
|
"about": "About",
|
||||||
|
"document": "Document",
|
||||||
|
"antdv": "Ant Design Vue Version",
|
||||||
|
"naive-ui": "Naive UI Version",
|
||||||
|
"element-plus": "Element Plus Version"
|
||||||
|
}
|
||||||
|
}
|
14
apps/web-antd/src/locales/langs/en-US/page.json
Normal file
14
apps/web-antd/src/locales/langs/en-US/page.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"auth": {
|
||||||
|
"login": "Login",
|
||||||
|
"register": "Register",
|
||||||
|
"codeLogin": "Code Login",
|
||||||
|
"qrcodeLogin": "Qr Code Login",
|
||||||
|
"forgetPassword": "Forget Password"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Dashboard",
|
||||||
|
"analytics": "Analytics",
|
||||||
|
"workspace": "Workspace"
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"page": {
|
|
||||||
"demos": {
|
|
||||||
"title": "演示",
|
|
||||||
"antd": "Ant Design Vue"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
12
apps/web-antd/src/locales/langs/zh-CN/demos.json
Normal file
12
apps/web-antd/src/locales/langs/zh-CN/demos.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"title": "演示",
|
||||||
|
"antd": "Ant Design Vue",
|
||||||
|
"vben": {
|
||||||
|
"title": "项目",
|
||||||
|
"about": "关于",
|
||||||
|
"document": "文档",
|
||||||
|
"antdv": "Ant Design Vue 版本",
|
||||||
|
"naive-ui": "Naive UI 版本",
|
||||||
|
"element-plus": "Element Plus 版本"
|
||||||
|
}
|
||||||
|
}
|
14
apps/web-antd/src/locales/langs/zh-CN/page.json
Normal file
14
apps/web-antd/src/locales/langs/zh-CN/page.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"auth": {
|
||||||
|
"login": "登录",
|
||||||
|
"register": "注册",
|
||||||
|
"codeLogin": "验证码登录",
|
||||||
|
"qrcodeLogin": "二维码登录",
|
||||||
|
"forgetPassword": "忘记密码"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "概览",
|
||||||
|
"analytics": "分析页",
|
||||||
|
"workspace": "工作台"
|
||||||
|
}
|
||||||
|
}
|
@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
|||||||
/**
|
/**
|
||||||
* @description 项目配置文件
|
* @description 项目配置文件
|
||||||
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
||||||
|
* !!! 更改配置后请清空缓存,否则可能不生效
|
||||||
*/
|
*/
|
||||||
export const overridesPreferences = defineOverridesPreferences({
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
// overrides
|
// overrides
|
||||||
|
@@ -5,10 +5,7 @@ import { preferences } from '@vben/preferences';
|
|||||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||||
import { startProgress, stopProgress } from '@vben/utils';
|
import { startProgress, stopProgress } from '@vben/utils';
|
||||||
|
|
||||||
import { useTitle } from '@vueuse/core';
|
import { accessRoutes, coreRouteNames } from '#/router/routes';
|
||||||
|
|
||||||
import { $t } from '#/locales';
|
|
||||||
import { coreRouteNames, dynamicRoutes } from '#/router/routes';
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
|
|
||||||
import { generateAccess } from './access';
|
import { generateAccess } from './access';
|
||||||
@@ -34,21 +31,12 @@ function setupCommonGuard(router: Router) {
|
|||||||
router.afterEach((to) => {
|
router.afterEach((to) => {
|
||||||
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
|
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
|
||||||
|
|
||||||
if (preferences.tabbar.enable) {
|
loadedPaths.add(to.path);
|
||||||
loadedPaths.add(to.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭页面加载进度条
|
// 关闭页面加载进度条
|
||||||
if (preferences.transition.progress) {
|
if (preferences.transition.progress) {
|
||||||
stopProgress();
|
stopProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 动态修改标题
|
|
||||||
if (preferences.app.dynamicTitle) {
|
|
||||||
const { title } = to.meta;
|
|
||||||
// useTitle(`${$t(title)} - ${preferences.app.name}`);
|
|
||||||
useTitle(`${$t(title)} - ${preferences.app.name}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,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;
|
||||||
@@ -84,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,
|
||||||
};
|
};
|
||||||
@@ -107,14 +100,17 @@ function setupAccessGuard(router: Router) {
|
|||||||
roles: userRoles,
|
roles: userRoles,
|
||||||
router,
|
router,
|
||||||
// 则会在菜单中显示,但是访问会被重定向到403
|
// 则会在菜单中显示,但是访问会被重定向到403
|
||||||
routes: dynamicRoutes,
|
routes: accessRoutes,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 保存菜单信息和路由信息
|
// 保存菜单信息和路由信息
|
||||||
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)),
|
||||||
|
@@ -19,7 +19,12 @@ const router = createRouter({
|
|||||||
: createWebHistory(import.meta.env.VITE_BASE),
|
: createWebHistory(import.meta.env.VITE_BASE),
|
||||||
// 应该添加到路由的初始路由列表。
|
// 应该添加到路由的初始路由列表。
|
||||||
routes,
|
routes,
|
||||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
scrollBehavior: (to, _from, savedPosition) => {
|
||||||
|
if (savedPosition) {
|
||||||
|
return savedPosition;
|
||||||
|
}
|
||||||
|
return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };
|
||||||
|
},
|
||||||
// 是否应该禁止尾部斜杠。
|
// 是否应该禁止尾部斜杠。
|
||||||
// strict: true,
|
// strict: true,
|
||||||
});
|
});
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
import { DEFAULT_HOME_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,28 +21,38 @@ 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,
|
||||||
meta: {
|
meta: {
|
||||||
|
hideInTab: true,
|
||||||
title: 'Authentication',
|
title: 'Authentication',
|
||||||
},
|
},
|
||||||
name: 'Authentication',
|
name: 'Authentication',
|
||||||
path: '/auth',
|
path: '/auth',
|
||||||
|
redirect: LOGIN_PATH,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
path: 'login',
|
path: 'login',
|
||||||
component: Login,
|
component: Login,
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.core.login'),
|
title: $t('page.auth.login'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -50,7 +60,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
path: 'code-login',
|
path: 'code-login',
|
||||||
component: () => import('#/views/_core/authentication/code-login.vue'),
|
component: () => import('#/views/_core/authentication/code-login.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.core.codeLogin'),
|
title: $t('page.auth.codeLogin'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -59,7 +69,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
component: () =>
|
component: () =>
|
||||||
import('#/views/_core/authentication/qrcode-login.vue'),
|
import('#/views/_core/authentication/qrcode-login.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.core.qrcodeLogin'),
|
title: $t('page.auth.qrcodeLogin'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -68,7 +78,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
component: () =>
|
component: () =>
|
||||||
import('#/views/_core/authentication/forget-password.vue'),
|
import('#/views/_core/authentication/forget-password.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.core.forgetPassword'),
|
title: $t('page.auth.forgetPassword'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -76,7 +86,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
path: 'register',
|
path: 'register',
|
||||||
component: () => import('#/views/_core/authentication/register.vue'),
|
component: () => import('#/views/_core/authentication/register.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.core.register'),
|
title: $t('page.auth.register'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@@ -10,15 +10,19 @@ const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
|
|||||||
|
|
||||||
// 有需要可以自行打开注释,并创建文件夹
|
// 有需要可以自行打开注释,并创建文件夹
|
||||||
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
|
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
|
||||||
|
// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
|
||||||
|
|
||||||
/** 动态路由 */
|
/** 动态路由 */
|
||||||
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
|
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[] = [];
|
||||||
const externalRoutes: RouteRecordRaw[] = [];
|
const externalRoutes: RouteRecordRaw[] = [];
|
||||||
|
|
||||||
/** 路由列表,由基本路由+静态路由组成 */
|
/** 路由列表,由基本路由、外部路由和404兜底路由组成
|
||||||
|
* 无需走权限验证(会一直显示在菜单中) */
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
...coreRoutes,
|
...coreRoutes,
|
||||||
...externalRoutes,
|
...externalRoutes,
|
||||||
@@ -28,4 +32,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
||||||
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
|
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
|
||||||
|
|
||||||
export { coreRouteNames, dynamicRoutes, routes };
|
/** 有权限校验的路由列表,包含动态路由和静态路由 */
|
||||||
|
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
|
||||||
|
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',
|
||||||
@@ -29,6 +27,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '/workspace',
|
path: '/workspace',
|
||||||
component: () => import('#/views/dashboard/workspace/index.vue'),
|
component: () => import('#/views/dashboard/workspace/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
|
icon: 'carbon:workspace',
|
||||||
title: $t('page.dashboard.workspace'),
|
title: $t('page.dashboard.workspace'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -1,23 +1,21 @@
|
|||||||
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,
|
||||||
order: 1000,
|
order: 1000,
|
||||||
title: $t('page.demos.title'),
|
title: $t('demos.title'),
|
||||||
},
|
},
|
||||||
name: 'Demos',
|
name: 'Demos',
|
||||||
path: '/demos',
|
path: '/demos',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.demos.antd'),
|
title: $t('demos.antd'),
|
||||||
},
|
},
|
||||||
name: 'AntDesignDemos',
|
name: 'AntDesignDemos',
|
||||||
path: '/demos/ant-design',
|
path: '/demos/ant-design',
|
||||||
|
@@ -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('page.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('page.vben.about'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'VbenDocument',
|
name: 'VbenDocument',
|
||||||
path: '/vben-admin/document',
|
path: '/vben-admin/document',
|
||||||
@@ -39,7 +29,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
meta: {
|
meta: {
|
||||||
icon: 'lucide:book-open-text',
|
icon: 'lucide:book-open-text',
|
||||||
link: VBEN_DOC_URL,
|
link: VBEN_DOC_URL,
|
||||||
title: $t('page.vben.document'),
|
title: $t('demos.vben.document'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -58,8 +48,9 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: IFrameView,
|
component: IFrameView,
|
||||||
meta: {
|
meta: {
|
||||||
badgeType: 'dot',
|
badgeType: 'dot',
|
||||||
|
icon: 'logos:naiveui',
|
||||||
link: VBEN_NAIVE_PREVIEW_URL,
|
link: VBEN_NAIVE_PREVIEW_URL,
|
||||||
title: $t('page.vben.naive-ui'),
|
title: $t('demos.vben.naive-ui'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -68,12 +59,23 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: IFrameView,
|
component: IFrameView,
|
||||||
meta: {
|
meta: {
|
||||||
badgeType: 'dot',
|
badgeType: 'dot',
|
||||||
|
icon: 'logos:element',
|
||||||
link: VBEN_ELE_PREVIEW_URL,
|
link: VBEN_ELE_PREVIEW_URL,
|
||||||
title: $t('page.vben.element-plus'),
|
title: $t('demos.vben.element-plus'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import type { LoginAndRegisterParams } from '@vben/common-ui';
|
import type { Recordable, UserInfo } from '@vben/types';
|
||||||
import type { UserInfo } from '@vben/types';
|
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
@@ -26,7 +25,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
* @param params 登录表单数据
|
* @param params 登录表单数据
|
||||||
*/
|
*/
|
||||||
async function authLogin(
|
async function authLogin(
|
||||||
params: LoginAndRegisterParams,
|
params: Recordable<any>,
|
||||||
onSuccess?: () => Promise<void> | void,
|
onSuccess?: () => Promise<void> | void,
|
||||||
) {
|
) {
|
||||||
// 异步处理用户登录操作并获取 accessToken
|
// 异步处理用户登录操作并获取 accessToken
|
||||||
@@ -76,11 +75,15 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function logout(redirect: boolean = true) {
|
async function logout(redirect: boolean = true) {
|
||||||
await logoutApi();
|
try {
|
||||||
|
await logoutApi();
|
||||||
|
} catch {
|
||||||
|
// 不做任何处理
|
||||||
|
}
|
||||||
resetAllStores();
|
resetAllStores();
|
||||||
accessStore.setLoginExpired(false);
|
accessStore.setLoginExpired(false);
|
||||||
|
|
||||||
// 回登陆页带上当前路由地址
|
// 回登录页带上当前路由地址
|
||||||
await router.replace({
|
await router.replace({
|
||||||
path: LOGIN_PATH,
|
path: LOGIN_PATH,
|
||||||
query: redirect
|
query: redirect
|
||||||
|
@@ -1,21 +1,60 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { LoginCodeParams } from '@vben/common-ui';
|
import type { VbenFormSchema } from '@vben/common-ui';
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationCodeLogin } from '@vben/common-ui';
|
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
|
||||||
import { LOGIN_PATH } from '@vben/constants';
|
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[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'VbenInput',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: $t('authentication.mobile'),
|
||||||
|
},
|
||||||
|
fieldName: 'phoneNumber',
|
||||||
|
label: $t('authentication.mobile'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: $t('authentication.mobileTip') })
|
||||||
|
.refine((v) => /^\d{11}$/.test(v), {
|
||||||
|
message: $t('authentication.mobileErrortip'),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'VbenPinInput',
|
||||||
|
componentProps: {
|
||||||
|
codeLength: CODE_LENGTH,
|
||||||
|
createText: (countdown: number) => {
|
||||||
|
const text =
|
||||||
|
countdown > 0
|
||||||
|
? $t('authentication.sendText', [countdown])
|
||||||
|
: $t('authentication.sendCode');
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
placeholder: $t('authentication.code'),
|
||||||
|
},
|
||||||
|
fieldName: 'code',
|
||||||
|
label: $t('authentication.code'),
|
||||||
|
rules: z.string().length(CODE_LENGTH, {
|
||||||
|
message: $t('authentication.codeTip', [CODE_LENGTH]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
/**
|
/**
|
||||||
* 异步处理登录操作
|
* 异步处理登录操作
|
||||||
* Asynchronously handle the login process
|
* Asynchronously handle the login process
|
||||||
* @param values 登录表单数据
|
* @param values 登录表单数据
|
||||||
*/
|
*/
|
||||||
async function handleLogin(values: LoginCodeParams) {
|
async function handleLogin(values: Recordable<any>) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(values);
|
console.log(values);
|
||||||
}
|
}
|
||||||
@@ -23,8 +62,8 @@ async function handleLogin(values: LoginCodeParams) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationCodeLogin
|
<AuthenticationCodeLogin
|
||||||
|
:form-schema="formSchema"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:login-path="LOGIN_PATH"
|
|
||||||
@submit="handleLogin"
|
@submit="handleLogin"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -1,14 +1,34 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import type { VbenFormSchema } from '@vben/common-ui';
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { AuthenticationForgetPassword } from '@vben/common-ui';
|
import { computed, ref } from 'vue';
|
||||||
import { LOGIN_PATH } from '@vben/constants';
|
|
||||||
|
import { AuthenticationForgetPassword, z } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
defineOptions({ name: 'ForgetPassword' });
|
defineOptions({ name: 'ForgetPassword' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
function handleSubmit(value: string) {
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'VbenInput',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: 'example@example.com',
|
||||||
|
},
|
||||||
|
fieldName: 'email',
|
||||||
|
label: $t('authentication.email'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: $t('authentication.emailTip') })
|
||||||
|
.email($t('authentication.emailValidErrorTip')),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleSubmit(value: Recordable<any>) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('reset email:', value);
|
console.log('reset email:', value);
|
||||||
}
|
}
|
||||||
@@ -16,8 +36,8 @@ function handleSubmit(value: string) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationForgetPassword
|
<AuthenticationForgetPassword
|
||||||
|
:form-schema="formSchema"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:login-path="LOGIN_PATH"
|
|
||||||
@submit="handleSubmit"
|
@submit="handleSubmit"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -1,18 +1,98 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { AuthenticationLogin } from '@vben/common-ui';
|
import type { VbenFormSchema } from '@vben/common-ui';
|
||||||
|
import type { BasicOption } from '@vben/types';
|
||||||
|
|
||||||
|
import { computed, markRaw } from 'vue';
|
||||||
|
|
||||||
|
import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
|
|
||||||
defineOptions({ name: 'Login' });
|
defineOptions({ name: 'Login' });
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const MOCK_USER_OPTIONS: BasicOption[] = [
|
||||||
|
{
|
||||||
|
label: 'Super',
|
||||||
|
value: 'vben',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Admin',
|
||||||
|
value: 'admin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'User',
|
||||||
|
value: 'jack',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'VbenSelect',
|
||||||
|
componentProps: {
|
||||||
|
options: MOCK_USER_OPTIONS,
|
||||||
|
placeholder: $t('authentication.selectAccount'),
|
||||||
|
},
|
||||||
|
fieldName: 'selectAccount',
|
||||||
|
label: $t('authentication.selectAccount'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: $t('authentication.selectAccount') })
|
||||||
|
.optional()
|
||||||
|
.default('vben'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'VbenInput',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: $t('authentication.usernameTip'),
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
trigger(values, form) {
|
||||||
|
if (values.selectAccount) {
|
||||||
|
const findUser = MOCK_USER_OPTIONS.find(
|
||||||
|
(item) => item.value === values.selectAccount,
|
||||||
|
);
|
||||||
|
if (findUser) {
|
||||||
|
form.setValues({
|
||||||
|
password: '123456',
|
||||||
|
username: findUser.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
triggerFields: ['selectAccount'],
|
||||||
|
},
|
||||||
|
fieldName: 'username',
|
||||||
|
label: $t('authentication.username'),
|
||||||
|
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: $t('authentication.password'),
|
||||||
|
},
|
||||||
|
fieldName: 'password',
|
||||||
|
label: $t('authentication.password'),
|
||||||
|
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: markRaw(SliderCaptcha),
|
||||||
|
fieldName: 'captcha',
|
||||||
|
rules: z.boolean().refine((value) => value, {
|
||||||
|
message: $t('authentication.verifyRequiredTip'),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationLogin
|
<AuthenticationLogin
|
||||||
|
:form-schema="formSchema"
|
||||||
:loading="authStore.loginLoading"
|
:loading="authStore.loginLoading"
|
||||||
password-placeholder="123456"
|
|
||||||
username-placeholder="vben"
|
|
||||||
@submit="authStore.authLogin"
|
@submit="authStore.authLogin"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -1,16 +1,87 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { LoginAndRegisterParams } from '@vben/common-ui';
|
import type { VbenFormSchema } from '@vben/common-ui';
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { computed, h, ref } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationRegister } from '@vben/common-ui';
|
import { AuthenticationRegister, z } from '@vben/common-ui';
|
||||||
import { LOGIN_PATH } from '@vben/constants';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
defineOptions({ name: 'Register' });
|
defineOptions({ name: 'Register' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
function handleSubmit(value: LoginAndRegisterParams) {
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'VbenInput',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: $t('authentication.usernameTip'),
|
||||||
|
},
|
||||||
|
fieldName: 'username',
|
||||||
|
label: $t('authentication.username'),
|
||||||
|
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
passwordStrength: true,
|
||||||
|
placeholder: $t('authentication.password'),
|
||||||
|
},
|
||||||
|
fieldName: 'password',
|
||||||
|
label: $t('authentication.password'),
|
||||||
|
renderComponentContent() {
|
||||||
|
return {
|
||||||
|
strengthText: () => $t('authentication.passwordStrength'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: $t('authentication.confirmPassword'),
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
rules(values) {
|
||||||
|
const { password } = values;
|
||||||
|
return z
|
||||||
|
.string({ required_error: $t('authentication.passwordTip') })
|
||||||
|
.min(1, { message: $t('authentication.passwordTip') })
|
||||||
|
.refine((value) => value === password, {
|
||||||
|
message: $t('authentication.confirmPasswordTip'),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
triggerFields: ['password'],
|
||||||
|
},
|
||||||
|
fieldName: 'confirmPassword',
|
||||||
|
label: $t('authentication.confirmPassword'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'VbenCheckbox',
|
||||||
|
fieldName: 'agreePolicy',
|
||||||
|
renderComponentContent: () => ({
|
||||||
|
default: () =>
|
||||||
|
h('span', [
|
||||||
|
$t('authentication.agree'),
|
||||||
|
h(
|
||||||
|
'a',
|
||||||
|
{
|
||||||
|
class: 'vben-link ml-1 ',
|
||||||
|
href: '',
|
||||||
|
},
|
||||||
|
`${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
rules: z.boolean().refine((value) => !!value, {
|
||||||
|
message: $t('authentication.agreeTip'),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleSubmit(value: Recordable<any>) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('register submit:', value);
|
console.log('register submit:', value);
|
||||||
}
|
}
|
||||||
@@ -18,8 +89,8 @@ function handleSubmit(value: LoginAndRegisterParams) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationRegister
|
<AuthenticationRegister
|
||||||
|
:form-schema="formSchema"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:login-path="LOGIN_PATH"
|
|
||||||
@submit="handleSubmit"
|
@submit="handleSubmit"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -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.2.1",
|
"version": "5.5.3",
|
||||||
"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": {
|
||||||
@@ -40,14 +40,14 @@
|
|||||||
"@vben/styles": "workspace:*",
|
"@vben/styles": "workspace:*",
|
||||||
"@vben/types": "workspace:*",
|
"@vben/types": "workspace:*",
|
||||||
"@vben/utils": "workspace:*",
|
"@vben/utils": "workspace:*",
|
||||||
"@vueuse/core": "^11.0.3",
|
"@vueuse/core": "catalog:",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "catalog:",
|
||||||
"element-plus": "^2.8.1",
|
"element-plus": "catalog:",
|
||||||
"pinia": "2.2.2",
|
"pinia": "catalog:",
|
||||||
"vue": "^3.4.38",
|
"vue": "catalog:",
|
||||||
"vue-router": "^4.4.3"
|
"vue-router": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"unplugin-element-plus": "^0.8.0"
|
"unplugin-element-plus": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
235
apps/web-ele/src/adapter/component/index.ts
Normal file
235
apps/web-ele/src/adapter/component/index.ts
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
/**
|
||||||
|
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||||
|
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Component, SetupContext } from 'vue';
|
||||||
|
|
||||||
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ElButton,
|
||||||
|
ElCheckbox,
|
||||||
|
ElCheckboxButton,
|
||||||
|
ElCheckboxGroup,
|
||||||
|
ElDatePicker,
|
||||||
|
ElDivider,
|
||||||
|
ElInput,
|
||||||
|
ElInputNumber,
|
||||||
|
ElNotification,
|
||||||
|
ElRadio,
|
||||||
|
ElRadioButton,
|
||||||
|
ElRadioGroup,
|
||||||
|
ElSelectV2,
|
||||||
|
ElSpace,
|
||||||
|
ElSwitch,
|
||||||
|
ElTimePicker,
|
||||||
|
ElTreeSelect,
|
||||||
|
ElUpload,
|
||||||
|
} from 'element-plus';
|
||||||
|
|
||||||
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
|
component: T,
|
||||||
|
type: 'input' | 'select',
|
||||||
|
) => {
|
||||||
|
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||||
|
const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);
|
||||||
|
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
|
export type ComponentType =
|
||||||
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect'
|
||||||
|
| 'Checkbox'
|
||||||
|
| 'CheckboxGroup'
|
||||||
|
| 'DatePicker'
|
||||||
|
| 'Divider'
|
||||||
|
| 'IconPicker'
|
||||||
|
| 'Input'
|
||||||
|
| 'InputNumber'
|
||||||
|
| 'RadioGroup'
|
||||||
|
| 'Select'
|
||||||
|
| 'Space'
|
||||||
|
| 'Switch'
|
||||||
|
| 'TimePicker'
|
||||||
|
| 'TreeSelect'
|
||||||
|
| 'Upload'
|
||||||
|
| BaseFormComponentType;
|
||||||
|
|
||||||
|
async function initComponentAdapter() {
|
||||||
|
const components: Partial<Record<ComponentType, Component>> = {
|
||||||
|
// 如果你的组件体积比较大,可以使用异步加载
|
||||||
|
// 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,
|
||||||
|
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 },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
// 自定义默认按钮
|
||||||
|
DefaultButton: (props, { attrs, slots }) => {
|
||||||
|
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
|
||||||
|
},
|
||||||
|
// 自定义主要按钮
|
||||||
|
PrimaryButton: (props, { attrs, slots }) => {
|
||||||
|
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
|
||||||
|
},
|
||||||
|
Divider: ElDivider,
|
||||||
|
IconPicker: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
IconPicker,
|
||||||
|
{
|
||||||
|
iconSlot: 'append',
|
||||||
|
modelValueProp: 'model-value',
|
||||||
|
inputComponent: ElInput,
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Input: withDefaultPlaceholder(ElInput, 'input'),
|
||||||
|
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
|
||||||
|
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 ? ElRadioButton : ElRadio, option),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
ElRadioGroup,
|
||||||
|
{ ...props, ...attrs },
|
||||||
|
{ ...slots, default: defaultSlot },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Select: (props, { attrs, slots }) => {
|
||||||
|
return h(ElSelectV2, { ...props, attrs }, slots);
|
||||||
|
},
|
||||||
|
Space: ElSpace,
|
||||||
|
Switch: ElSwitch,
|
||||||
|
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'),
|
||||||
|
Upload: ElUpload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将组件注册到全局共享状态中
|
||||||
|
globalShareState.setComponents(components);
|
||||||
|
|
||||||
|
// 定义全局共享状态中的消息提示
|
||||||
|
globalShareState.defineMessage({
|
||||||
|
// 复制成功消息提示
|
||||||
|
copyPreferencesSuccess: (title, content) => {
|
||||||
|
ElNotification({
|
||||||
|
title,
|
||||||
|
message: content,
|
||||||
|
position: 'bottom-right',
|
||||||
|
duration: 0,
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { initComponentAdapter };
|
39
apps/web-ele/src/adapter/form.ts
Normal file
39
apps/web-ele/src/adapter/form.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import type {
|
||||||
|
VbenFormSchema as FormSchema,
|
||||||
|
VbenFormProps,
|
||||||
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
|
import type { ComponentType } from './component';
|
||||||
|
|
||||||
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
|
setupVbenForm<ComponentType>({
|
||||||
|
config: {
|
||||||
|
modelPropNameMap: {
|
||||||
|
Upload: 'fileList',
|
||||||
|
CheckboxGroup: 'model-value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defineRules: {
|
||||||
|
required: (value, _params, ctx) => {
|
||||||
|
if (value === undefined || value === null || value.length === 0) {
|
||||||
|
return $t('ui.formRules.required', [ctx.label]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
selectRequired: (value, _params, ctx) => {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return $t('ui.formRules.selectRequired', [ctx.label]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const useVbenForm = useForm<ComponentType>;
|
||||||
|
|
||||||
|
export { useVbenForm, z };
|
||||||
|
|
||||||
|
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||||
|
export type { VbenFormProps };
|
68
apps/web-ele/src/adapter/vxe-table.ts
Normal file
68
apps/web-ele/src/adapter/vxe-table.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||||
|
|
||||||
|
import { ElButton, ElImage } from 'element-plus';
|
||||||
|
|
||||||
|
import { useVbenForm } from './form';
|
||||||
|
|
||||||
|
setupVbenVxeTable({
|
||||||
|
configVxeTable: (vxeUI) => {
|
||||||
|
vxeUI.setConfig({
|
||||||
|
grid: {
|
||||||
|
align: 'center',
|
||||||
|
border: false,
|
||||||
|
columnConfig: {
|
||||||
|
resizable: true,
|
||||||
|
},
|
||||||
|
minHeight: 180,
|
||||||
|
formConfig: {
|
||||||
|
// 全局禁用vxe-table的表单配置,使用formOptions
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
autoLoad: true,
|
||||||
|
response: {
|
||||||
|
result: 'items',
|
||||||
|
total: 'total',
|
||||||
|
list: 'items',
|
||||||
|
},
|
||||||
|
showActiveMsg: true,
|
||||||
|
showResponseMsg: false,
|
||||||
|
},
|
||||||
|
round: true,
|
||||||
|
showOverflow: true,
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
||||||
|
vxeUI.renderer.add('CellImage', {
|
||||||
|
renderTableDefault(_renderOpts, params) {
|
||||||
|
const { column, row } = params;
|
||||||
|
const src = row[column.field];
|
||||||
|
return h(ElImage, { src, previewSrcList: [src] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
||||||
|
vxeUI.renderer.add('CellLink', {
|
||||||
|
renderTableDefault(renderOpts) {
|
||||||
|
const { props } = renderOpts;
|
||||||
|
return h(
|
||||||
|
ElButton,
|
||||||
|
{ size: 'small', link: true },
|
||||||
|
{ default: () => props?.text },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
||||||
|
// vxeUI.formats.add
|
||||||
|
},
|
||||||
|
useVbenForm,
|
||||||
|
});
|
||||||
|
|
||||||
|
export { useVbenVxeGrid };
|
||||||
|
|
||||||
|
export type * from '@vben/plugins/vxe-table';
|
@@ -3,17 +3,13 @@ import { baseRequestClient, requestClient } from '#/api/request';
|
|||||||
export namespace AuthApi {
|
export namespace AuthApi {
|
||||||
/** 登录接口参数 */
|
/** 登录接口参数 */
|
||||||
export interface LoginParams {
|
export interface LoginParams {
|
||||||
password: string;
|
password?: string;
|
||||||
username: string;
|
username?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 登录接口返回值 */
|
/** 登录接口返回值 */
|
||||||
export interface LoginResult {
|
export interface LoginResult {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
desc: string;
|
|
||||||
realName: string;
|
|
||||||
userId: string;
|
|
||||||
username: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RefreshTokenResult {
|
export interface RefreshTokenResult {
|
||||||
|
@@ -1,10 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* 该文件可自行根据业务逻辑进行调整
|
* 该文件可自行根据业务逻辑进行调整
|
||||||
*/
|
*/
|
||||||
|
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';
|
||||||
@@ -18,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,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -67,18 +71,14 @@ function createRequestClient(baseURL: string) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// response数据解构
|
// 处理返回的响应数据格式
|
||||||
client.addResponseInterceptor({
|
client.addResponseInterceptor(
|
||||||
fulfilled: (response) => {
|
defaultResponseInterceptor({
|
||||||
const { data: responseData, status } = response;
|
codeField: 'code',
|
||||||
|
dataField: 'data',
|
||||||
const { code, data, message: msg } = responseData;
|
successCode: 0,
|
||||||
if (status >= 200 && status < 400 && code === 0) {
|
}),
|
||||||
return data;
|
);
|
||||||
}
|
|
||||||
throw new Error(`Error ${status}: ${msg}`);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// token过期的处理
|
// token过期的处理
|
||||||
client.addResponseInterceptor(
|
client.addResponseInterceptor(
|
||||||
@@ -93,12 +93,21 @@ function createRequestClient(baseURL: string) {
|
|||||||
|
|
||||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||||||
client.addResponseInterceptor(
|
client.addResponseInterceptor(
|
||||||
errorMessageResponseInterceptor((msg: string) => ElMessage.error(msg)),
|
errorMessageResponseInterceptor((msg: string, error) => {
|
||||||
|
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||||||
|
// 当前mock接口返回的错误字段是 error 或者 message
|
||||||
|
const responseData = error?.response?.data ?? {};
|
||||||
|
const errorMessage = responseData?.error ?? responseData?.message ?? '';
|
||||||
|
// 如果没有错误信息,则会根据状态码进行提示
|
||||||
|
ElMessage.error(errorMessage || msg);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
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,18 +1,37 @@
|
|||||||
import { createApp } from 'vue';
|
import { createApp, watchEffect } from 'vue';
|
||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
|
import { initTippy } from '@vben/common-ui';
|
||||||
|
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 { setupI18n } from '#/locales';
|
import { useTitle } from '@vueuse/core';
|
||||||
|
import { ElLoading } from 'element-plus';
|
||||||
|
|
||||||
|
import { $t, setupI18n } from '#/locales';
|
||||||
|
|
||||||
|
import { initComponentAdapter } from './adapter/component';
|
||||||
import App from './app.vue';
|
import App from './app.vue';
|
||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
|
|
||||||
async function bootstrap(namespace: string) {
|
async function bootstrap(namespace: string) {
|
||||||
|
// 初始化组件适配器
|
||||||
|
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);
|
||||||
|
|
||||||
// 国际化 i18n 配置
|
// 国际化 i18n 配置
|
||||||
await setupI18n(app);
|
await setupI18n(app);
|
||||||
|
|
||||||
@@ -22,9 +41,22 @@ async function bootstrap(namespace: string) {
|
|||||||
// 安装权限指令
|
// 安装权限指令
|
||||||
registerAccessDirective(app);
|
registerAccessDirective(app);
|
||||||
|
|
||||||
|
// 初始化 tippy
|
||||||
|
initTippy(app);
|
||||||
|
|
||||||
// 配置路由及路由守卫
|
// 配置路由及路由守卫
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
|
// 动态更新标题
|
||||||
|
watchEffect(() => {
|
||||||
|
if (preferences.app.dynamicTitle) {
|
||||||
|
const routeTitle = router.currentRoute.value.meta?.title;
|
||||||
|
const pageTitle =
|
||||||
|
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
|
||||||
|
useTitle(pageTitle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { NotificationItem } from '@vben/layouts';
|
import type { NotificationItem } from '@vben/layouts';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
||||||
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||||
|
import { useWatermark } from '@vben/hooks';
|
||||||
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
||||||
import {
|
import {
|
||||||
BasicLayout,
|
BasicLayout,
|
||||||
@@ -13,11 +14,12 @@ import {
|
|||||||
UserDropdown,
|
UserDropdown,
|
||||||
} from '@vben/layouts';
|
} from '@vben/layouts';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { storeToRefs, useAccessStore, useUserStore } from '@vben/stores';
|
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||||
import { openWindow } from '@vben/utils';
|
import { openWindow } from '@vben/utils';
|
||||||
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
|
import LoginForm from '#/views/_core/authentication/login.vue';
|
||||||
|
|
||||||
const notifications = ref<NotificationItem[]>([
|
const notifications = ref<NotificationItem[]>([
|
||||||
{
|
{
|
||||||
@@ -53,6 +55,7 @@ const notifications = ref<NotificationItem[]>([
|
|||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
|
const { destroyWatermark, updateWatermark } = useWatermark();
|
||||||
const showDot = computed(() =>
|
const showDot = computed(() =>
|
||||||
notifications.value.some((item) => !item.isRead),
|
notifications.value.some((item) => !item.isRead),
|
||||||
);
|
);
|
||||||
@@ -65,7 +68,7 @@ const menus = computed(() => [
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: BookOpenText,
|
icon: BookOpenText,
|
||||||
text: $t('widgets.document'),
|
text: $t('ui.widgets.document'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
handler: () => {
|
handler: () => {
|
||||||
@@ -83,12 +86,10 @@ const menus = computed(() => [
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: CircleHelp,
|
icon: CircleHelp,
|
||||||
text: $t('widgets.qa'),
|
text: $t('ui.widgets.qa'),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { loginLoading } = storeToRefs(authStore);
|
|
||||||
|
|
||||||
const avatar = computed(() => {
|
const avatar = computed(() => {
|
||||||
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
||||||
});
|
});
|
||||||
@@ -104,6 +105,21 @@ function handleNoticeClear() {
|
|||||||
function handleMakeAll() {
|
function handleMakeAll() {
|
||||||
notifications.value.forEach((item) => (item.isRead = true));
|
notifications.value.forEach((item) => (item.isRead = true));
|
||||||
}
|
}
|
||||||
|
watch(
|
||||||
|
() => preferences.app.watermark,
|
||||||
|
async (enable) => {
|
||||||
|
if (enable) {
|
||||||
|
await updateWatermark({
|
||||||
|
content: `${userStore.userInfo?.username}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
destroyWatermark();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -130,11 +146,9 @@ function handleMakeAll() {
|
|||||||
<AuthenticationLoginExpiredModal
|
<AuthenticationLoginExpiredModal
|
||||||
v-model:open="accessStore.loginExpired"
|
v-model:open="accessStore.loginExpired"
|
||||||
:avatar
|
:avatar
|
||||||
:loading="loginLoading"
|
>
|
||||||
password-placeholder="123456"
|
<LoginForm />
|
||||||
username-placeholder="vben"
|
</AuthenticationLoginExpiredModal>
|
||||||
@submit="authStore.authLogin"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<template #lock-screen>
|
<template #lock-screen>
|
||||||
<LockScreen :avatar @to-login="handleLogout" />
|
<LockScreen :avatar @to-login="handleLogout" />
|
||||||
|
@@ -1,10 +1,16 @@
|
|||||||
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 { $t, setupI18n as coreSetup, loadLocalesMap } from '@vben/locales';
|
import {
|
||||||
|
$t,
|
||||||
|
setupI18n as coreSetup,
|
||||||
|
loadLocalesMapFromDir,
|
||||||
|
} from '@vben/locales';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@@ -13,10 +19,12 @@ import defaultLocale from 'element-plus/es/locale/lang/zh-cn';
|
|||||||
|
|
||||||
const elementLocale = ref<Language>(defaultLocale);
|
const elementLocale = ref<Language>(defaultLocale);
|
||||||
|
|
||||||
const modules = import.meta.glob('./langs/*.json');
|
const modules = import.meta.glob('./langs/**/*.json');
|
||||||
|
|
||||||
const localesMap = loadLocalesMap(modules);
|
|
||||||
|
|
||||||
|
const localesMap = loadLocalesMapFromDir(
|
||||||
|
/\.\/langs\/([^/]+)\/(.*)\.json$/,
|
||||||
|
modules,
|
||||||
|
);
|
||||||
/**
|
/**
|
||||||
* 加载应用特有的语言包
|
* 加载应用特有的语言包
|
||||||
* 这里也可以改造为从服务端获取翻译数据
|
* 这里也可以改造为从服务端获取翻译数据
|
||||||
@@ -45,14 +53,14 @@ async function loadThirdPartyMessage(lang: SupportedLanguagesType) {
|
|||||||
async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||||
let locale;
|
let locale;
|
||||||
switch (lang) {
|
switch (lang) {
|
||||||
case 'zh-CN': {
|
|
||||||
locale = await import('dayjs/locale/zh-cn');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'en-US': {
|
case 'en-US': {
|
||||||
locale = await import('dayjs/locale/en');
|
locale = await import('dayjs/locale/en');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'zh-CN': {
|
||||||
|
locale = await import('dayjs/locale/zh-cn');
|
||||||
|
break;
|
||||||
|
}
|
||||||
// 默认使用英语
|
// 默认使用英语
|
||||||
default: {
|
default: {
|
||||||
locale = await import('dayjs/locale/en');
|
locale = await import('dayjs/locale/en');
|
||||||
@@ -71,14 +79,14 @@ async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
|||||||
*/
|
*/
|
||||||
async function loadElementLocale(lang: SupportedLanguagesType) {
|
async function loadElementLocale(lang: SupportedLanguagesType) {
|
||||||
switch (lang) {
|
switch (lang) {
|
||||||
case 'zh-CN': {
|
|
||||||
elementLocale.value = defaultLocale;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'en-US': {
|
case 'en-US': {
|
||||||
elementLocale.value = enLocale;
|
elementLocale.value = enLocale;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'zh-CN': {
|
||||||
|
elementLocale.value = defaultLocale;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"page": {
|
|
||||||
"demos": {
|
|
||||||
"title": "Demos",
|
|
||||||
"element-plus": "Element Plus"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
13
apps/web-ele/src/locales/langs/en-US/demos.json
Normal file
13
apps/web-ele/src/locales/langs/en-US/demos.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"title": "Demos",
|
||||||
|
"elementPlus": "Element Plus",
|
||||||
|
"form": "Form",
|
||||||
|
"vben": {
|
||||||
|
"title": "Project",
|
||||||
|
"about": "About",
|
||||||
|
"document": "Document",
|
||||||
|
"antdv": "Ant Design Vue Version",
|
||||||
|
"naive-ui": "Naive UI Version",
|
||||||
|
"element-plus": "Element Plus Version"
|
||||||
|
}
|
||||||
|
}
|
14
apps/web-ele/src/locales/langs/en-US/page.json
Normal file
14
apps/web-ele/src/locales/langs/en-US/page.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"auth": {
|
||||||
|
"login": "Login",
|
||||||
|
"register": "Register",
|
||||||
|
"codeLogin": "Code Login",
|
||||||
|
"qrcodeLogin": "Qr Code Login",
|
||||||
|
"forgetPassword": "Forget Password"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Dashboard",
|
||||||
|
"analytics": "Analytics",
|
||||||
|
"workspace": "Workspace"
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"page": {
|
|
||||||
"demos": {
|
|
||||||
"title": "演示",
|
|
||||||
"element-plus": "Element Plus"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
13
apps/web-ele/src/locales/langs/zh-CN/demos.json
Normal file
13
apps/web-ele/src/locales/langs/zh-CN/demos.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"title": "演示",
|
||||||
|
"elementPlus": "Element Plus",
|
||||||
|
"form": "表单演示",
|
||||||
|
"vben": {
|
||||||
|
"title": "项目",
|
||||||
|
"about": "关于",
|
||||||
|
"document": "文档",
|
||||||
|
"antdv": "Ant Design Vue 版本",
|
||||||
|
"naive-ui": "Naive UI 版本",
|
||||||
|
"element-plus": "Element Plus 版本"
|
||||||
|
}
|
||||||
|
}
|
14
apps/web-ele/src/locales/langs/zh-CN/page.json
Normal file
14
apps/web-ele/src/locales/langs/zh-CN/page.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"auth": {
|
||||||
|
"login": "登录",
|
||||||
|
"register": "注册",
|
||||||
|
"codeLogin": "验证码登录",
|
||||||
|
"qrcodeLogin": "二维码登录",
|
||||||
|
"forgetPassword": "忘记密码"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "概览",
|
||||||
|
"analytics": "分析页",
|
||||||
|
"workspace": "工作台"
|
||||||
|
}
|
||||||
|
}
|
@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
|||||||
/**
|
/**
|
||||||
* @description 项目配置文件
|
* @description 项目配置文件
|
||||||
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
||||||
|
* !!! 更改配置后请清空缓存,否则可能不生效
|
||||||
*/
|
*/
|
||||||
export const overridesPreferences = defineOverridesPreferences({
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
// overrides
|
// overrides
|
||||||
|
@@ -5,10 +5,7 @@ import { preferences } from '@vben/preferences';
|
|||||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||||
import { startProgress, stopProgress } from '@vben/utils';
|
import { startProgress, stopProgress } from '@vben/utils';
|
||||||
|
|
||||||
import { useTitle } from '@vueuse/core';
|
import { accessRoutes, coreRouteNames } from '#/router/routes';
|
||||||
|
|
||||||
import { $t } from '#/locales';
|
|
||||||
import { coreRouteNames, dynamicRoutes } from '#/router/routes';
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
|
|
||||||
import { generateAccess } from './access';
|
import { generateAccess } from './access';
|
||||||
@@ -34,21 +31,12 @@ function setupCommonGuard(router: Router) {
|
|||||||
router.afterEach((to) => {
|
router.afterEach((to) => {
|
||||||
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
|
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
|
||||||
|
|
||||||
if (preferences.tabbar.enable) {
|
loadedPaths.add(to.path);
|
||||||
loadedPaths.add(to.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭页面加载进度条
|
// 关闭页面加载进度条
|
||||||
if (preferences.transition.progress) {
|
if (preferences.transition.progress) {
|
||||||
stopProgress();
|
stopProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 动态修改标题
|
|
||||||
if (preferences.app.dynamicTitle) {
|
|
||||||
const { title } = to.meta;
|
|
||||||
// useTitle(`${$t(title)} - ${preferences.app.name}`);
|
|
||||||
useTitle(`${$t(title)} - ${preferences.app.name}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,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;
|
||||||
@@ -84,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,
|
||||||
};
|
};
|
||||||
@@ -107,14 +100,17 @@ function setupAccessGuard(router: Router) {
|
|||||||
roles: userRoles,
|
roles: userRoles,
|
||||||
router,
|
router,
|
||||||
// 则会在菜单中显示,但是访问会被重定向到403
|
// 则会在菜单中显示,但是访问会被重定向到403
|
||||||
routes: dynamicRoutes,
|
routes: accessRoutes,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 保存菜单信息和路由信息
|
// 保存菜单信息和路由信息
|
||||||
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)),
|
||||||
|
@@ -19,7 +19,12 @@ const router = createRouter({
|
|||||||
: createWebHistory(import.meta.env.VITE_BASE),
|
: createWebHistory(import.meta.env.VITE_BASE),
|
||||||
// 应该添加到路由的初始路由列表。
|
// 应该添加到路由的初始路由列表。
|
||||||
routes,
|
routes,
|
||||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
scrollBehavior: (to, _from, savedPosition) => {
|
||||||
|
if (savedPosition) {
|
||||||
|
return savedPosition;
|
||||||
|
}
|
||||||
|
return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };
|
||||||
|
},
|
||||||
// 是否应该禁止尾部斜杠。
|
// 是否应该禁止尾部斜杠。
|
||||||
// strict: true,
|
// strict: true,
|
||||||
});
|
});
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
import { DEFAULT_HOME_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,28 +21,38 @@ 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,
|
||||||
meta: {
|
meta: {
|
||||||
|
hideInTab: true,
|
||||||
title: 'Authentication',
|
title: 'Authentication',
|
||||||
},
|
},
|
||||||
name: 'Authentication',
|
name: 'Authentication',
|
||||||
path: '/auth',
|
path: '/auth',
|
||||||
|
redirect: LOGIN_PATH,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
path: 'login',
|
path: 'login',
|
||||||
component: Login,
|
component: Login,
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.core.login'),
|
title: $t('page.auth.login'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -50,7 +60,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
path: 'code-login',
|
path: 'code-login',
|
||||||
component: () => import('#/views/_core/authentication/code-login.vue'),
|
component: () => import('#/views/_core/authentication/code-login.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.core.codeLogin'),
|
title: $t('page.auth.codeLogin'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -59,7 +69,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
component: () =>
|
component: () =>
|
||||||
import('#/views/_core/authentication/qrcode-login.vue'),
|
import('#/views/_core/authentication/qrcode-login.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.core.qrcodeLogin'),
|
title: $t('page.auth.qrcodeLogin'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -68,7 +78,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
component: () =>
|
component: () =>
|
||||||
import('#/views/_core/authentication/forget-password.vue'),
|
import('#/views/_core/authentication/forget-password.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.core.forgetPassword'),
|
title: $t('page.auth.forgetPassword'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -76,7 +86,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
path: 'register',
|
path: 'register',
|
||||||
component: () => import('#/views/_core/authentication/register.vue'),
|
component: () => import('#/views/_core/authentication/register.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.core.register'),
|
title: $t('page.auth.register'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@@ -10,15 +10,19 @@ const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
|
|||||||
|
|
||||||
// 有需要可以自行打开注释,并创建文件夹
|
// 有需要可以自行打开注释,并创建文件夹
|
||||||
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
|
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
|
||||||
|
// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
|
||||||
|
|
||||||
/** 动态路由 */
|
/** 动态路由 */
|
||||||
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
|
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[] = [];
|
||||||
const externalRoutes: RouteRecordRaw[] = [];
|
const externalRoutes: RouteRecordRaw[] = [];
|
||||||
|
|
||||||
/** 路由列表,由基本路由+静态路由组成 */
|
/** 路由列表,由基本路由、外部路由和404兜底路由组成
|
||||||
|
* 无需走权限验证(会一直显示在菜单中) */
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
...coreRoutes,
|
...coreRoutes,
|
||||||
...externalRoutes,
|
...externalRoutes,
|
||||||
@@ -28,4 +32,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
||||||
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
|
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
|
||||||
|
|
||||||
export { coreRouteNames, dynamicRoutes, routes };
|
/** 有权限校验的路由列表,包含动态路由和静态路由 */
|
||||||
|
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
|
||||||
|
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',
|
||||||
@@ -29,6 +27,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '/workspace',
|
path: '/workspace',
|
||||||
component: () => import('#/views/dashboard/workspace/index.vue'),
|
component: () => import('#/views/dashboard/workspace/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
|
icon: 'carbon:workspace',
|
||||||
title: $t('page.dashboard.workspace'),
|
title: $t('page.dashboard.workspace'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -1,28 +1,34 @@
|
|||||||
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,
|
||||||
order: 1000,
|
order: 1000,
|
||||||
title: $t('page.demos.title'),
|
title: $t('demos.title'),
|
||||||
},
|
},
|
||||||
name: 'Demos',
|
name: 'Demos',
|
||||||
path: '/demos',
|
path: '/demos',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.demos.element-plus'),
|
title: $t('demos.elementPlus'),
|
||||||
},
|
},
|
||||||
name: 'NaiveDemos',
|
name: 'NaiveDemos',
|
||||||
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'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@@ -7,31 +7,22 @@ import {
|
|||||||
VBEN_LOGO_URL,
|
VBEN_LOGO_URL,
|
||||||
VBEN_NAIVE_PREVIEW_URL,
|
VBEN_NAIVE_PREVIEW_URL,
|
||||||
} from '@vben/constants';
|
} from '@vben/constants';
|
||||||
|
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('page.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('page.vben.about'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'VbenDocument',
|
name: 'VbenDocument',
|
||||||
path: '/vben-admin/document',
|
path: '/vben-admin/document',
|
||||||
@@ -39,7 +30,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
meta: {
|
meta: {
|
||||||
icon: 'lucide:book-open-text',
|
icon: 'lucide:book-open-text',
|
||||||
link: VBEN_DOC_URL,
|
link: VBEN_DOC_URL,
|
||||||
title: $t('page.vben.document'),
|
title: $t('demos.vben.document'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -58,8 +49,9 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: IFrameView,
|
component: IFrameView,
|
||||||
meta: {
|
meta: {
|
||||||
badgeType: 'dot',
|
badgeType: 'dot',
|
||||||
|
icon: 'logos:naiveui',
|
||||||
link: VBEN_NAIVE_PREVIEW_URL,
|
link: VBEN_NAIVE_PREVIEW_URL,
|
||||||
title: $t('page.vben.naive-ui'),
|
title: $t('demos.vben.naive-ui'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -68,12 +60,23 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: IFrameView,
|
component: IFrameView,
|
||||||
meta: {
|
meta: {
|
||||||
badgeType: 'dot',
|
badgeType: 'dot',
|
||||||
|
icon: SvgAntdvLogoIcon,
|
||||||
link: VBEN_ANT_PREVIEW_URL,
|
link: VBEN_ANT_PREVIEW_URL,
|
||||||
title: $t('page.vben.antdv'),
|
title: $t('demos.vben.antdv'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import type { LoginAndRegisterParams } from '@vben/common-ui';
|
import type { Recordable, UserInfo } from '@vben/types';
|
||||||
import type { UserInfo } from '@vben/types';
|
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
@@ -26,7 +25,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
* @param params 登录表单数据
|
* @param params 登录表单数据
|
||||||
*/
|
*/
|
||||||
async function authLogin(
|
async function authLogin(
|
||||||
params: LoginAndRegisterParams,
|
params: Recordable<any>,
|
||||||
onSuccess?: () => Promise<void> | void,
|
onSuccess?: () => Promise<void> | void,
|
||||||
) {
|
) {
|
||||||
// 异步处理用户登录操作并获取 accessToken
|
// 异步处理用户登录操作并获取 accessToken
|
||||||
@@ -77,11 +76,15 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function logout(redirect: boolean = true) {
|
async function logout(redirect: boolean = true) {
|
||||||
await logoutApi();
|
try {
|
||||||
|
await logoutApi();
|
||||||
|
} catch {
|
||||||
|
// 不做任何处理
|
||||||
|
}
|
||||||
resetAllStores();
|
resetAllStores();
|
||||||
accessStore.setLoginExpired(false);
|
accessStore.setLoginExpired(false);
|
||||||
|
|
||||||
// 回登陆页带上当前路由地址
|
// 回登录页带上当前路由地址
|
||||||
await router.replace({
|
await router.replace({
|
||||||
path: LOGIN_PATH,
|
path: LOGIN_PATH,
|
||||||
query: redirect
|
query: redirect
|
||||||
|
@@ -1,21 +1,60 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { LoginCodeParams } from '@vben/common-ui';
|
import type { VbenFormSchema } from '@vben/common-ui';
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationCodeLogin } from '@vben/common-ui';
|
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
|
||||||
import { LOGIN_PATH } from '@vben/constants';
|
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[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'VbenInput',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: $t('authentication.mobile'),
|
||||||
|
},
|
||||||
|
fieldName: 'phoneNumber',
|
||||||
|
label: $t('authentication.mobile'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: $t('authentication.mobileTip') })
|
||||||
|
.refine((v) => /^\d{11}$/.test(v), {
|
||||||
|
message: $t('authentication.mobileErrortip'),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'VbenPinInput',
|
||||||
|
componentProps: {
|
||||||
|
codeLength: CODE_LENGTH,
|
||||||
|
createText: (countdown: number) => {
|
||||||
|
const text =
|
||||||
|
countdown > 0
|
||||||
|
? $t('authentication.sendText', [countdown])
|
||||||
|
: $t('authentication.sendCode');
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
placeholder: $t('authentication.code'),
|
||||||
|
},
|
||||||
|
fieldName: 'code',
|
||||||
|
label: $t('authentication.code'),
|
||||||
|
rules: z.string().length(CODE_LENGTH, {
|
||||||
|
message: $t('authentication.codeTip', [CODE_LENGTH]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
/**
|
/**
|
||||||
* 异步处理登录操作
|
* 异步处理登录操作
|
||||||
* Asynchronously handle the login process
|
* Asynchronously handle the login process
|
||||||
* @param values 登录表单数据
|
* @param values 登录表单数据
|
||||||
*/
|
*/
|
||||||
async function handleLogin(values: LoginCodeParams) {
|
async function handleLogin(values: Recordable<any>) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(values);
|
console.log(values);
|
||||||
}
|
}
|
||||||
@@ -23,8 +62,8 @@ async function handleLogin(values: LoginCodeParams) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationCodeLogin
|
<AuthenticationCodeLogin
|
||||||
|
:form-schema="formSchema"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:login-path="LOGIN_PATH"
|
|
||||||
@submit="handleLogin"
|
@submit="handleLogin"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -1,14 +1,34 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import type { VbenFormSchema } from '@vben/common-ui';
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { AuthenticationForgetPassword } from '@vben/common-ui';
|
import { computed, ref } from 'vue';
|
||||||
import { LOGIN_PATH } from '@vben/constants';
|
|
||||||
|
import { AuthenticationForgetPassword, z } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
defineOptions({ name: 'ForgetPassword' });
|
defineOptions({ name: 'ForgetPassword' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
function handleSubmit(value: string) {
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'VbenInput',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: 'example@example.com',
|
||||||
|
},
|
||||||
|
fieldName: 'email',
|
||||||
|
label: $t('authentication.email'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: $t('authentication.emailTip') })
|
||||||
|
.email($t('authentication.emailValidErrorTip')),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleSubmit(value: Recordable<any>) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('reset email:', value);
|
console.log('reset email:', value);
|
||||||
}
|
}
|
||||||
@@ -16,8 +36,8 @@ function handleSubmit(value: string) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationForgetPassword
|
<AuthenticationForgetPassword
|
||||||
|
:form-schema="formSchema"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:login-path="LOGIN_PATH"
|
|
||||||
@submit="handleSubmit"
|
@submit="handleSubmit"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -1,18 +1,98 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { AuthenticationLogin } from '@vben/common-ui';
|
import type { VbenFormSchema } from '@vben/common-ui';
|
||||||
|
import type { BasicOption } from '@vben/types';
|
||||||
|
|
||||||
|
import { computed, markRaw } from 'vue';
|
||||||
|
|
||||||
|
import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
|
|
||||||
defineOptions({ name: 'Login' });
|
defineOptions({ name: 'Login' });
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const MOCK_USER_OPTIONS: BasicOption[] = [
|
||||||
|
{
|
||||||
|
label: 'Super',
|
||||||
|
value: 'vben',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Admin',
|
||||||
|
value: 'admin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'User',
|
||||||
|
value: 'jack',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'VbenSelect',
|
||||||
|
componentProps: {
|
||||||
|
options: MOCK_USER_OPTIONS,
|
||||||
|
placeholder: $t('authentication.selectAccount'),
|
||||||
|
},
|
||||||
|
fieldName: 'selectAccount',
|
||||||
|
label: $t('authentication.selectAccount'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: $t('authentication.selectAccount') })
|
||||||
|
.optional()
|
||||||
|
.default('vben'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'VbenInput',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: $t('authentication.usernameTip'),
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
trigger(values, form) {
|
||||||
|
if (values.selectAccount) {
|
||||||
|
const findUser = MOCK_USER_OPTIONS.find(
|
||||||
|
(item) => item.value === values.selectAccount,
|
||||||
|
);
|
||||||
|
if (findUser) {
|
||||||
|
form.setValues({
|
||||||
|
password: '123456',
|
||||||
|
username: findUser.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
triggerFields: ['selectAccount'],
|
||||||
|
},
|
||||||
|
fieldName: 'username',
|
||||||
|
label: $t('authentication.username'),
|
||||||
|
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: $t('authentication.password'),
|
||||||
|
},
|
||||||
|
fieldName: 'password',
|
||||||
|
label: $t('authentication.password'),
|
||||||
|
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: markRaw(SliderCaptcha),
|
||||||
|
fieldName: 'captcha',
|
||||||
|
rules: z.boolean().refine((value) => value, {
|
||||||
|
message: $t('authentication.verifyRequiredTip'),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationLogin
|
<AuthenticationLogin
|
||||||
|
:form-schema="formSchema"
|
||||||
:loading="authStore.loginLoading"
|
:loading="authStore.loginLoading"
|
||||||
password-placeholder="123456"
|
|
||||||
username-placeholder="vben"
|
|
||||||
@submit="authStore.authLogin"
|
@submit="authStore.authLogin"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user