Compare commits
458 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
59183029b6 | |
|
|
e81ca2c13f | |
|
|
dcccef1c02 | |
|
|
9a5bee4dce | |
|
|
29665f02bf | |
|
|
06f776d1ef | |
|
|
40f0ba71f5 | |
|
|
0fced45a9c | |
|
|
2ea7da06c5 | |
|
|
c164904a14 | |
|
|
a0ceb45df9 | |
|
|
c641542c71 | |
|
|
a3d8e4bfc1 | |
|
|
e385823d46 | |
|
|
897220e19a | |
|
|
b293e112c6 | |
|
|
627e31f1b0 | |
|
|
8020b4b743 | |
|
|
228c5463da | |
|
|
50ee691191 | |
|
|
eda6ffaf1e | |
|
|
f542db27f9 | |
|
|
b2cf1646a4 | |
|
|
adecddae67 | |
|
|
a653e428f3 | |
|
|
eb62e63a04 | |
|
|
ccabbf0e97 | |
|
|
5b84ac5b13 | |
|
|
f610bd690b | |
|
|
76f9d3d9fc | |
|
|
1e6c39a4c6 | |
|
|
7a1f8da68f | |
|
|
51cae9b00c | |
|
|
7cbeaa8390 | |
|
|
6be3a0e204 | |
|
|
a9b76ba2ed | |
|
|
53ccec1d80 | |
|
|
4af5d6152b | |
|
|
307781f437 | |
|
|
1326994d8e | |
|
|
fd70a3f3e0 | |
|
|
298930b0d7 | |
|
|
54d95b8761 | |
|
|
4a16040d3e | |
|
|
ee95548340 | |
|
|
320e687bad | |
|
|
ad43c6817e | |
|
|
c8747c079d | |
|
|
224bfe7fcb | |
|
|
f443bfbc7b | |
|
|
195b2ea0d2 | |
|
|
4150479549 | |
|
|
5ebf513498 | |
|
|
4e4ffc439c | |
|
|
ad7ed50b52 | |
|
|
92f8916225 | |
|
|
7e4edd270d | |
|
|
332ff44219 | |
|
|
834ce3efc0 | |
|
|
5211f5065d | |
|
|
96d6f89732 | |
|
|
6ab06584eb | |
|
|
a6433c2b50 | |
|
|
128a131797 | |
|
|
c775d7ed80 | |
|
|
b8b4308e1c | |
|
|
80d6e2255f | |
|
|
4e0968d4b7 | |
|
|
44a5809a46 | |
|
|
2428fb1407 | |
|
|
bb78882f72 | |
|
|
df88a23102 | |
|
|
ca5f360231 | |
|
|
147b50ec45 | |
|
|
34439dce4e | |
|
|
9a22027b35 | |
|
|
282a102826 | |
|
|
417e6c2ade | |
|
|
9d69d7f46c | |
|
|
87d1593a1f | |
|
|
7fbdf3d914 | |
|
|
65287cf4b7 | |
|
|
6da3017dcf | |
|
|
5c02057198 | |
|
|
a7ca7cdb9f | |
|
|
79408d406d | |
|
|
e555f71bf8 | |
|
|
4c320346c3 | |
|
|
e5ec88169a | |
|
|
914711ae04 | |
|
|
4c1e3b9548 | |
|
|
9cd3987475 | |
|
|
47a853330d | |
|
|
2aced2f659 | |
|
|
cd955df02f | |
|
|
0a819df2bf | |
|
|
67afcadcf0 | |
|
|
1128ef5acd | |
|
|
ca39b8d0c9 | |
|
|
fece74f744 | |
|
|
6b3506f128 | |
|
|
5613dcef99 | |
|
|
3528517fe0 | |
|
|
2a5b520ec9 | |
|
|
e2fb3602f1 | |
|
|
da3580cbd7 | |
|
|
0c300d040c | |
|
|
d43a3729c3 | |
|
|
bed97a84d8 | |
|
|
b908076846 | |
|
|
885a0a9a00 | |
|
|
340baf4f0b | |
|
|
82cda0edaa | |
|
|
9fe875355a | |
|
|
5f21bd2036 | |
|
|
5b5ea6d2d8 | |
|
|
3dcfd23036 | |
|
|
186914bcac | |
|
|
4b3205fee8 | |
|
|
e4453841db | |
|
|
32db4cbd11 | |
|
|
5558249cd3 | |
|
|
86b636ae54 | |
|
|
c9f7154524 | |
|
|
d72f872369 | |
|
|
b300011d07 | |
|
|
3946253d6e | |
|
|
11fc367845 | |
|
|
bdc65cc250 | |
|
|
70dad0f600 | |
|
|
26e9aa244b | |
|
|
913f77fd2f | |
|
|
dba774e1c7 | |
|
|
af09d652a3 | |
|
|
0babdfbc44 | |
|
|
f154d53be9 | |
|
|
ed3cd2fe3b | |
|
|
59912a00bc | |
|
|
675d8b0179 | |
|
|
a1ca296fc0 | |
|
|
c1b1fe90fd | |
|
|
30b5610a73 | |
|
|
db9b9df8f7 | |
|
|
ae6a75e913 | |
|
|
37d72c1628 | |
|
|
ab3e6bb37c | |
|
|
9ddb899a1a | |
|
|
1f0cda8aee | |
|
|
90ae85317c | |
|
|
a8ae891aff | |
|
|
1f2df3e944 | |
|
|
e39a432210 | |
|
|
6b3bcee582 | |
|
|
6c274b75b8 | |
|
|
278032c94b | |
|
|
23a8982f5c | |
|
|
5df6c32d04 | |
|
|
7cae330c3c | |
|
|
100aaa4cee | |
|
|
ead0b73e7b | |
|
|
2ace846e38 | |
|
|
1d98393f0c | |
|
|
c48ee2a364 | |
|
|
95d1e8432f | |
|
|
4d59ac78bd | |
|
|
f1143e134e | |
|
|
e3e869faee | |
|
|
8350e72393 | |
|
|
15f74b9d97 | |
|
|
55b54e24fe | |
|
|
46b4ce81e4 | |
|
|
7a723d03d0 | |
|
|
9d6fbfd0d6 | |
|
|
8fd6bf47b1 | |
|
|
f25f3a34d0 | |
|
|
2823848fae | |
|
|
b9467b2bc3 | |
|
|
fa190e0975 | |
|
|
90dc8cf997 | |
|
|
53c5ccc00a | |
|
|
06c9e8d7c1 | |
|
|
f32818c6aa | |
|
|
fb03afb6b7 | |
|
|
577efa56a9 | |
|
|
cb98b3a47e | |
|
|
8daf9a3ce5 | |
|
|
a83d8248d7 | |
|
|
4cdc92f759 | |
|
|
54c668c3f0 | |
|
|
ac3fc6b7d3 | |
|
|
a6a6efdf59 | |
|
|
8043faf6c7 | |
|
|
ebed9e64ed | |
|
|
913636ae44 | |
|
|
7b064e9f33 | |
|
|
16da0eaca3 | |
|
|
6acfee2737 | |
|
|
92abf7edaa | |
|
|
395babc1f5 | |
|
|
68cde54bad | |
|
|
c7d7529c00 | |
|
|
748f60c7bb | |
|
|
ffee62e940 | |
|
|
a0ea221131 | |
|
|
2846bcb84e | |
|
|
542ed6c08f | |
|
|
6dabb848a5 | |
|
|
de0181e0d9 | |
|
|
a850d426ef | |
|
|
771277d5d9 | |
|
|
20b4f5c99f | |
|
|
e7fa87b301 | |
|
|
40c66958bc | |
|
|
600fc71aed | |
|
|
443e4b04cd | |
|
|
556a3c0fab | |
|
|
1eca52f962 | |
|
|
e21adb395b | |
|
|
0e4bf80bf4 | |
|
|
107750971b | |
|
|
24e1be47ca | |
|
|
7e0978c764 | |
|
|
83a0c9662d | |
|
|
a4736a49f8 | |
|
|
1cbdf442ee | |
|
|
f91a2702c9 | |
|
|
c885c0c71a | |
|
|
a8f67ab717 | |
|
|
aa7d8630b5 | |
|
|
36313f378e | |
|
|
45054d3238 | |
|
|
173e6b08c9 | |
|
|
75e4d07395 | |
|
|
2a86404ba5 | |
|
|
b8a0199cde | |
|
|
a46ed55a86 | |
|
|
1a9fbddef4 | |
|
|
1209aaafb4 | |
|
|
8e71261d49 | |
|
|
586978f1b0 | |
|
|
49e45eab54 | |
|
|
bd22793ceb | |
|
|
b2013436c5 | |
|
|
cc808cb8c5 | |
|
|
afffc4b3f0 | |
|
|
99710ef9dc | |
|
|
3d4ae04d9b | |
|
|
707b391449 | |
|
|
45b843f344 | |
|
|
191fd90f06 | |
|
|
05920cd66d | |
|
|
01508d5e42 | |
|
|
57cf6cbc9e | |
|
|
dd69d7c1a5 | |
|
|
63743b6929 | |
|
|
38597dd19d | |
|
|
03ebbea46a | |
|
|
8e7a5d1ec3 | |
|
|
e7365a4a00 | |
|
|
aa74a2535b | |
|
|
32379ba4b7 | |
|
|
bf4fed78f2 | |
|
|
722afc85df | |
|
|
3036596d16 | |
|
|
aee539f37e | |
|
|
05b41692ba | |
|
|
7fe8d7b4be | |
|
|
aace726a91 | |
|
|
e6f6e5464a | |
|
|
7d04b600fb | |
|
|
8a622889ff | |
|
|
463bfde2ac | |
|
|
893f74dc3e | |
|
|
e136679934 | |
|
|
8a215fbcc7 | |
|
|
ac5e4c4722 | |
|
|
04d01b0bab | |
|
|
cb1d7565a3 | |
|
|
1d9b6407a4 | |
|
|
22ed522711 | |
|
|
a3598ef859 | |
|
|
6fe09ec2dd | |
|
|
57911d9e09 | |
|
|
3aee283495 | |
|
|
54b24c2677 | |
|
|
8cadad0a1e | |
|
|
633c5f3cda | |
|
|
a8431e2040 | |
|
|
7a2b916387 | |
|
|
f3deefae56 | |
|
|
d0a7065991 | |
|
|
5b7e7c4d56 | |
|
|
f4dfb68b7b | |
|
|
8f4f27d860 | |
|
|
e9eab29953 | |
|
|
4f1eeb7da5 | |
|
|
6fd426d719 | |
|
|
331da3c8c7 | |
|
|
c48943bc67 | |
|
|
f6c5bc4b7c | |
|
|
448f073143 | |
|
|
fc31aa3c8f | |
|
|
7680b33b99 | |
|
|
548da70f9f | |
|
|
0441afc24f | |
|
|
1196dab9e4 | |
|
|
00bf48704d | |
|
|
7a1218e6f0 | |
|
|
dbb1a19c5d | |
|
|
f433b207c6 | |
|
|
82b91dfed3 | |
|
|
1295aee180 | |
|
|
00105e1302 | |
|
|
e82433e3e7 | |
|
|
ccaef2b591 | |
|
|
bb5d75bc7e | |
|
|
528395e2c3 | |
|
|
6a9012e5e4 | |
|
|
6e8315ab40 | |
|
|
36aa195378 | |
|
|
de2662ca8f | |
|
|
35acb9558b | |
|
|
c4a262d1e2 | |
|
|
a1e021074e | |
|
|
0a7ead980a | |
|
|
cdcbd58f0e | |
|
|
c57f3d8820 | |
|
|
02c977f969 | |
|
|
a0019cef04 | |
|
|
e447a8a569 | |
|
|
071468b716 | |
|
|
24b8bba754 | |
|
|
6d60071515 | |
|
|
baed599fcc | |
|
|
7cb2699f19 | |
|
|
1ce562601f | |
|
|
4b5da81ba6 | |
|
|
6aca9a9c99 | |
|
|
c55465a6c0 | |
|
|
2ae684bdad | |
|
|
ce2bfa5cd2 | |
|
|
63265c1a6b | |
|
|
fa195fde8e | |
|
|
1057f2932b | |
|
|
b9224fc379 | |
|
|
57dd818170 | |
|
|
49256ec1b7 | |
|
|
f6f92e5403 | |
|
|
613c311076 | |
|
|
9ee7a7d9ff | |
|
|
44f8aed06d | |
|
|
d5d4a5c591 | |
|
|
74381aa8c1 | |
|
|
203ee9b623 | |
|
|
13b1ef71a9 | |
|
|
ba08820be8 | |
|
|
d9e933e3a6 | |
|
|
50216e5047 | |
|
|
6c8c49966a | |
|
|
4aeb7a489a | |
|
|
3862942e9f | |
|
|
8571fc43b0 | |
|
|
59aabd956d | |
|
|
9b09ba4483 | |
|
|
42f2fecb1e | |
|
|
af0057940a | |
|
|
9e3d75ae65 | |
|
|
e5f5523bc6 | |
|
|
4cc900f542 | |
|
|
ce34e6e1a0 | |
|
|
db1dfae481 | |
|
|
012412ec22 | |
|
|
db97d414ec | |
|
|
8bf286fda0 | |
|
|
91119eac8e | |
|
|
686c3f9208 | |
|
|
8a7e2bd8e4 | |
|
|
174c4ae749 | |
|
|
67da9417a8 | |
|
|
d5b49e6a3b | |
|
|
c894617e10 | |
|
|
10f4641fee | |
|
|
c478bef269 | |
|
|
0302b70c48 | |
|
|
2426f891e7 | |
|
|
e2433fc531 | |
|
|
bbc74ae663 | |
|
|
ea79b7d6a1 | |
|
|
f4a4ced88d | |
|
|
19b2d7af41 | |
|
|
343d8a1c1e | |
|
|
9480f8272a | |
|
|
0d9e260a6a | |
|
|
51bca25345 | |
|
|
694396dcfb | |
|
|
1cb53e943e | |
|
|
13c8318adc | |
|
|
48ed797055 | |
|
|
3c2285141c | |
|
|
49b884c0b1 | |
|
|
24d20ca9ee | |
|
|
6f02181024 | |
|
|
17d5d1b889 | |
|
|
a5c76ef89d | |
|
|
e622c052a9 | |
|
|
ed3353a271 | |
|
|
de28c5c4c2 | |
|
|
965f5f96b7 | |
|
|
61d9df7f58 | |
|
|
231a5169ec | |
|
|
ce7b7b910a | |
|
|
f7f01c9280 | |
|
|
81a61558cb | |
|
|
a9f21c1acb | |
|
|
19c7f0d5dd | |
|
|
cd43149429 | |
|
|
19919f6685 | |
|
|
036ef294db | |
|
|
7d2bc2e885 | |
|
|
89b237f6b4 | |
|
|
d8f685708d | |
|
|
f8ce09a203 | |
|
|
59d83d29cb | |
|
|
02193755be | |
|
|
6d524906a3 | |
|
|
cbd1f0bcbb | |
|
|
2ba2c8e986 | |
|
|
06f1ae1a66 | |
|
|
029b2ffaab | |
|
|
64ac25de00 | |
|
|
6606dfd40a | |
|
|
6da4a39ff9 | |
|
|
aa95d0e87c | |
|
|
6353f0a8e9 | |
|
|
e6327ae9da | |
|
|
4395353c22 | |
|
|
c023ebbdb9 | |
|
|
af3fe53ec8 | |
|
|
e981fb159f | |
|
|
79b9d55854 | |
|
|
b2055a4457 | |
|
|
7bf7e09002 | |
|
|
de8d39ffed | |
|
|
543a7e3962 | |
|
|
9dfe3f5af8 | |
|
|
f11b08d8cb | |
|
|
45b6f08984 | |
|
|
92a4676f8d | |
|
|
7bf7c0bb06 | |
|
|
8f8cf5b704 | |
|
|
6be238430d | |
|
|
f77216d8f4 | |
|
|
d42a9b2409 | |
|
|
6753834054 | |
|
|
77a4a64eb4 | |
|
|
49db40d557 | |
|
|
0032c608f1 | |
|
|
fa603b32b1 |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 163 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 46 KiB |
|
|
@ -9,7 +9,7 @@ runs:
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version-file: .node-version
|
node-version-file: .node-version
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ jobs:
|
||||||
- windows-latest
|
- windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,12 @@ jobs:
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v5
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|
@ -90,7 +90,7 @@ jobs:
|
||||||
- windows-latest
|
- windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ jobs:
|
||||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|
@ -98,7 +98,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|
@ -129,7 +129,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,6 @@ jobs:
|
||||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
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@v7
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,15 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [20]
|
node-version: [22]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
# - name: Checkout code
|
# - name: Checkout code
|
||||||
# uses: actions/checkout@v4
|
# uses: actions/checkout@v6
|
||||||
# with:
|
# with:
|
||||||
# fetch-depth: 0
|
# fetch-depth: 0
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ jobs:
|
||||||
echo "version=${version}" >> $GITHUB_OUTPUT
|
echo "version=${version}" >> $GITHUB_OUTPUT
|
||||||
echo "major=${major}" >> $GITHUB_OUTPUT
|
echo "major=${major}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: release-drafter/release-drafter@v6
|
- uses: release-drafter/release-drafter@v7
|
||||||
with:
|
with:
|
||||||
version: ${{ steps.version.outputs.version }}
|
version: ${{ steps.version.outputs.version }}
|
||||||
publish: true
|
publish: true
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ yarn.lock
|
||||||
package-lock.json
|
package-lock.json
|
||||||
.VSCodeCounter
|
.VSCodeCounter
|
||||||
**/backend-mock/data
|
**/backend-mock/data
|
||||||
|
.omx
|
||||||
# local env files
|
# local env files
|
||||||
.env.local
|
.env.local
|
||||||
.env.*.local
|
.env.*.local
|
||||||
|
|
@ -50,3 +50,10 @@ vite.config.ts.*
|
||||||
*.sw?
|
*.sw?
|
||||||
.history
|
.history
|
||||||
.cursor
|
.cursor
|
||||||
|
|
||||||
|
# AI
|
||||||
|
.agent
|
||||||
|
.agents
|
||||||
|
.claude
|
||||||
|
.codex
|
||||||
|
skills-lock.json
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
22.1.0
|
22.22.0
|
||||||
|
|
|
||||||
4
.npmrc
4
.npmrc
|
|
@ -1,8 +1,8 @@
|
||||||
registry=https://registry.npmmirror.com
|
registry=https://registry.npmmirror.com
|
||||||
public-hoist-pattern[]=lefthook
|
public-hoist-pattern[]=lefthook
|
||||||
public-hoist-pattern[]=eslint
|
public-hoist-pattern[]=eslint
|
||||||
public-hoist-pattern[]=prettier
|
public-hoist-pattern[]=oxfmt
|
||||||
public-hoist-pattern[]=prettier-plugin-tailwindcss
|
public-hoist-pattern[]=oxlint
|
||||||
public-hoist-pattern[]=stylelint
|
public-hoist-pattern[]=stylelint
|
||||||
public-hoist-pattern[]=*postcss*
|
public-hoist-pattern[]=*postcss*
|
||||||
public-hoist-pattern[]=@commitlint/*
|
public-hoist-pattern[]=@commitlint/*
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
dist
|
|
||||||
dev-dist
|
|
||||||
.local
|
|
||||||
.output.js
|
|
||||||
node_modules
|
|
||||||
.nvmrc
|
|
||||||
coverage
|
|
||||||
CODEOWNERS
|
|
||||||
.nitro
|
|
||||||
.output
|
|
||||||
|
|
||||||
|
|
||||||
**/*.svg
|
|
||||||
**/*.sh
|
|
||||||
|
|
||||||
public
|
|
||||||
.npmrc
|
|
||||||
*-lock.yaml
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export { default } from '@vben/prettier-config';
|
|
||||||
|
|
@ -2,3 +2,7 @@ dist
|
||||||
public
|
public
|
||||||
__tests__
|
__tests__
|
||||||
coverage
|
coverage
|
||||||
|
.codex
|
||||||
|
.claude
|
||||||
|
.agent
|
||||||
|
.agents
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,18 @@
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
// Vue 3 的语言支持
|
// Vue 3 的语言支持
|
||||||
"Vue.volar",
|
"Vue.volar",
|
||||||
// 将 ESLint JavaScript 集成到 VS Code 中。
|
// 将 eslint 集成到 VS Code 中。
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
|
// 将 oxlint 集成到 VS Code 中。
|
||||||
|
"oxc.oxc-vscode",
|
||||||
// Visual Studio Code 的官方 Stylelint 扩展
|
// Visual Studio Code 的官方 Stylelint 扩展
|
||||||
"stylelint.vscode-stylelint",
|
"stylelint.vscode-stylelint",
|
||||||
// 使用 Prettier 的代码格式化程序
|
// 使用 oxfmt 的代码格式化程序
|
||||||
"esbenp.prettier-vscode",
|
"oxc.oxc-vscode",
|
||||||
// 支持 dotenv 文件语法
|
// 支持 dotenv 文件语法
|
||||||
"mikestead.dotenv",
|
"mikestead.dotenv",
|
||||||
|
// YAML 语言支持,供 ESLint 校验 pnpm-workspace.yaml 等文件
|
||||||
|
"redhat.vscode-yaml",
|
||||||
// 源代码的拼写检查器
|
// 源代码的拼写检查器
|
||||||
"streetsidesoftware.code-spell-checker",
|
"streetsidesoftware.code-spell-checker",
|
||||||
// Tailwind CSS 的官方 VS Code 插件
|
// Tailwind CSS 的官方 VS Code 插件
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,15 @@
|
||||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "chrome",
|
||||||
|
"name": "vben admin antd dev",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "http://localhost:5999",
|
||||||
|
"env": { "NODE_ENV": "development" },
|
||||||
|
"sourceMaps": true,
|
||||||
|
"webRoot": "${workspaceFolder}/apps/web-antdv-next"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
"name": "vben admin antd dev",
|
"name": "vben admin antd dev",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"tailwindCSS.experimental.configFile": "internal/tailwind-config/src/index.ts",
|
"tailwindCSS.experimental.configFile": "internal/tailwind-config/src/theme.css",
|
||||||
|
"tailwindCSS.lint.suggestCanonicalClasses": "ignore",
|
||||||
// workbench
|
// workbench
|
||||||
"workbench.list.smoothScrolling": true,
|
"workbench.list.smoothScrolling": true,
|
||||||
"workbench.startupEditor": "newUntitledFile",
|
"workbench.startupEditor": "newUntitledFile",
|
||||||
|
|
@ -31,39 +32,51 @@
|
||||||
"editor.autoClosingOvertype": "always",
|
"editor.autoClosingOvertype": "always",
|
||||||
"editor.autoClosingQuotes": "beforeWhitespace",
|
"editor.autoClosingQuotes": "beforeWhitespace",
|
||||||
"editor.wordSeparators": "`~!@#%^&*()=+[{]}\\|;:'\",.<>/?",
|
"editor.wordSeparators": "`~!@#%^&*()=+[{]}\\|;:'\",.<>/?",
|
||||||
|
"editor.quickSuggestions": {
|
||||||
|
"strings": "on"
|
||||||
|
},
|
||||||
|
|
||||||
|
// lint && format
|
||||||
|
"oxc.enable": true,
|
||||||
|
"oxc.typeAware": true,
|
||||||
|
"oxc.configPath": "oxlint.config.ts",
|
||||||
|
"oxc.fmt.configPath": "oxfmt.config.ts",
|
||||||
|
"eslint.useFlatConfig": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit",
|
"source.fixAll.eslint": "explicit",
|
||||||
|
"source.fixAll.oxc": "explicit",
|
||||||
"source.fixAll.stylelint": "explicit",
|
"source.fixAll.stylelint": "explicit",
|
||||||
"source.organizeImports": "never"
|
"source.organizeImports": "never"
|
||||||
},
|
},
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "oxc.oxc-vscode",
|
||||||
"[html]": {
|
"[html]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "oxc.oxc-vscode"
|
||||||
},
|
},
|
||||||
"[css]": {
|
"[css]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "oxc.oxc-vscode"
|
||||||
},
|
},
|
||||||
"[scss]": {
|
"[scss]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "oxc.oxc-vscode"
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "oxc.oxc-vscode"
|
||||||
},
|
},
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "oxc.oxc-vscode"
|
||||||
},
|
},
|
||||||
"[json]": {
|
"[json]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "oxc.oxc-vscode"
|
||||||
},
|
},
|
||||||
"[markdown]": {
|
"[markdown]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "oxc.oxc-vscode"
|
||||||
},
|
},
|
||||||
"[jsonc]": {
|
"[jsonc]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "oxc.oxc-vscode"
|
||||||
},
|
},
|
||||||
"[vue]": {
|
"[vue]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "oxc.oxc-vscode"
|
||||||
},
|
},
|
||||||
|
|
||||||
// extensions
|
// extensions
|
||||||
"extensions.ignoreRecommendations": true,
|
"extensions.ignoreRecommendations": true,
|
||||||
|
|
||||||
|
|
@ -79,6 +92,7 @@
|
||||||
"files.insertFinalNewline": true,
|
"files.insertFinalNewline": true,
|
||||||
"files.simpleDialog.enable": true,
|
"files.simpleDialog.enable": true,
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
|
"*.css": "tailwindcss",
|
||||||
"*.ejs": "html",
|
"*.ejs": "html",
|
||||||
"*.art": "html",
|
"*.art": "html",
|
||||||
"**/tsconfig.json": "jsonc",
|
"**/tsconfig.json": "jsonc",
|
||||||
|
|
@ -118,7 +132,7 @@
|
||||||
// search
|
// search
|
||||||
"search.searchEditor.singleClickBehaviour": "peekDefinition",
|
"search.searchEditor.singleClickBehaviour": "peekDefinition",
|
||||||
"search.followSymlinks": false,
|
"search.followSymlinks": false,
|
||||||
// 在使用搜索功能时,将这些文件夹/文件排除在外
|
// 使用搜索功能时,将这些文件和文件夹排除在外
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/node_modules": true,
|
"**/node_modules": true,
|
||||||
"**/*.log": true,
|
"**/*.log": true,
|
||||||
|
|
@ -159,7 +173,7 @@
|
||||||
"emmet.triggerExpansionOnTab": false,
|
"emmet.triggerExpansionOnTab": false,
|
||||||
|
|
||||||
"errorLens.enabledDiagnosticLevels": ["warning", "error"],
|
"errorLens.enabledDiagnosticLevels": ["warning", "error"],
|
||||||
"errorLens.excludeBySource": ["cSpell", "Grammarly", "eslint"],
|
"errorLens.excludeBySource": ["cSpell", "Grammarly"],
|
||||||
|
|
||||||
"stylelint.enable": true,
|
"stylelint.enable": true,
|
||||||
"stylelint.packageManager": "pnpm",
|
"stylelint.packageManager": "pnpm",
|
||||||
|
|
@ -167,9 +181,10 @@
|
||||||
"stylelint.customSyntax": "postcss-html",
|
"stylelint.customSyntax": "postcss-html",
|
||||||
"stylelint.snippet": ["css", "less", "postcss", "scss", "vue"],
|
"stylelint.snippet": ["css", "less", "postcss", "scss", "vue"],
|
||||||
|
|
||||||
"typescript.inlayHints.enumMemberValues.enabled": true,
|
"js/ts.tsdk.path": "node_modules/typescript/lib",
|
||||||
"typescript.preferences.preferTypeOnlyAutoImports": true,
|
"js/ts.inlayHints.enumMemberValues.enabled": true,
|
||||||
"typescript.preferences.includePackageJsonAutoImports": "on",
|
"js/ts.preferences.preferTypeOnlyAutoImports": true,
|
||||||
|
"js/ts.preferences.includePackageJsonAutoImports": "on",
|
||||||
|
|
||||||
"eslint.validate": [
|
"eslint.validate": [
|
||||||
"javascript",
|
"javascript",
|
||||||
|
|
@ -181,7 +196,8 @@
|
||||||
"markdown",
|
"markdown",
|
||||||
"json",
|
"json",
|
||||||
"jsonc",
|
"jsonc",
|
||||||
"json5"
|
"json5",
|
||||||
|
"yaml"
|
||||||
],
|
],
|
||||||
|
|
||||||
"tailwindCSS.experimental.classRegex": [
|
"tailwindCSS.experimental.classRegex": [
|
||||||
|
|
@ -192,7 +208,7 @@
|
||||||
"*": false
|
"*": false
|
||||||
},
|
},
|
||||||
|
|
||||||
"cssVariables.lookupFiles": ["packages/core/base/design/src/**/*.css"],
|
"cssVariables.lookupFiles": ["packages/@core/base/design/src/**/*.css"],
|
||||||
|
|
||||||
"i18n-ally.localesPaths": [
|
"i18n-ally.localesPaths": [
|
||||||
"packages/locales/src/langs",
|
"packages/locales/src/langs",
|
||||||
|
|
@ -217,12 +233,9 @@
|
||||||
"*.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",
|
||||||
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml",
|
"oxlint.config.ts": ".eslintignore,.stylelintignore,.commitlintrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml,oxfmt.config.*,eslint.config.*"
|
||||||
"tailwind.config.mjs": "postcss.*"
|
|
||||||
},
|
},
|
||||||
"commentTranslate.hover.enabled": false,
|
"commentTranslate.hover.enabled": false,
|
||||||
"commentTranslate.multiLineMerge": true,
|
"commentTranslate.multiLineMerge": true,
|
||||||
"vue.server.hybridMode": true,
|
"vue.server.hybridMode": true
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
|
||||||
"oxc.enable": false
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
226
README.md
226
README.md
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
## 🐶 新手必读
|
## 🐶 新手必读
|
||||||
|
|
||||||
- nodejs > 20.12.0 && pnpm > 10.22.0 (强制使用pnpm)
|
- nodejs >= v20.19.0(推荐 v22 / v24) && pnpm >= 10.32.1(强制使用 pnpm)
|
||||||
- 演示地址【Vue3 + element-plus】:<http://dashboard-vue3.yudao.iocoder.cn>
|
- 演示地址【Vue3 + element-plus】:<http://dashboard-vue3.yudao.iocoder.cn>
|
||||||
- 演示地址【Vue3 + vben5(ant-design-vue)】:<http://dashboard-vben.yudao.iocoder.cn>
|
- 演示地址【Vue3 + vben5(ant-design-vue)】:<http://dashboard-vben.yudao.iocoder.cn>
|
||||||
- 演示地址【Vue2 + element-ui】:<http://dashboard.yudao.iocoder.cn>
|
- 演示地址【Vue2 + element-ui】:<http://dashboard.yudao.iocoder.cn>
|
||||||
|
|
@ -20,12 +20,12 @@
|
||||||
|
|
||||||
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
|
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
|
||||||
|
|
||||||
- 采用最新 [vue-vben-admin](https://github.com/vbenjs/vue-vben-admin) v5 实现
|
- 采用最新 [vue-vben-admin](https://github.com/vbenjs/vue-vben-admin) v5.7.0 实现
|
||||||
- 支持 [Ant Design Vue](https://www.antdv.com/) | [Element Plus](https://element-plus.org/zh-CN/) | [Naive UI](https://www.naiveui.com/) | [TDesign](https://tdesign.tencent.com/) 多种免费开源的中后台模版,具备如下特性:
|
- 支持 [Ant Design Vue](https://www.antdv.com/) | [Element Plus](https://element-plus.org/zh-CN/) | [Naive UI](https://www.naiveui.com/) | [TDesign](https://tdesign.tencent.com/) 多种免费开源的中后台模版,具备如下特性:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- **最新技术栈**:使用 Vue3、Vite6 等前端前沿技术开发
|
- **最新技术栈**:使用 Vue3、Vite8 等前端前沿技术开发
|
||||||
- **TypeScript**: 应用程序级 JavaScript 的语言
|
- **TypeScript**: 应用程序级 JavaScript 的语言
|
||||||
- **主题**: 提供多套主题色彩,可配置自定义主题
|
- **主题**: 提供多套主题色彩,可配置自定义主题
|
||||||
- **国际化**:内置完善的国际化方案
|
- **国际化**:内置完善的国际化方案
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
- **组件**:二次封装了多个常用的组件
|
- **组件**:二次封装了多个常用的组件
|
||||||
- **示例**:内置丰富的示例
|
- **示例**:内置丰富的示例
|
||||||
|
|
||||||
## [外包项目请联系【非项目需求请勿扫码,非客服,不解答项目问题】](https://www.shuduokeji.com)
|
## [外包项目请联系【非项目需求请勿扫码,非客服,不解答项目问题】](https://www.shuduokeji.com?yudao)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
@ -41,26 +41,26 @@
|
||||||
|
|
||||||
| 框架 | 说明 | 版本 |
|
| 框架 | 说明 | 版本 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| [Vue](https://staging-cn.vuejs.org/) | vue框架 | 3.5.24 |
|
| [Vue](https://staging-cn.vuejs.org/) | vue框架 | 3.5.30 |
|
||||||
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 7.2.2 |
|
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 8.0.0 |
|
||||||
| [Ant Design Vue](https://www.antdv.com/) | Ant Design Vue | 4.2.6 |
|
| [Ant Design Vue](https://www.antdv.com/) | Ant Design Vue | 4.2.6 |
|
||||||
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.10.2 |
|
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.13.5 |
|
||||||
| [Naive UI](https://www.naiveui.com/) | Naive UI | 2.42.0 |
|
| [Naive UI](https://www.naiveui.com/) | Naive UI | 2.44.1 |
|
||||||
| [TDesign](https://tdesign.tencent.com/) | TDesign | 1.17.1 |
|
| [TDesign](https://tdesign.tencent.com/) | TDesign | 1.18.5 |
|
||||||
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 超集 | 5.9.3 |
|
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 超集 | 5.9.3 |
|
||||||
| [pinia](https://pinia.vuejs.org/) | Vue 存储库替代 vuex5 | 3.0.3 |
|
| [pinia](https://pinia.vuejs.org/) | Vue 存储库替代 vuex5 | 3.0.4 |
|
||||||
| [vueuse](https://vueuse.org/) | 常用工具集 | 13.4.0 |
|
| [vueuse](https://vueuse.org/) | 常用工具集 | 14.2.1 |
|
||||||
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 11.1.7 |
|
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 11.3.0 |
|
||||||
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.5.1 |
|
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 5.0.3 |
|
||||||
| [Tailwind CSS](https://tailwindcss.com/) | 原子 CSS | 3.4.18 |
|
| [Tailwind CSS](https://tailwindcss.com/) | 原子 CSS | 4.2.1 |
|
||||||
| [Iconify](https://iconify.design/) | 图标组件 | 5.0.0 |
|
| [Iconify](https://iconify.design/) | 图标组件 | 5.0.0 |
|
||||||
| [Iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.406 |
|
| [Iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.449 |
|
||||||
| [TinyMCE](https://www.tiny.cloud/) | 富文本编辑器 | 6.1.0 |
|
| [TinyMCE](https://www.tiny.cloud/) | 富文本编辑器 | 7.3.0 |
|
||||||
| [Echarts](https://echarts.apache.org/) | 图表库 | 6.0.0 |
|
| [Echarts](https://echarts.apache.org/) | 图表库 | 6.0.0 |
|
||||||
| [axios](https://axios-http.com/) | http客户端 | 1.10.0 |
|
| [axios](https://axios-http.com/) | http客户端 | 1.13.6 |
|
||||||
| [dayjs](https://day.js.org/) | 日期处理库 | 1.11.13 |
|
| [dayjs](https://day.js.org/) | 日期处理库 | 1.11.20 |
|
||||||
| [vee-validate](https://vee-validate.logaretm.com/) | 表单验证 | 4.15.1 |
|
| [vee-validate](https://vee-validate.logaretm.com/) | 表单验证 | 4.15.1 |
|
||||||
| [zod](https://zod.dev/) | 数据验证 | 3.25.67 |
|
| [zod](https://zod.dev/) | 数据验证 | 3.25.76 |
|
||||||
|
|
||||||
## 🔥 后端架构
|
## 🔥 后端架构
|
||||||
|
|
||||||
|
|
@ -84,31 +84,31 @@
|
||||||
|
|
||||||
- 通用模块(必选):系统功能、基础设施
|
- 通用模块(必选):系统功能、基础设施
|
||||||
- 通用模块(可选):工作流程、支付系统、数据报表、会员中心
|
- 通用模块(可选):工作流程、支付系统、数据报表、会员中心
|
||||||
- 业务系统(按需):ERP 系统、CRM 系统、商城系统、微信公众号、AI 大模型
|
- 业务系统(按需):ERP 系统、CRM 系统、MES 系统、商城系统、微信公众号、AI 大模型、IoT 物联网
|
||||||
|
|
||||||
### 系统功能
|
### 系统功能
|
||||||
|
|
||||||
| | 功能 | 描述 |
|
| | 功能 | 描述 |
|
||||||
|----|-------|---------------------------------|
|
| --- | --- | --- |
|
||||||
| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 |
|
| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 |
|
||||||
| ⭐️ | 在线用户 | 当前系统中活跃用户状态监控,支持手动踢下线 |
|
| ⭐️ | 在线用户 | 当前系统中活跃用户状态监控,支持手动踢下线 |
|
||||||
| | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 |
|
| | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 |
|
||||||
| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 |
|
| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 |
|
||||||
| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 |
|
| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 |
|
||||||
| | 岗位管理 | 配置系统用户所属担任职务 |
|
| | 岗位管理 | 配置系统用户所属担任职务 |
|
||||||
| 🚀 | 租户管理 | 配置系统租户,支持 SaaS 场景下的多租户功能 |
|
| 🚀 | 租户管理 | 配置系统租户,支持 SaaS 场景下的多租户功能 |
|
||||||
| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 |
|
| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 |
|
||||||
| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 |
|
| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 |
|
||||||
| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、腾讯云等主流短信平台 |
|
| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、腾讯云等主流短信平台 |
|
||||||
| 🚀 | 邮件管理 | 邮箱账号、邮件模版、邮件发送日志,支持所有邮件平台 |
|
| 🚀 | 邮件管理 | 邮箱账号、邮件模版、邮件发送日志,支持所有邮件平台 |
|
||||||
| 🚀 | 站内信 | 系统内的消息通知,提供站内信模版、站内信消息 |
|
| 🚀 | 站内信 | 系统内的消息通知,提供站内信模版、站内信消息 |
|
||||||
| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
|
| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
|
||||||
| ⭐️ | 登录日志 | 系统登录日志记录查询,包含登录异常 |
|
| ⭐️ | 登录日志 | 系统登录日志记录查询,包含登录异常 |
|
||||||
| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 |
|
| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 |
|
||||||
| | 通知公告 | 系统通知公告信息发布维护 |
|
| | 通知公告 | 系统通知公告信息发布维护 |
|
||||||
| 🚀 | 敏感词 | 配置系统敏感词,支持标签分组 |
|
| 🚀 | 敏感词 | 配置系统敏感词,支持标签分组 |
|
||||||
| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
|
| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
|
||||||
| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 |
|
| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 |
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
@ -126,32 +126,32 @@
|
||||||
>
|
>
|
||||||
> 前者支持轻量配置简单流程,后者实现复杂场景深度编排
|
> 前者支持轻量配置简单流程,后者实现复杂场景深度编排
|
||||||
|
|
||||||
| 功能列表 | 功能描述 | 是否完成 |
|
| 功能列表 | 功能描述 | 是否完成 |
|
||||||
|------------|-------------------------------------------------------------------------------------|------|
|
| --- | --- | --- |
|
||||||
| SIMPLE 设计器 | 仿钉钉/飞书设计器,支持拖拽搭建表单流程,10 分钟快速完成审批流程配置 | ✅ |
|
| SIMPLE 设计器 | 仿钉钉/飞书设计器,支持拖拽搭建表单流程,10 分钟快速完成审批流程配置 | ✅ |
|
||||||
| BPMN 设计器 | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求 | ✅ |
|
| BPMN 设计器 | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求 | ✅ |
|
||||||
| 会签 | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点 | ✅ |
|
| 会签 | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点 | ✅ |
|
||||||
| 或签 | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点 | ✅ |
|
| 或签 | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点 | ✅ |
|
||||||
| 依次审批 | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批,A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅ |
|
| 依次审批 | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批,A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅ |
|
||||||
| 抄送 | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人 | ✅ |
|
| 抄送 | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人 | ✅ |
|
||||||
| 驳回 | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点 | ✅ |
|
| 驳回 | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点 | ✅ |
|
||||||
| 转办 | A 转给其 B 审批,B 审批后,进入下一节点 | ✅ |
|
| 转办 | A 转给其 B 审批,B 审批后,进入下一节点 | ✅ |
|
||||||
| 委派 | A 转给其 B 审批,B 审批后,转给 A,A 继续审批后进入下一节点 | ✅ |
|
| 委派 | A 转给其 B 审批,B 审批后,转给 A,A 继续审批后进入下一节点 | ✅ |
|
||||||
| 加签 | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签 | ✅ |
|
| 加签 | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签 | ✅ |
|
||||||
| 减签 | (取消加签)在当前审批人操作之前,减少审批人 | ✅ |
|
| 减签 | (取消加签)在当前审批人操作之前,减少审批人 | ✅ |
|
||||||
| 撤销 | (取消流程)流程发起人,可以对流程进行撤销处理 | ✅ |
|
| 撤销 | (取消流程)流程发起人,可以对流程进行撤销处理 | ✅ |
|
||||||
| 终止 | 系统管理员,在任意节点终止流程实例 | ✅ |
|
| 终止 | 系统管理员,在任意节点终止流程实例 | ✅ |
|
||||||
| 表单权限 | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限 | ✅ |
|
| 表单权限 | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限 | ✅ |
|
||||||
| 超时审批 | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作 | ✅ |
|
| 超时审批 | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作 | ✅ |
|
||||||
| 自动提醒 | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次 | ✅ |
|
| 自动提醒 | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次 | ✅ |
|
||||||
| 父子流程 | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程 | ✅ |
|
| 父子流程 | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程 | ✅ |
|
||||||
| 条件分支 | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行 | ✅ |
|
| 条件分支 | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行 | ✅ |
|
||||||
| 并行分支 | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行 | ✅ |
|
| 并行分支 | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行 | ✅ |
|
||||||
| 包容分支 | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支 | ✅ |
|
| 包容分支 | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支 | ✅ |
|
||||||
| 路由分支 | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行) | ✅ |
|
| 路由分支 | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行) | ✅ |
|
||||||
| 触发节点 | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等 | ✅ |
|
| 触发节点 | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等 | ✅ |
|
||||||
| 延迟节点 | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等 | ✅ |
|
| 延迟节点 | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等 | ✅ |
|
||||||
| 拓展设置 | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等 | ✅ |
|
| 拓展设置 | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等 | ✅ |
|
||||||
|
|
||||||
### 支付系统
|
### 支付系统
|
||||||
|
|
||||||
|
|
@ -165,26 +165,26 @@
|
||||||
|
|
||||||
### 基础设施
|
### 基础设施
|
||||||
|
|
||||||
| | 功能 | 描述 |
|
| | 功能 | 描述 |
|
||||||
|----|-----------|----------------------------------------------|
|
| --- | --- | --- |
|
||||||
| 🚀 | 代码生成 | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载 |
|
| 🚀 | 代码生成 | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载 |
|
||||||
| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 |
|
| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 |
|
||||||
| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 |
|
| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 |
|
||||||
| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 |
|
| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 |
|
||||||
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
|
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
|
||||||
| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
|
| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
|
||||||
| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等 |
|
| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等 |
|
||||||
| 🚀 | WebSocket | 提供 WebSocket 接入示例,支持一对一、一对多发送方式 |
|
| 🚀 | WebSocket | 提供 WebSocket 接入示例,支持一对一、一对多发送方式 |
|
||||||
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
|
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
|
||||||
| | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 |
|
| | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 |
|
||||||
| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
|
| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
|
||||||
| 🚀 | 消息队列 | 基于 Redis 实现消息队列,Stream 提供集群消费,Pub/Sub 提供广播消费 |
|
| 🚀 | 消息队列 | 基于 Redis 实现消息队列,Stream 提供集群消费,Pub/Sub 提供广播消费 |
|
||||||
| 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
|
| 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
|
||||||
| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 |
|
| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 |
|
||||||
| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 |
|
| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 |
|
||||||
| 🚀 | 服务保障 | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景 |
|
| 🚀 | 服务保障 | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景 |
|
||||||
| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
|
| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
|
||||||
| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
|
| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
@ -197,19 +197,19 @@
|
||||||
|
|
||||||
### 微信公众号
|
### 微信公众号
|
||||||
|
|
||||||
| | 功能 | 描述 |
|
| | 功能 | 描述 |
|
||||||
|----|--------|-------------------------------|
|
| --- | --- | --- |
|
||||||
| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 |
|
| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 |
|
||||||
| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 |
|
| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 |
|
||||||
| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 |
|
| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 |
|
||||||
| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 |
|
| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 |
|
||||||
| 🚀 | 模版消息 | 配置和发送模版消息,用于向粉丝推送通知类消息 |
|
| 🚀 | 模版消息 | 配置和发送模版消息,用于向粉丝推送通知类消息 |
|
||||||
| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 |
|
| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 |
|
||||||
| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 |
|
| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 |
|
||||||
| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 |
|
| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 |
|
||||||
| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 |
|
| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 |
|
||||||
| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 |
|
| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 |
|
||||||
| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 |
|
| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 |
|
||||||
|
|
||||||
### 商城系统
|
### 商城系统
|
||||||
|
|
||||||
|
|
@ -219,6 +219,16 @@
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### 会员中心
|
||||||
|
|
||||||
|
| | 功能 | 描述 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 🚀 | 会员管理 | 会员是 C 端的消费者,该功能用于会员的搜索与管理 |
|
||||||
|
| 🚀 | 会员标签 | 对会员的标签进行创建、查询、修改、删除等操作 |
|
||||||
|
| 🚀 | 会员等级 | 对会员的等级、成长值进行管理,可用于订单折扣等会员权益 |
|
||||||
|
| 🚀 | 会员分组 | 对会员进行分组,用于用户画像、内容推送等运营手段 |
|
||||||
|
| 🚀 | 积分签到 | 回馈给签到、消费等行为的积分,会员可订单抵现、积分兑换等途径消耗 |
|
||||||
|
|
||||||
### ERP 系统
|
### ERP 系统
|
||||||
|
|
||||||
演示地址:<https://doc.iocoder.cn/erp-preview/>
|
演示地址:<https://doc.iocoder.cn/erp-preview/>
|
||||||
|
|
@ -231,6 +241,14 @@
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### MES 系统
|
||||||
|
|
||||||
|
演示地址:<https://doc.iocoder.cn/mes-preview/>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### AI 大模型
|
### AI 大模型
|
||||||
|
|
||||||
演示地址:<https://doc.iocoder.cn/ai-preview/>
|
演示地址:<https://doc.iocoder.cn/ai-preview/>
|
||||||
|
|
@ -238,3 +256,11 @@
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### IoT 物联网
|
||||||
|
|
||||||
|
演示地址:<https://doc.iocoder.cn/iot/build>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
|
||||||
|
|
@ -33,3 +33,6 @@ VITE_APP_API_ENCRYPT_REQUEST_KEY = 52549111389893486934626385991395
|
||||||
VITE_APP_API_ENCRYPT_RESPONSE_KEY = 96103715984234343991809655248883
|
VITE_APP_API_ENCRYPT_RESPONSE_KEY = 96103715984234343991809655248883
|
||||||
# VITE_APP_API_ENCRYPT_REQUEST_KEY = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCls2rIpnGdYnLFgz1XU13GbNQ5DloyPpvW00FPGjqn5Z6JpK+kDtVlnkhwR87iRrE5Vf2WNqRX6vzbLSgveIQY8e8oqGCb829myjf1MuI+ZzN4ghf/7tEYhZJGPI9AbfxFqBUzm+kR3/HByAI22GLT96WM26QiMK8n3tIP/yiLswIDAQAB
|
# VITE_APP_API_ENCRYPT_REQUEST_KEY = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCls2rIpnGdYnLFgz1XU13GbNQ5DloyPpvW00FPGjqn5Z6JpK+kDtVlnkhwR87iRrE5Vf2WNqRX6vzbLSgveIQY8e8oqGCb829myjf1MuI+ZzN4ghf/7tEYhZJGPI9AbfxFqBUzm+kR3/HByAI22GLT96WM26QiMK8n3tIP/yiLswIDAQAB
|
||||||
# VITE_APP_API_ENCRYPT_RESPONSE_KEY = MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOH8IfIFxL/MR9XIg1UDv5z1fGXQI93E8wrU4iPFovL/sEt9uSgSkjyidC2O7N+m7EKtoN6b1u7cEwXSkwf3kfK0jdWLSQaNpX5YshqXCBzbDfugDaxuyYrNA4/tIMs7mzZAk0APuRXB35Dmupou7Yw7TFW/7QhQmGfzeEKULQvnAgMBAAECgYAw8LqlQGyQoPv5p3gRxEMOCfgL0JzD3XBJKztiRd35RDh40Nx1ejgjW4dPioFwGiVWd2W8cAGHLzALdcQT2KDJh+T/tsd4SPmI6uSBBK6Ff2DkO+kFFcuYvfclQQKqxma5CaZOSqhgenacmgTMFeg2eKlY3symV6JlFNu/IKU42QJBAOhxAK/Eq3e61aYQV2JSguhMR3b8NXJJRroRs/QHEanksJtl+M+2qhkC9nQVXBmBkndnkU/l2tYcHfSBlAyFySMCQQD445tgm/J2b6qMQmuUGQAYDN8FIkHjeKmha+l/fv0igWm8NDlBAem91lNDIPBUzHL1X1+pcts5bjmq99YdOnhtAkAg2J8dN3B3idpZDiQbC8fd5bGPmdI/pSUudAP27uzLEjr2qrE/QPPGdwm2m7IZFJtK7kK1hKio6u48t/bg0iL7AkEAuUUs94h+v702Fnym+jJ2CHEkXvz2US8UDs52nWrZYiM1o1y4tfSHm8H8bv8JCAa9GHyriEawfBraILOmllFdLQJAQSRZy4wmlaG48MhVXodB85X+VZ9krGXZ2TLhz7kz9iuToy53l9jTkESt6L5BfBDCVdIwcXLYgK+8KFdHN5W7HQ==
|
# VITE_APP_API_ENCRYPT_RESPONSE_KEY = MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOH8IfIFxL/MR9XIg1UDv5z1fGXQI93E8wrU4iPFovL/sEt9uSgSkjyidC2O7N+m7EKtoN6b1u7cEwXSkwf3kfK0jdWLSQaNpX5YshqXCBzbDfugDaxuyYrNA4/tIMs7mzZAk0APuRXB35Dmupou7Yw7TFW/7QhQmGfzeEKULQvnAgMBAAECgYAw8LqlQGyQoPv5p3gRxEMOCfgL0JzD3XBJKztiRd35RDh40Nx1ejgjW4dPioFwGiVWd2W8cAGHLzALdcQT2KDJh+T/tsd4SPmI6uSBBK6Ff2DkO+kFFcuYvfclQQKqxma5CaZOSqhgenacmgTMFeg2eKlY3symV6JlFNu/IKU42QJBAOhxAK/Eq3e61aYQV2JSguhMR3b8NXJJRroRs/QHEanksJtl+M+2qhkC9nQVXBmBkndnkU/l2tYcHfSBlAyFySMCQQD445tgm/J2b6qMQmuUGQAYDN8FIkHjeKmha+l/fv0igWm8NDlBAem91lNDIPBUzHL1X1+pcts5bjmq99YdOnhtAkAg2J8dN3B3idpZDiQbC8fd5bGPmdI/pSUudAP27uzLEjr2qrE/QPPGdwm2m7IZFJtK7kK1hKio6u48t/bg0iL7AkEAuUUs94h+v702Fnym+jJ2CHEkXvz2US8UDs52nWrZYiM1o1y4tfSHm8H8bv8JCAa9GHyriEawfBraILOmllFdLQJAQSRZy4wmlaG48MhVXodB85X+VZ9krGXZ2TLhz7kz9iuToy53l9jTkESt6L5BfBDCVdIwcXLYgK+8KFdHN5W7HQ==
|
||||||
|
|
||||||
|
# 百度地图
|
||||||
|
VITE_BAIDU_MAP_KEY=Y2aJXiswwPxy6mwFs1z9c7U5gwX9WfUN
|
||||||
|
|
@ -21,3 +21,6 @@ VITE_INJECT_APP_LOADING=true
|
||||||
|
|
||||||
# 打包后是否生成dist.zip
|
# 打包后是否生成dist.zip
|
||||||
VITE_ARCHIVER=true
|
VITE_ARCHIVER=true
|
||||||
|
|
||||||
|
# 验证码的开关
|
||||||
|
VITE_APP_CAPTCHA_ENABLE=true
|
||||||
|
|
@ -12,16 +12,15 @@
|
||||||
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
|
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
|
||||||
/>
|
/>
|
||||||
<!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 -->
|
<!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 -->
|
||||||
<title><%= VITE_APP_TITLE %></title>
|
<title>%VITE_APP_TITLE%</title>
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<script>
|
<script>
|
||||||
var HM_ID = '<%= VITE_APP_BAIDU_CODE %>'
|
var HM_ID = '<%= VITE_APP_BAIDU_CODE %>';
|
||||||
if (HM_ID) {
|
if (HM_ID) {
|
||||||
var _hmt = _hmt || [];
|
var _hmt = _hmt || [];
|
||||||
(function () {
|
(function () {
|
||||||
var hm = document.createElement('script');
|
var hm = document.createElement('script');
|
||||||
hm.src =
|
hm.src = 'https://hm.baidu.com/hm.js?' + HM_ID;
|
||||||
'https://hm.baidu.com/hm.js?' + HM_ID;
|
|
||||||
var s = document.getElementsByTagName('script')[0];
|
var s = document.getElementsByTagName('script')[0];
|
||||||
s.parentNode.insertBefore(hm, s);
|
s.parentNode.insertBefore(hm, s);
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vben/web-antd",
|
"name": "@vben/web-antd",
|
||||||
"version": "5.5.9",
|
"version": "5.7.0",
|
||||||
"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": {
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export { default } from '@vben/tailwind-config/postcss';
|
|
||||||
|
|
@ -2537,12 +2537,12 @@ interface EditorSelection {
|
||||||
normalize: () => Range;
|
normalize: () => Range;
|
||||||
selectorChanged: (selector: string, callback: (active: boolean, args: {
|
selectorChanged: (selector: string, callback: (active: boolean, args: {
|
||||||
node: Node;
|
node: Node;
|
||||||
selector: String;
|
selector: string;
|
||||||
parents: Node[];
|
parents: Node[];
|
||||||
}) => void) => EditorSelection;
|
}) => void) => EditorSelection;
|
||||||
selectorChangedWithUnbind: (selector: string, callback: (active: boolean, args: {
|
selectorChangedWithUnbind: (selector: string, callback: (active: boolean, args: {
|
||||||
node: Node;
|
node: Node;
|
||||||
selector: String;
|
selector: string;
|
||||||
parents: Node[];
|
parents: Node[];
|
||||||
}) => void) => {
|
}) => void) => {
|
||||||
unbind: () => void;
|
unbind: () => void;
|
||||||
|
|
@ -3217,9 +3217,9 @@ interface Tools {
|
||||||
<T, R>(arr: ArrayLike<T> | null | undefined, cb: ArrayCallback<T, R>): R[];
|
<T, R>(arr: ArrayLike<T> | null | undefined, cb: ArrayCallback<T, R>): R[];
|
||||||
<T, R>(obj: Record<string, T> | null | undefined, cb: ObjCallback<T, R>): R[];
|
<T, R>(obj: Record<string, T> | null | undefined, cb: ObjCallback<T, R>): R[];
|
||||||
};
|
};
|
||||||
extend: (obj: Object, ext: Object, ...objs: Object[]) => any;
|
extend: (obj: object, ext: object, ...objs: object[]) => any;
|
||||||
walk: <T extends Record<string, any>>(obj: T, f: WalkCallback<T>, n?: keyof T, scope?: any) => void;
|
walk: <T extends Record<string, any>>(obj: T, f: WalkCallback<T>, n?: keyof T, scope?: any) => void;
|
||||||
resolve: (path: string, o?: Object) => any;
|
resolve: (path: string, o?: object) => any;
|
||||||
explode: (s: string | string[], d?: string | RegExp) => string[];
|
explode: (s: string | string[], d?: string | RegExp) => string[];
|
||||||
_addCacheSuffix: (url: string) => string;
|
_addCacheSuffix: (url: string) => string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,36 +3,82 @@
|
||||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable vue/one-component-per-file */
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
AutoCompleteProps,
|
||||||
|
ButtonProps,
|
||||||
|
CascaderProps,
|
||||||
|
CheckboxGroupProps,
|
||||||
|
CheckboxProps,
|
||||||
|
DatePickerProps,
|
||||||
|
DividerProps,
|
||||||
|
InputNumberProps,
|
||||||
|
InputProps,
|
||||||
|
MentionsProps,
|
||||||
|
RadioGroupProps,
|
||||||
|
RadioProps,
|
||||||
|
RateProps,
|
||||||
|
SelectProps,
|
||||||
|
SpaceProps,
|
||||||
|
SwitchProps,
|
||||||
|
TextAreaProps,
|
||||||
|
TimePickerProps,
|
||||||
|
TreeSelectProps,
|
||||||
UploadChangeParam,
|
UploadChangeParam,
|
||||||
UploadFile,
|
UploadFile,
|
||||||
UploadProps,
|
UploadProps,
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
|
import type { RangePickerProps } from 'ant-design-vue/es/date-picker';
|
||||||
|
|
||||||
import type { Component, Ref } from 'vue';
|
import type { Component, Ref } from 'vue';
|
||||||
|
|
||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type {
|
||||||
|
ApiComponentSharedProps,
|
||||||
|
BaseFormComponentType,
|
||||||
|
IconPickerProps,
|
||||||
|
} from '@vben/common-ui';
|
||||||
|
import type { Sortable } from '@vben/hooks';
|
||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
computed,
|
||||||
defineAsyncComponent,
|
defineAsyncComponent,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
h,
|
h,
|
||||||
|
nextTick,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
ref,
|
ref,
|
||||||
render,
|
render,
|
||||||
unref,
|
unref,
|
||||||
watch,
|
watch,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
|
|
||||||
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
import {
|
||||||
|
ApiComponent,
|
||||||
|
globalShareState,
|
||||||
|
IconPicker,
|
||||||
|
VCropper,
|
||||||
|
} from '@vben/common-ui';
|
||||||
|
import { useSortable } from '@vben/hooks';
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import { notification } from 'ant-design-vue';
|
import { message, Modal, notification } from 'ant-design-vue';
|
||||||
|
|
||||||
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
||||||
import { FileUpload, ImageUpload } from '#/components/upload';
|
import { FileUpload, ImageUpload } from '#/components/upload';
|
||||||
|
type AdapterUploadProps = UploadProps & {
|
||||||
|
aspectRatio?: string;
|
||||||
|
crop?: boolean;
|
||||||
|
draggable?: boolean;
|
||||||
|
handleChange?: (event: UploadChangeParam) => void;
|
||||||
|
maxSize?: number;
|
||||||
|
onDragSort?: (oldIndex: number, newIndex: number) => void;
|
||||||
|
onHandleChange?: (event: UploadChangeParam) => void;
|
||||||
|
};
|
||||||
|
|
||||||
const AutoComplete = defineAsyncComponent(
|
const AutoComplete = defineAsyncComponent(
|
||||||
() => import('ant-design-vue/es/auto-complete'),
|
() => import('ant-design-vue/es/auto-complete'),
|
||||||
|
|
@ -124,173 +170,110 @@ const withDefaultPlaceholder = <T extends Component>(
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const withPreviewUpload = () => {
|
const IMAGE_EXTENSIONS = new Set([
|
||||||
return defineComponent({
|
'bmp',
|
||||||
name: Upload.name,
|
'gif',
|
||||||
emits: ['change', 'update:modelValue'],
|
'jpeg',
|
||||||
setup: (
|
'jpg',
|
||||||
props: any,
|
'png',
|
||||||
{ attrs, slots, emit }: { attrs: any; emit: any; slots: any },
|
'svg',
|
||||||
) => {
|
'webp',
|
||||||
const previewVisible = ref<boolean>(false);
|
]);
|
||||||
|
|
||||||
const placeholder = attrs?.placeholder || $t(`ui.placeholder.upload`);
|
/**
|
||||||
|
* 检查是否为图片文件
|
||||||
const listType = attrs?.listType || attrs?.['list-type'] || 'text';
|
*/
|
||||||
|
function isImageFile(file: UploadFile): boolean {
|
||||||
const fileList = ref<UploadProps['fileList']>(
|
if (file.url) {
|
||||||
attrs?.fileList || attrs?.['file-list'] || [],
|
try {
|
||||||
);
|
const pathname = new URL(file.url, 'http://localhost').pathname;
|
||||||
|
const ext = pathname.split('.').pop()?.toLowerCase();
|
||||||
const handleChange = async (event: UploadChangeParam) => {
|
return ext ? IMAGE_EXTENSIONS.has(ext) : false;
|
||||||
fileList.value = event.fileList;
|
} catch {
|
||||||
emit('change', event);
|
const ext = file.url?.split('.').pop()?.toLowerCase();
|
||||||
emit(
|
return ext ? IMAGE_EXTENSIONS.has(ext) : false;
|
||||||
'update:modelValue',
|
|
||||||
event.fileList?.length ? fileList.value : undefined,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePreview = async (file: UploadFile) => {
|
|
||||||
previewVisible.value = true;
|
|
||||||
await previewImage(file, previewVisible, fileList);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderUploadButton = (): any => {
|
|
||||||
const isDisabled = attrs.disabled;
|
|
||||||
|
|
||||||
// 如果禁用,不渲染上传按钮
|
|
||||||
if (isDisabled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 否则渲染默认上传按钮
|
|
||||||
return isEmpty(slots)
|
|
||||||
? createDefaultSlotsWithUpload(listType, placeholder)
|
|
||||||
: slots;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 可以监听到表单API设置的值
|
|
||||||
watch(
|
|
||||||
() => attrs.modelValue,
|
|
||||||
(res) => {
|
|
||||||
fileList.value = res;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return () =>
|
|
||||||
h(
|
|
||||||
Upload,
|
|
||||||
{
|
|
||||||
...props,
|
|
||||||
...attrs,
|
|
||||||
fileList: fileList.value,
|
|
||||||
onChange: handleChange,
|
|
||||||
onPreview: handlePreview,
|
|
||||||
},
|
|
||||||
renderUploadButton(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const createDefaultSlotsWithUpload = (
|
|
||||||
listType: string,
|
|
||||||
placeholder: string,
|
|
||||||
) => {
|
|
||||||
switch (listType) {
|
|
||||||
case 'picture-card': {
|
|
||||||
return {
|
|
||||||
default: () => placeholder,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return {
|
|
||||||
default: () =>
|
|
||||||
h(
|
|
||||||
Button,
|
|
||||||
{
|
|
||||||
icon: h(IconifyIcon, {
|
|
||||||
icon: 'ant-design:upload-outlined',
|
|
||||||
class: 'mb-1 size-4',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
() => placeholder,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
if (!file.type) {
|
||||||
|
const ext = file.name?.split('.').pop()?.toLowerCase();
|
||||||
|
return ext ? IMAGE_EXTENSIONS.has(ext) : false;
|
||||||
|
}
|
||||||
|
return file.type.startsWith('image/');
|
||||||
|
}
|
||||||
|
|
||||||
const previewImage = async (
|
/**
|
||||||
|
* 创建默认的上传按钮插槽
|
||||||
|
*/
|
||||||
|
function createDefaultUploadSlots(listType: string, placeholder: string) {
|
||||||
|
if (listType === 'picture-card') {
|
||||||
|
return { default: () => placeholder };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
default: () =>
|
||||||
|
h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
icon: h(IconifyIcon, {
|
||||||
|
icon: 'ant-design:upload-outlined',
|
||||||
|
class: 'mb-1 size-4',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
() => placeholder,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件的 Base64
|
||||||
|
*/
|
||||||
|
function getBase64(file: File): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.addEventListener('load', () => resolve(reader.result as string));
|
||||||
|
reader.addEventListener('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预览图片
|
||||||
|
*/
|
||||||
|
async function previewImage(
|
||||||
file: UploadFile,
|
file: UploadFile,
|
||||||
visible: Ref<boolean>,
|
visible: Ref<boolean>,
|
||||||
fileList: Ref<UploadProps['fileList']>,
|
fileList: Ref<UploadProps['fileList']>,
|
||||||
) => {
|
) {
|
||||||
// 检查是否为图片文件的辅助函数
|
// 非图片文件直接打开链接
|
||||||
const isImageFile = (file: UploadFile): boolean => {
|
|
||||||
const imageExtensions = new Set([
|
|
||||||
'bmp',
|
|
||||||
'gif',
|
|
||||||
'jpeg',
|
|
||||||
'jpg',
|
|
||||||
'png',
|
|
||||||
'webp',
|
|
||||||
]);
|
|
||||||
if (file.url) {
|
|
||||||
const ext = file.url?.split('.').pop()?.toLowerCase();
|
|
||||||
return ext ? imageExtensions.has(ext) : false;
|
|
||||||
}
|
|
||||||
if (!file.type) {
|
|
||||||
const ext = file.name?.split('.').pop()?.toLowerCase();
|
|
||||||
return ext ? imageExtensions.has(ext) : false;
|
|
||||||
}
|
|
||||||
return file.type.startsWith('image/');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 如果当前文件不是图片,直接打开
|
|
||||||
if (!isImageFile(file)) {
|
if (!isImageFile(file)) {
|
||||||
if (file.url) {
|
const url = file.url || file.preview;
|
||||||
window.open(file.url, '_blank');
|
if (url) {
|
||||||
} else if (file.preview) {
|
window.open(url, '_blank');
|
||||||
window.open(file.preview, '_blank');
|
|
||||||
} else {
|
} else {
|
||||||
console.warn('无法打开文件,没有可用的URL或预览地址');
|
message.error($t('ui.formRules.previewWarning'));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对于图片文件,继续使用预览组
|
|
||||||
const [ImageComponent, PreviewGroupComponent] = await Promise.all([
|
const [ImageComponent, PreviewGroupComponent] = await Promise.all([
|
||||||
Image,
|
Image,
|
||||||
PreviewGroup,
|
PreviewGroup,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const getBase64 = (file: File) => {
|
// 过滤图片文件并生成预览
|
||||||
return new Promise((resolve, reject) => {
|
const imageFiles = (unref(fileList) || []).filter((f) => isImageFile(f));
|
||||||
const reader = new FileReader();
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
reader.addEventListener('load', () => resolve(reader.result));
|
|
||||||
reader.addEventListener('error', (error) => reject(error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// 从fileList中过滤出所有图片文件
|
|
||||||
const imageFiles = (unref(fileList) || []).filter((element) =>
|
|
||||||
isImageFile(element),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 为所有没有预览地址的图片生成预览
|
|
||||||
for (const imgFile of imageFiles) {
|
for (const imgFile of imageFiles) {
|
||||||
if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
|
if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
|
||||||
imgFile.preview = (await getBase64(imgFile.originFileObj)) as string;
|
imgFile.preview = await getBase64(imgFile.originFileObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const container: HTMLElement | null = document.createElement('div');
|
|
||||||
document.body.append(container);
|
|
||||||
|
|
||||||
// 用于追踪组件是否已卸载
|
const container = document.createElement('div');
|
||||||
|
document.body.append(container);
|
||||||
let isUnmounted = false;
|
let isUnmounted = false;
|
||||||
|
|
||||||
|
const currentIndex = imageFiles.findIndex((f) => f.uid === file.uid);
|
||||||
|
|
||||||
const PreviewWrapper = {
|
const PreviewWrapper = {
|
||||||
setup() {
|
setup() {
|
||||||
return () => {
|
return () => {
|
||||||
|
|
@ -301,12 +284,10 @@ const previewImage = async (
|
||||||
class: 'hidden',
|
class: 'hidden',
|
||||||
preview: {
|
preview: {
|
||||||
visible: visible.value,
|
visible: visible.value,
|
||||||
// 设置初始显示的图片索引
|
current: currentIndex,
|
||||||
current: imageFiles.findIndex((f) => f.uid === file.uid),
|
|
||||||
onVisibleChange: (value: boolean) => {
|
onVisibleChange: (value: boolean) => {
|
||||||
visible.value = value;
|
visible.value = value;
|
||||||
if (!value) {
|
if (!value) {
|
||||||
// 延迟清理,确保动画完成
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!isUnmounted && container) {
|
if (!isUnmounted && container) {
|
||||||
isUnmounted = true;
|
isUnmounted = true;
|
||||||
|
|
@ -319,7 +300,6 @@ const previewImage = async (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
() =>
|
() =>
|
||||||
// 渲染所有图片文件
|
|
||||||
imageFiles.map((imgFile) =>
|
imageFiles.map((imgFile) =>
|
||||||
h(ImageComponent, {
|
h(ImageComponent, {
|
||||||
key: imgFile.uid,
|
key: imgFile.uid,
|
||||||
|
|
@ -332,6 +312,292 @@ const previewImage = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
render(h(PreviewWrapper), container);
|
render(h(PreviewWrapper), container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片裁剪操作
|
||||||
|
*/
|
||||||
|
function cropImage(file: File, aspectRatio: string | undefined) {
|
||||||
|
return new Promise<Blob | string | undefined>((resolve, reject) => {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
document.body.append(container);
|
||||||
|
|
||||||
|
let isUnmounted = false;
|
||||||
|
let objectUrl: null | string = null;
|
||||||
|
|
||||||
|
const open = ref<boolean>(true);
|
||||||
|
const cropperRef = ref<InstanceType<typeof VCropper> | null>(null);
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
open.value = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!isUnmounted && container) {
|
||||||
|
if (objectUrl) {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
}
|
||||||
|
isUnmounted = true;
|
||||||
|
render(null, container);
|
||||||
|
container.remove();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CropperWrapper = {
|
||||||
|
setup() {
|
||||||
|
return () => {
|
||||||
|
if (isUnmounted) return null;
|
||||||
|
if (!objectUrl) {
|
||||||
|
objectUrl = URL.createObjectURL(file);
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
Modal,
|
||||||
|
{
|
||||||
|
open: open.value,
|
||||||
|
title: h('div', {}, [
|
||||||
|
$t('ui.crop.title'),
|
||||||
|
h(
|
||||||
|
'span',
|
||||||
|
{
|
||||||
|
class: `${aspectRatio ? '' : 'hidden'} ml-2 text-sm text-gray-400 font-normal`,
|
||||||
|
},
|
||||||
|
$t('ui.crop.titleTip', [aspectRatio]),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
centered: true,
|
||||||
|
width: 548,
|
||||||
|
keyboard: false,
|
||||||
|
maskClosable: false,
|
||||||
|
closable: false,
|
||||||
|
cancelText: $t('common.cancel'),
|
||||||
|
okText: $t('ui.crop.confirm'),
|
||||||
|
destroyOnClose: true,
|
||||||
|
onOk: async () => {
|
||||||
|
const cropper = cropperRef.value;
|
||||||
|
if (!cropper) {
|
||||||
|
reject(new Error('Cropper not found'));
|
||||||
|
closeModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const dataUrl = await cropper.getCropImage();
|
||||||
|
if (dataUrl) {
|
||||||
|
resolve(dataUrl);
|
||||||
|
} else {
|
||||||
|
reject(new Error($t('ui.crop.errorTip')));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
reject(new Error($t('ui.crop.errorTip')));
|
||||||
|
} finally {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
resolve('');
|
||||||
|
closeModal();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
() =>
|
||||||
|
h(VCropper, {
|
||||||
|
ref: (ref: any) => (cropperRef.value = ref),
|
||||||
|
img: objectUrl as string,
|
||||||
|
aspectRatio,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
render(h(CropperWrapper), container);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带预览功能的上传组件
|
||||||
|
*/
|
||||||
|
const withPreviewUpload = () => {
|
||||||
|
return defineComponent({
|
||||||
|
name: Upload.name,
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
setup(
|
||||||
|
props: any,
|
||||||
|
{ attrs, slots, emit }: { attrs: any; emit: any; slots: any },
|
||||||
|
) {
|
||||||
|
const previewVisible = ref<boolean>(false);
|
||||||
|
const placeholder = attrs?.placeholder || $t('ui.placeholder.upload');
|
||||||
|
const listType = attrs?.listType || attrs?.['list-type'] || 'text';
|
||||||
|
const fileList = ref<UploadProps['fileList']>(
|
||||||
|
attrs?.fileList || attrs?.['file-list'] || [],
|
||||||
|
);
|
||||||
|
|
||||||
|
const maxSize = computed(() => attrs?.maxSize ?? attrs?.['max-size']);
|
||||||
|
const aspectRatio = computed(
|
||||||
|
() => attrs?.aspectRatio ?? attrs?.['aspect-ratio'],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleBeforeUpload = async (
|
||||||
|
file: UploadFile,
|
||||||
|
originFileList: Array<File>,
|
||||||
|
) => {
|
||||||
|
// 文件大小限制
|
||||||
|
if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) {
|
||||||
|
message.error($t('ui.formRules.sizeLimit', [maxSize.value]));
|
||||||
|
file.status = 'removed';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片裁剪处理
|
||||||
|
if (
|
||||||
|
attrs.crop &&
|
||||||
|
!attrs.multiple &&
|
||||||
|
originFileList[0] &&
|
||||||
|
isImageFile(file)
|
||||||
|
) {
|
||||||
|
file.status = 'removed';
|
||||||
|
const blob = await cropImage(originFileList[0], aspectRatio.value);
|
||||||
|
if (!blob) {
|
||||||
|
throw new Error($t('ui.crop.errorTip'));
|
||||||
|
}
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs.beforeUpload?.(file) ?? true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (event: UploadChangeParam) => {
|
||||||
|
try {
|
||||||
|
attrs.handleChange?.(event);
|
||||||
|
attrs.onHandleChange?.(event);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
fileList.value = event.fileList.filter(
|
||||||
|
(file) => file.status !== 'removed',
|
||||||
|
);
|
||||||
|
emit(
|
||||||
|
'update:modelValue',
|
||||||
|
event.fileList?.length ? fileList.value : undefined,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePreview = async (file: UploadFile) => {
|
||||||
|
previewVisible.value = true;
|
||||||
|
await previewImage(file, previewVisible, fileList);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderUploadButton = () => {
|
||||||
|
if (attrs.disabled) return null;
|
||||||
|
return isEmpty(slots)
|
||||||
|
? createDefaultUploadSlots(listType, placeholder)
|
||||||
|
: slots;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 拖拽排序
|
||||||
|
const draggable = computed(
|
||||||
|
() => (attrs.draggable ?? false) && !attrs.disabled,
|
||||||
|
);
|
||||||
|
const uploadId = `upload-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
||||||
|
const sortableInstance = ref<null | Sortable>(null);
|
||||||
|
|
||||||
|
const styleId = `upload-drag-style-${uploadId}`;
|
||||||
|
|
||||||
|
function injectDragStyle() {
|
||||||
|
if (!document.querySelector(`[id="${styleId}"]`)) {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = styleId;
|
||||||
|
style.textContent = `
|
||||||
|
[data-upload-id="${uploadId}"] .ant-upload-list-item { cursor: move; }
|
||||||
|
[data-upload-id="${uploadId}"] .ant-upload-list-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.15); }
|
||||||
|
`;
|
||||||
|
document.head.append(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeDragStyle() {
|
||||||
|
document.querySelector(`[id="${styleId}"]`)?.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initSortable(retryCount = 0) {
|
||||||
|
if (!draggable.value) return;
|
||||||
|
|
||||||
|
injectDragStyle();
|
||||||
|
await nextTick();
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
const container = document.querySelector(
|
||||||
|
`[data-upload-id="${uploadId}"] .ant-upload-list`,
|
||||||
|
) as HTMLElement;
|
||||||
|
|
||||||
|
if (!container) {
|
||||||
|
if (retryCount < 5) {
|
||||||
|
setTimeout(() => initSortable(retryCount + 1), 200);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { initializeSortable } = useSortable(container, {
|
||||||
|
animation: 300,
|
||||||
|
delay: 400,
|
||||||
|
delayOnTouchOnly: true,
|
||||||
|
filter:
|
||||||
|
'.ant-upload-select, .ant-upload-list-item-error, .ant-upload-list-item-uploading',
|
||||||
|
onEnd: (evt) => {
|
||||||
|
const { oldIndex, newIndex } = evt;
|
||||||
|
if (
|
||||||
|
oldIndex === undefined ||
|
||||||
|
newIndex === undefined ||
|
||||||
|
oldIndex === newIndex
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = [...(fileList.value || [])];
|
||||||
|
const [movedItem] = list.splice(oldIndex, 1);
|
||||||
|
if (movedItem) {
|
||||||
|
list.splice(newIndex, 0, movedItem);
|
||||||
|
fileList.value = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs.onDragSort?.(oldIndex, newIndex);
|
||||||
|
emit('update:modelValue', fileList.value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
sortableInstance.value = await initializeSortable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听表单值变化
|
||||||
|
watch(
|
||||||
|
() => attrs.modelValue,
|
||||||
|
(res) => {
|
||||||
|
fileList.value = res;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(initSortable);
|
||||||
|
onUnmounted(() => {
|
||||||
|
sortableInstance.value?.destroy();
|
||||||
|
removeDragStyle();
|
||||||
|
});
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{ 'data-upload-id': uploadId, class: 'w-full' },
|
||||||
|
h(
|
||||||
|
Upload,
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
fileList: fileList.value,
|
||||||
|
beforeUpload: handleBeforeUpload,
|
||||||
|
onChange: handleChange,
|
||||||
|
onPreview: handlePreview,
|
||||||
|
},
|
||||||
|
renderUploadButton() as any,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
|
|
@ -369,11 +635,45 @@ export type ComponentType =
|
||||||
| 'Upload'
|
| 'Upload'
|
||||||
| BaseFormComponentType;
|
| BaseFormComponentType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与 {@link ComponentType} 中注册的组件名一一对应,便于 Schema 上 `component` + `componentProps` 联动提示
|
||||||
|
*/
|
||||||
|
export interface ComponentPropsMap {
|
||||||
|
ApiCascader: ApiComponentSharedProps & CascaderProps;
|
||||||
|
ApiSelect: ApiComponentSharedProps & SelectProps;
|
||||||
|
ApiTreeSelect: ApiComponentSharedProps & TreeSelectProps;
|
||||||
|
AutoComplete: AutoCompleteProps;
|
||||||
|
Cascader: CascaderProps;
|
||||||
|
Checkbox: CheckboxProps;
|
||||||
|
CheckboxGroup: CheckboxGroupProps;
|
||||||
|
DatePicker: DatePickerProps;
|
||||||
|
DefaultButton: ButtonProps;
|
||||||
|
Divider: DividerProps;
|
||||||
|
IconPicker: IconPickerProps;
|
||||||
|
Input: InputProps;
|
||||||
|
InputNumber: InputNumberProps;
|
||||||
|
InputPassword: InputProps;
|
||||||
|
Mentions: MentionsProps;
|
||||||
|
PrimaryButton: ButtonProps;
|
||||||
|
Radio: RadioProps;
|
||||||
|
RadioGroup: RadioGroupProps;
|
||||||
|
RangePicker: RangePickerProps;
|
||||||
|
Rate: RateProps;
|
||||||
|
Select: SelectProps;
|
||||||
|
Space: SpaceProps;
|
||||||
|
Switch: SwitchProps;
|
||||||
|
Textarea: TextAreaProps;
|
||||||
|
TimePicker: TimePickerProps;
|
||||||
|
TreeSelect: TreeSelectProps;
|
||||||
|
Upload: AdapterUploadProps;
|
||||||
|
}
|
||||||
|
|
||||||
async function initComponentAdapter() {
|
async function initComponentAdapter() {
|
||||||
const components: Partial<Record<ComponentType, Component>> = {
|
const components: Partial<Record<ComponentType, Component>> = {
|
||||||
// 如果你的组件体积比较大,可以使用异步加载
|
// 如果你的组件体积比较大,可以使用异步加载
|
||||||
// Button: () =>
|
// Button: () =>
|
||||||
// import('xxx').then((res) => res.Button),
|
// import('xxx').then((res) => res.Button),
|
||||||
|
|
||||||
ApiCascader: withDefaultPlaceholder(ApiComponent, 'select', {
|
ApiCascader: withDefaultPlaceholder(ApiComponent, 'select', {
|
||||||
component: Cascader,
|
component: Cascader,
|
||||||
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||||
|
|
@ -381,34 +681,20 @@ async function initComponentAdapter() {
|
||||||
modelPropName: 'value',
|
modelPropName: 'value',
|
||||||
visibleEvent: 'onVisibleChange',
|
visibleEvent: 'onVisibleChange',
|
||||||
}),
|
}),
|
||||||
ApiSelect: withDefaultPlaceholder(
|
ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {
|
||||||
{
|
component: Select,
|
||||||
...ApiComponent,
|
loadingSlot: 'suffixIcon',
|
||||||
name: 'ApiSelect',
|
modelPropName: 'value',
|
||||||
},
|
visibleEvent: 'onVisibleChange',
|
||||||
'select',
|
}),
|
||||||
{
|
ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {
|
||||||
component: Select,
|
component: TreeSelect,
|
||||||
loadingSlot: 'suffixIcon',
|
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||||
visibleEvent: 'onDropdownVisibleChange',
|
loadingSlot: 'suffixIcon',
|
||||||
modelPropName: 'value',
|
modelPropName: 'value',
|
||||||
},
|
optionsPropName: 'treeData',
|
||||||
),
|
visibleEvent: 'onVisibleChange',
|
||||||
ApiTreeSelect: withDefaultPlaceholder(
|
}),
|
||||||
{
|
|
||||||
...ApiComponent,
|
|
||||||
name: 'ApiTreeSelect',
|
|
||||||
},
|
|
||||||
'select',
|
|
||||||
{
|
|
||||||
component: TreeSelect,
|
|
||||||
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
|
||||||
loadingSlot: 'suffixIcon',
|
|
||||||
modelPropName: 'value',
|
|
||||||
optionsPropName: 'treeData',
|
|
||||||
visibleEvent: 'onVisibleChange',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
AutoComplete,
|
AutoComplete,
|
||||||
Cascader,
|
Cascader,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import type {
|
import type {
|
||||||
|
VbenFormProps as FormProps,
|
||||||
VbenFormSchema as FormSchema,
|
VbenFormSchema as FormSchema,
|
||||||
VbenFormProps,
|
|
||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
import type { ComponentType } from './component';
|
import type { ComponentPropsMap, ComponentType } from './component';
|
||||||
|
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
@ -61,9 +61,9 @@ async function initSetupVbenForm() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const useVbenForm = useForm<ComponentType>;
|
const useVbenForm = useForm<ComponentType, ComponentPropsMap>;
|
||||||
|
|
||||||
export { initSetupVbenForm, useVbenForm, z };
|
export { initSetupVbenForm, useVbenForm, z };
|
||||||
|
|
||||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
export type VbenFormSchema = FormSchema<ComponentType, ComponentPropsMap>;
|
||||||
export type { VbenFormProps };
|
export type VbenFormProps = FormProps<ComponentType, ComponentPropsMap>;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import type { ComponentPropsMap, ComponentType } from './component';
|
||||||
|
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
@ -10,7 +12,7 @@ import {
|
||||||
AsyncVxeTable,
|
AsyncVxeTable,
|
||||||
createRequiredValidation,
|
createRequiredValidation,
|
||||||
setupVbenVxeTable,
|
setupVbenVxeTable,
|
||||||
useVbenVxeGrid,
|
useVbenVxeGrid as useGrid,
|
||||||
} from '@vben/plugins/vxe-table';
|
} from '@vben/plugins/vxe-table';
|
||||||
import {
|
import {
|
||||||
erpCountInputFormatter,
|
erpCountInputFormatter,
|
||||||
|
|
@ -199,7 +201,7 @@ setupVbenVxeTable({
|
||||||
vxeUI.renderer.add('CellOperation', {
|
vxeUI.renderer.add('CellOperation', {
|
||||||
renderTableDefault({ attrs, options, props }, { column, row }) {
|
renderTableDefault({ attrs, options, props }, { column, row }) {
|
||||||
const defaultProps = { size: 'small', type: 'link', ...props };
|
const defaultProps = { size: 'small', type: 'link', ...props };
|
||||||
let align = 'end';
|
let align: string;
|
||||||
switch (column.align) {
|
switch (column.align) {
|
||||||
case 'center': {
|
case 'center': {
|
||||||
align = 'center';
|
align = 'center';
|
||||||
|
|
@ -363,10 +365,13 @@ setupVbenVxeTable({
|
||||||
useVbenForm,
|
useVbenForm,
|
||||||
});
|
});
|
||||||
|
|
||||||
export { createRequiredValidation, useVbenVxeGrid };
|
export { createRequiredValidation };
|
||||||
|
|
||||||
export const [VxeTable, VxeColumn] = [AsyncVxeTable, AsyncVxeColumn];
|
export const [VxeTable, VxeColumn] = [AsyncVxeTable, AsyncVxeColumn];
|
||||||
|
|
||||||
export * from '#/components/table-action';
|
export * from '#/components/table-action';
|
||||||
|
export const useVbenVxeGrid = <T extends Record<string, any>>(
|
||||||
|
...rest: Parameters<typeof useGrid<T, ComponentType, ComponentPropsMap>>
|
||||||
|
) => useGrid<T, ComponentType, ComponentPropsMap>(...rest);
|
||||||
|
|
||||||
export type * from '@vben/plugins/vxe-table';
|
export type * from '@vben/plugins/vxe-table';
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,10 @@ export namespace CrmCustomerLimitConfigApi {
|
||||||
|
|
||||||
/** 客户限制配置类型 */
|
/** 客户限制配置类型 */
|
||||||
export enum LimitConfType {
|
export enum LimitConfType {
|
||||||
/** 锁定客户数限制 */
|
|
||||||
CUSTOMER_LOCK_LIMIT = 2,
|
|
||||||
/** 拥有客户数限制 */
|
/** 拥有客户数限制 */
|
||||||
CUSTOMER_QUANTITY_LIMIT = 1,
|
CUSTOMER_QUANTITY_LIMIT = 1,
|
||||||
|
/** 锁定客户数限制 */
|
||||||
|
CUSTOMER_LOCK_LIMIT = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查询客户限制配置列表 */
|
/** 查询客户限制配置列表 */
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,11 @@ export namespace CrmPermissionApi {
|
||||||
* CRM 业务类型枚举
|
* CRM 业务类型枚举
|
||||||
*/
|
*/
|
||||||
export enum BizTypeEnum {
|
export enum BizTypeEnum {
|
||||||
CRM_BUSINESS = 4, // 商机
|
|
||||||
CRM_CLUE = 1, // 线索
|
CRM_CLUE = 1, // 线索
|
||||||
CRM_CONTACT = 3, // 联系人
|
|
||||||
CRM_CONTRACT = 5, // 合同
|
|
||||||
CRM_CUSTOMER = 2, // 客户
|
CRM_CUSTOMER = 2, // 客户
|
||||||
|
CRM_CONTACT = 3, // 联系人
|
||||||
|
CRM_BUSINESS = 4, // 商机
|
||||||
|
CRM_CONTRACT = 5, // 合同
|
||||||
CRM_PRODUCT = 6, // 产品
|
CRM_PRODUCT = 6, // 产品
|
||||||
CRM_RECEIVABLE = 7, // 回款
|
CRM_RECEIVABLE = 7, // 回款
|
||||||
CRM_RECEIVABLE_PLAN = 8, // 回款计划
|
CRM_RECEIVABLE_PLAN = 8, // 回款计划
|
||||||
|
|
|
||||||
|
|
@ -3,39 +3,48 @@ import type { PageParam, PageResult } from '@vben/request';
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace IotDeviceApi {
|
export namespace IotDeviceApi {
|
||||||
// TODO @haohao:需要跟后端对齐,必要的 ReqVO、RespVO
|
|
||||||
/** 设备 */
|
/** 设备 */
|
||||||
export interface Device {
|
export interface Device {
|
||||||
id?: number; // 设备 ID,主键,自增
|
id?: number; // 设备编号
|
||||||
deviceName: string; // 设备名称
|
deviceName: string; // 设备名称
|
||||||
|
nickname?: string; // 备注名称
|
||||||
|
serialNumber?: string; // 设备序列号
|
||||||
|
picUrl?: string; // 设备图片
|
||||||
|
groupIds?: number[]; // 设备分组编号数组
|
||||||
productId: number; // 产品编号
|
productId: number; // 产品编号
|
||||||
productKey?: string; // 产品标识
|
productKey?: string; // 产品标识
|
||||||
|
productName?: string; // 产品名称(只有部分接口返回,例如 getDeviceLocationList)
|
||||||
deviceType?: number; // 设备类型
|
deviceType?: number; // 设备类型
|
||||||
nickname?: string; // 设备备注名称
|
|
||||||
gatewayId?: number; // 网关设备 ID
|
gatewayId?: number; // 网关设备 ID
|
||||||
state?: number; // 设备状态
|
state?: number; // 设备状态
|
||||||
status?: number; // 设备状态(兼容字段)
|
|
||||||
onlineTime?: Date; // 最后上线时间
|
onlineTime?: Date; // 最后上线时间
|
||||||
offlineTime?: Date; // 最后离线时间
|
offlineTime?: Date; // 最后离线时间
|
||||||
activeTime?: Date; // 设备激活时间
|
activeTime?: Date; // 设备激活时间
|
||||||
createTime?: Date; // 创建时间
|
deviceSecret?: string; // 设备密钥,用于设备认证
|
||||||
ip?: string; // 设备的 IP 地址
|
config?: string; // 设备配置
|
||||||
firmwareVersion?: string; // 设备的固件版本
|
|
||||||
deviceSecret?: string; // 设备密钥,用于设备认证,需安全存储
|
|
||||||
mqttClientId?: string; // MQTT 客户端 ID
|
|
||||||
mqttUsername?: string; // MQTT 用户名
|
|
||||||
mqttPassword?: string; // MQTT 密码
|
|
||||||
authType?: string; // 认证类型
|
|
||||||
locationType?: number; // 定位类型
|
|
||||||
latitude?: number; // 设备位置的纬度
|
latitude?: number; // 设备位置的纬度
|
||||||
longitude?: number; // 设备位置的经度
|
longitude?: number; // 设备位置的经度
|
||||||
areaId?: number; // 地区编码
|
createTime?: Date; // 创建时间
|
||||||
address?: string; // 设备详细地址
|
}
|
||||||
serialNumber?: string; // 设备序列号
|
|
||||||
config?: string; // 设备配置
|
/** 设备更新分组 Request VO */
|
||||||
groupIds?: number[]; // 添加分组 ID
|
export interface DeviceUpdateGroupReqVO {
|
||||||
picUrl?: string; // 设备图片
|
ids: number[]; // 设备编号列表(必填)
|
||||||
location?: string; // 位置信息(格式:经度,纬度)
|
groupIds: number[]; // 分组编号列表(必填)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 设备认证信息 Response VO */
|
||||||
|
export interface DeviceAuthInfoRespVO {
|
||||||
|
clientId: string; // 客户端 ID
|
||||||
|
username: string; // 用户名
|
||||||
|
password: string; // 密码
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 设备导入 Response VO */
|
||||||
|
export interface DeviceImportRespVO {
|
||||||
|
createDeviceNames?: string[]; // 创建成功的设备名称列表
|
||||||
|
updateDeviceNames?: string[]; // 更新成功的设备名称列表
|
||||||
|
failureDeviceNames?: Record<string, string>; // 失败的设备名称及原因
|
||||||
}
|
}
|
||||||
|
|
||||||
/** IoT 设备属性详细 VO */
|
/** IoT 设备属性详细 VO */
|
||||||
|
|
@ -56,25 +65,12 @@ export namespace IotDeviceApi {
|
||||||
updateTime: Date; // 更新时间
|
updateTime: Date; // 更新时间
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设备认证参数 VO */
|
|
||||||
export interface DeviceAuthInfo {
|
|
||||||
clientId: string; // 客户端 ID
|
|
||||||
username: string; // 用户名
|
|
||||||
password: string; // 密码
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 设备发送消息 Request VO */
|
/** 设备发送消息 Request VO */
|
||||||
export interface DeviceMessageSendReq {
|
export interface DeviceMessageSendReq {
|
||||||
deviceId: number; // 设备编号
|
deviceId: number; // 设备编号
|
||||||
method: string; // 请求方法
|
method: string; // 请求方法
|
||||||
params?: any; // 请求参数
|
params?: any; // 请求参数
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设备分组更新请求 */
|
|
||||||
export interface DeviceGroupUpdateReq {
|
|
||||||
ids: number[]; // 设备 ID 列表
|
|
||||||
groupIds: number[]; // 分组 ID 列表
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查询设备分页 */
|
/** 查询设备分页 */
|
||||||
|
|
@ -92,33 +88,33 @@ export function getDevice(id: number) {
|
||||||
|
|
||||||
/** 新增设备 */
|
/** 新增设备 */
|
||||||
export function createDevice(data: IotDeviceApi.Device) {
|
export function createDevice(data: IotDeviceApi.Device) {
|
||||||
return requestClient.post('/iot/device/create', data);
|
return requestClient.post<number>('/iot/device/create', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改设备 */
|
/** 修改设备 */
|
||||||
export function updateDevice(data: IotDeviceApi.Device) {
|
export function updateDevice(data: IotDeviceApi.Device) {
|
||||||
return requestClient.put('/iot/device/update', data);
|
return requestClient.put<boolean>('/iot/device/update', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改设备分组 */
|
/** 修改设备分组 */
|
||||||
export function updateDeviceGroup(data: IotDeviceApi.DeviceGroupUpdateReq) {
|
export function updateDeviceGroup(data: IotDeviceApi.DeviceUpdateGroupReqVO) {
|
||||||
return requestClient.put('/iot/device/update-group', data);
|
return requestClient.put<boolean>('/iot/device/update-group', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除单个设备 */
|
/** 删除单个设备 */
|
||||||
export function deleteDevice(id: number) {
|
export function deleteDevice(id: number) {
|
||||||
return requestClient.delete(`/iot/device/delete?id=${id}`);
|
return requestClient.delete<boolean>(`/iot/device/delete?id=${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除多个设备 */
|
/** 删除多个设备 */
|
||||||
export function deleteDeviceList(ids: number[]) {
|
export function deleteDeviceList(ids: number[]) {
|
||||||
return requestClient.delete('/iot/device/delete-list', {
|
return requestClient.delete<boolean>('/iot/device/delete-list', {
|
||||||
params: { ids: ids.join(',') },
|
params: { ids: ids.join(',') },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 导出设备 */
|
/** 导出设备 */
|
||||||
export function exportDeviceExcel(params: any) {
|
export function exportDeviceExcel(params: PageParam) {
|
||||||
return requestClient.download('/iot/device/export-excel', { params });
|
return requestClient.download('/iot/device/export-excel', { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,6 +137,11 @@ export function getDeviceListByProductId(productId: number) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 获取设备位置列表(用于地图展示) */
|
||||||
|
export function getDeviceLocationList() {
|
||||||
|
return requestClient.get<IotDeviceApi.Device[]>('/iot/device/location-list');
|
||||||
|
}
|
||||||
|
|
||||||
/** 获取导入模板 */
|
/** 获取导入模板 */
|
||||||
export function importDeviceTemplate() {
|
export function importDeviceTemplate() {
|
||||||
return requestClient.download('/iot/device/get-import-template');
|
return requestClient.download('/iot/device/get-import-template');
|
||||||
|
|
@ -148,10 +149,13 @@ export function importDeviceTemplate() {
|
||||||
|
|
||||||
/** 导入设备 */
|
/** 导入设备 */
|
||||||
export function importDevice(file: File, updateSupport: boolean) {
|
export function importDevice(file: File, updateSupport: boolean) {
|
||||||
return requestClient.upload('/iot/device/import', {
|
return requestClient.upload<IotDeviceApi.DeviceImportRespVO>(
|
||||||
file,
|
'/iot/device/import',
|
||||||
updateSupport,
|
{
|
||||||
});
|
file,
|
||||||
|
updateSupport,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取设备属性最新数据 */
|
/** 获取设备属性最新数据 */
|
||||||
|
|
@ -172,7 +176,7 @@ export function getHistoryDevicePropertyList(params: any) {
|
||||||
|
|
||||||
/** 获取设备认证信息 */
|
/** 获取设备认证信息 */
|
||||||
export function getDeviceAuthInfo(id: number) {
|
export function getDeviceAuthInfo(id: number) {
|
||||||
return requestClient.get<IotDeviceApi.DeviceAuthInfo>(
|
return requestClient.get<IotDeviceApi.DeviceAuthInfoRespVO>(
|
||||||
'/iot/device/get-auth-info',
|
'/iot/device/get-auth-info',
|
||||||
{ params: { id } },
|
{ params: { id } },
|
||||||
);
|
);
|
||||||
|
|
@ -196,3 +200,35 @@ export function getDeviceMessagePairPage(params: PageParam) {
|
||||||
export function sendDeviceMessage(params: IotDeviceApi.DeviceMessageSendReq) {
|
export function sendDeviceMessage(params: IotDeviceApi.DeviceMessageSendReq) {
|
||||||
return requestClient.post('/iot/device/message/send', params);
|
return requestClient.post('/iot/device/message/send', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 绑定子设备到网关设备 */
|
||||||
|
export function bindDeviceGateway(gatewayId: number, subIds: number[]) {
|
||||||
|
return requestClient.put<boolean>('/iot/device/bind-gateway', {
|
||||||
|
gatewayId,
|
||||||
|
subIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解绑子设备与网关设备 */
|
||||||
|
export function unbindDeviceGateway(gatewayId: number, subIds: number[]) {
|
||||||
|
return requestClient.put<boolean>('/iot/device/unbind-gateway', {
|
||||||
|
gatewayId,
|
||||||
|
subIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取网关设备的子设备列表 */
|
||||||
|
export function getSubDeviceList(gatewayId: number) {
|
||||||
|
return requestClient.get<IotDeviceApi.Device[]>(
|
||||||
|
'/iot/device/sub-device-list',
|
||||||
|
{ params: { gatewayId } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取未绑定的子设备分页 */
|
||||||
|
export function getUnboundSubDevicePage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<IotDeviceApi.Device>>(
|
||||||
|
'/iot/device/unbound-sub-device-page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace IotDeviceModbusConfigApi {
|
||||||
|
/** Modbus 连接配置 VO */
|
||||||
|
export interface ModbusConfig {
|
||||||
|
id?: number; // 主键
|
||||||
|
deviceId: number; // 设备编号
|
||||||
|
ip: string; // Modbus 服务器 IP 地址
|
||||||
|
port: number; // Modbus 服务器端口
|
||||||
|
slaveId: number; // 从站地址
|
||||||
|
timeout: number; // 连接超时时间,单位:毫秒
|
||||||
|
retryInterval: number; // 重试间隔,单位:毫秒
|
||||||
|
mode: number; // 模式
|
||||||
|
frameFormat: number; // 帧格式
|
||||||
|
status: number; // 状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取设备的 Modbus 连接配置 */
|
||||||
|
export function getModbusConfig(deviceId: number) {
|
||||||
|
return requestClient.get<IotDeviceModbusConfigApi.ModbusConfig>(
|
||||||
|
'/iot/device-modbus-config/get',
|
||||||
|
{ params: { deviceId } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存 Modbus 连接配置 */
|
||||||
|
export function saveModbusConfig(data: IotDeviceModbusConfigApi.ModbusConfig) {
|
||||||
|
return requestClient.post('/iot/device-modbus-config/save', data);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
import type { PageParam, PageResult } from '@vben/request';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace IotDeviceModbusPointApi {
|
||||||
|
/** Modbus 点位配置 VO */
|
||||||
|
export interface ModbusPoint {
|
||||||
|
id?: number; // 主键
|
||||||
|
deviceId: number; // 设备编号
|
||||||
|
thingModelId?: number; // 物模型属性编号
|
||||||
|
identifier: string; // 属性标识符
|
||||||
|
name: string; // 属性名称
|
||||||
|
functionCode?: number; // Modbus 功能码
|
||||||
|
registerAddress?: number; // 寄存器起始地址
|
||||||
|
registerCount?: number; // 寄存器数量
|
||||||
|
byteOrder?: string; // 字节序
|
||||||
|
rawDataType?: string; // 原始数据类型
|
||||||
|
scale: number; // 缩放因子
|
||||||
|
pollInterval: number; // 轮询间隔,单位:毫秒
|
||||||
|
status: number; // 状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取设备的 Modbus 点位分页 */
|
||||||
|
export function getModbusPointPage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<IotDeviceModbusPointApi.ModbusPoint>>(
|
||||||
|
'/iot/device-modbus-point/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取 Modbus 点位详情 */
|
||||||
|
export function getModbusPoint(id: number) {
|
||||||
|
return requestClient.get<IotDeviceModbusPointApi.ModbusPoint>(
|
||||||
|
`/iot/device-modbus-point/get?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建 Modbus 点位配置 */
|
||||||
|
export function createModbusPoint(data: IotDeviceModbusPointApi.ModbusPoint) {
|
||||||
|
return requestClient.post('/iot/device-modbus-point/create', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新 Modbus 点位配置 */
|
||||||
|
export function updateModbusPoint(data: IotDeviceModbusPointApi.ModbusPoint) {
|
||||||
|
return requestClient.put('/iot/device-modbus-point/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除 Modbus 点位配置 */
|
||||||
|
export function deleteModbusPoint(id: number) {
|
||||||
|
return requestClient.delete(`/iot/device-modbus-point/delete?id=${id}`);
|
||||||
|
}
|
||||||
|
|
@ -8,8 +8,9 @@ export namespace IotProductApi {
|
||||||
id?: number; // 产品编号
|
id?: number; // 产品编号
|
||||||
name: string; // 产品名称
|
name: string; // 产品名称
|
||||||
productKey?: string; // 产品标识
|
productKey?: string; // 产品标识
|
||||||
|
productSecret?: string; // 产品密钥
|
||||||
protocolId?: number; // 协议编号
|
protocolId?: number; // 协议编号
|
||||||
protocolType?: number; // 接入协议类型
|
protocolType?: string; // 协议类型
|
||||||
categoryId?: number; // 产品所属品类标识符
|
categoryId?: number; // 产品所属品类标识符
|
||||||
categoryName?: string; // 产品所属品类名称
|
categoryName?: string; // 产品所属品类名称
|
||||||
icon?: string; // 产品图标
|
icon?: string; // 产品图标
|
||||||
|
|
@ -17,16 +18,35 @@ export namespace IotProductApi {
|
||||||
description?: string; // 产品描述
|
description?: string; // 产品描述
|
||||||
status?: number; // 产品状态
|
status?: number; // 产品状态
|
||||||
deviceType?: number; // 设备类型
|
deviceType?: number; // 设备类型
|
||||||
locationType?: number; // 定位类型
|
|
||||||
netType?: number; // 联网方式
|
netType?: number; // 联网方式
|
||||||
codecType?: string; // 数据格式(编解码器类型)
|
serializeType?: string; // 序列化类型
|
||||||
dataFormat?: number; // 数据格式
|
dataFormat?: number; // 数据格式
|
||||||
validateType?: number; // 认证方式
|
validateType?: number; // 认证方式
|
||||||
|
registerEnabled?: boolean; // 是否开启动态注册
|
||||||
deviceCount?: number; // 设备数量
|
deviceCount?: number; // 设备数量
|
||||||
createTime?: Date; // 创建时间
|
createTime?: Date; // 创建时间
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IoT 协议类型枚举
|
||||||
|
export enum ProtocolTypeEnum {
|
||||||
|
COAP = 'coap',
|
||||||
|
EMQX = 'emqx',
|
||||||
|
HTTP = 'http',
|
||||||
|
MODBUS_TCP_CLIENT = 'modbus_tcp_client',
|
||||||
|
MODBUS_TCP_SERVER = 'modbus_tcp_server',
|
||||||
|
MQTT = 'mqtt',
|
||||||
|
TCP = 'tcp',
|
||||||
|
UDP = 'udp',
|
||||||
|
WEBSOCKET = 'websocket',
|
||||||
|
}
|
||||||
|
|
||||||
|
// IoT 序列化类型枚举
|
||||||
|
export enum SerializeTypeEnum {
|
||||||
|
BINARY = 'binary',
|
||||||
|
JSON = 'json',
|
||||||
|
}
|
||||||
|
|
||||||
/** 查询产品分页 */
|
/** 查询产品分页 */
|
||||||
export function getProductPage(params: PageParam) {
|
export function getProductPage(params: PageParam) {
|
||||||
return requestClient.get<PageResult<IotProductApi.Product>>(
|
return requestClient.get<PageResult<IotProductApi.Product>>(
|
||||||
|
|
@ -68,8 +88,13 @@ export function updateProductStatus(id: number, status: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查询产品(精简)列表 */
|
/** 查询产品(精简)列表 */
|
||||||
export function getSimpleProductList() {
|
export function getSimpleProductList(deviceType?: number) {
|
||||||
return requestClient.get<IotProductApi.Product[]>('/iot/product/simple-list');
|
return requestClient.get<IotProductApi.Product[]>(
|
||||||
|
'/iot/product/simple-list',
|
||||||
|
{
|
||||||
|
params: { deviceType },
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 根据 ProductKey 获取产品信息 */
|
/** 根据 ProductKey 获取产品信息 */
|
||||||
|
|
|
||||||
|
|
@ -119,13 +119,58 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
response.data = apiEncrypt.decryptResponse(response.data);
|
response.data = apiEncrypt.decryptResponse(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('响应数据解密失败:', error);
|
console.error('响应数据解密失败:', error);
|
||||||
throw new Error(`响应数据解密失败: ${(error as Error).message}`);
|
throw new Error(`响应数据解密失败: ${(error as Error).message}`, {
|
||||||
|
cause: error,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// add by 芋艿:对应 https://t.zsxq.com/SHqWw 反馈
|
||||||
|
// 处理 Blob 响应中的业务错误(如 401):后端把「账号未登录」包成 HTTP 200 + body {code: 401, msg: ...},
|
||||||
|
// download 强制 responseType: 'blob' 后被 axios 包成 application/json 的 Blob,defaultResponseInterceptor 走
|
||||||
|
// responseReturn === 'body' 分支直接返回,绕过了 authenticateResponseInterceptor 的 401 token 刷新;
|
||||||
|
// 这里把这种 Blob 解析回 JSON,再以 axios 风格抛出,让后续拦截器接管
|
||||||
|
client.addResponseInterceptor({
|
||||||
|
fulfilled: async (response) => {
|
||||||
|
const blob = response.data;
|
||||||
|
if (!(blob instanceof Blob)) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
// Blob.type 在部分环境可能为空或大小写不一,叠加 response header 一起判断更稳
|
||||||
|
const blobType = (blob.type || '').toLowerCase();
|
||||||
|
const headerType = String(
|
||||||
|
response.headers?.['content-type'] ??
|
||||||
|
response.headers?.['Content-Type'] ??
|
||||||
|
'',
|
||||||
|
).toLowerCase();
|
||||||
|
if (
|
||||||
|
!blobType.includes('application/json') &&
|
||||||
|
!headerType.includes('application/json')
|
||||||
|
) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
let parsed: any;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(await blob.text());
|
||||||
|
} catch {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
if (parsed && parsed.code !== undefined && parsed.code !== 0) {
|
||||||
|
response.data = parsed;
|
||||||
|
throw Object.assign(new Error(parsed.msg ?? 'Request failed'), {
|
||||||
|
config: response.config,
|
||||||
|
response,
|
||||||
|
data: parsed,
|
||||||
|
isAxiosError: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// 处理返回的响应数据格式
|
// 处理返回的响应数据格式
|
||||||
client.addResponseInterceptor(
|
client.addResponseInterceptor(
|
||||||
defaultResponseInterceptor({
|
defaultResponseInterceptor({
|
||||||
|
|
|
||||||
|
|
@ -41,16 +41,8 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
onConfirm: handleOk,
|
onConfirm: handleOk,
|
||||||
onOpenChange(isOpen) {
|
onOpenChange(isOpen) {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
// 打开时,进行 loading 加载。后续 CropperImage 组件加载完毕,会自动关闭 loading(通过 handleReady)
|
// 只有存在可加载图片时才显示 loading,避免空图或异常链接导致一直 loading
|
||||||
modalLoading(true);
|
modalLoading(!!src.value);
|
||||||
const img = new Image();
|
|
||||||
img.src = src.value;
|
|
||||||
img.addEventListener('load', () => {
|
|
||||||
modalLoading(false);
|
|
||||||
});
|
|
||||||
img.addEventListener('error', () => {
|
|
||||||
modalLoading(false);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// 关闭时,清空右侧预览
|
// 关闭时,清空右侧预览
|
||||||
previewSource.value = '';
|
previewSource.value = '';
|
||||||
|
|
@ -73,10 +65,14 @@ function handleBeforeUpload(file: File) {
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
src.value = '';
|
src.value = '';
|
||||||
previewSource.value = '';
|
previewSource.value = '';
|
||||||
|
modalLoading(true);
|
||||||
reader.addEventListener('load', (e) => {
|
reader.addEventListener('load', (e) => {
|
||||||
src.value = (e.target?.result as string) ?? '';
|
src.value = (e.target?.result as string) ?? '';
|
||||||
filename = file.name;
|
filename = file.name;
|
||||||
});
|
});
|
||||||
|
reader.addEventListener('error', () => {
|
||||||
|
modalLoading(false);
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,6 +86,10 @@ function handleReady(cropperInstance: CropperType) {
|
||||||
modalLoading(false);
|
modalLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleCropperError() {
|
||||||
|
modalLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
function handlerToolbar(event: string, arg?: number) {
|
function handlerToolbar(event: string, arg?: number) {
|
||||||
if (event === 'scaleX') {
|
if (event === 'scaleX') {
|
||||||
scaleX = arg = scaleX === -1 ? 1 : -1;
|
scaleX = arg = scaleX === -1 ? 1 : -1;
|
||||||
|
|
@ -141,6 +141,7 @@ async function handleOk() {
|
||||||
:src="src"
|
:src="src"
|
||||||
height="300px"
|
height="300px"
|
||||||
@cropend="handleCropend"
|
@cropend="handleCropend"
|
||||||
|
@cropend-error="handleCropperError"
|
||||||
@ready="handleReady"
|
@ready="handleReady"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,10 @@ function getRoundedCanvas() {
|
||||||
context.fill();
|
context.fill();
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleImageError() {
|
||||||
|
emit('cropendError');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -154,6 +158,7 @@ function getRoundedCanvas() {
|
||||||
:crossorigin="crossorigin"
|
:crossorigin="crossorigin"
|
||||||
:src="src"
|
:src="src"
|
||||||
:style="getImageStyle"
|
:style="getImageStyle"
|
||||||
|
@error="handleImageError"
|
||||||
class="h-auto max-w-full"
|
class="h-auto max-w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export function useDescription(options?: Partial<DescriptionProps>) {
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
setup(_props, { attrs, slots }) {
|
setup(_props, { attrs, slots }) {
|
||||||
return () => {
|
return () => {
|
||||||
// @ts-ignore - 避免类型实例化过深
|
// @ts-expect-error - 避免类型实例化过深
|
||||||
return h(Description, { ...propsState, ...attrs }, slots);
|
return h(Description, { ...propsState, ...attrs }, slots);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
<!-- 省市区选择器 (Ant Design Vue 版本) -->
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { AreaLevelEnum } from '@vben/constants';
|
||||||
|
|
||||||
|
import { Cascader } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getAreaTree } from '#/api/system/area';
|
||||||
|
|
||||||
|
defineOptions({ name: 'AreaSelect' });
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
modelValue: undefined,
|
||||||
|
value: undefined,
|
||||||
|
level: AreaLevelEnum.DISTRICT,
|
||||||
|
disabled: false,
|
||||||
|
placeholder: '请选择省市区',
|
||||||
|
clearable: true,
|
||||||
|
showAllLevels: true,
|
||||||
|
separator: '/',
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:modelValue', value: number[] | string[] | undefined): void;
|
||||||
|
(e: 'update:value', value: number[] | string[] | undefined): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// 地区数据接口
|
||||||
|
interface AreaVO {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
code: string;
|
||||||
|
parentId?: number;
|
||||||
|
sort?: number;
|
||||||
|
status?: number;
|
||||||
|
children?: AreaVO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接受父组件参数
|
||||||
|
interface Props {
|
||||||
|
modelValue?: number[] | string[];
|
||||||
|
value?: number[] | string[];
|
||||||
|
level?: (typeof AreaLevelEnum)[keyof typeof AreaLevelEnum];
|
||||||
|
disabled?: boolean;
|
||||||
|
placeholder?: string;
|
||||||
|
clearable?: boolean;
|
||||||
|
showAllLevels?: boolean;
|
||||||
|
separator?: string;
|
||||||
|
// eslint-disable-next-line vue/require-default-prop
|
||||||
|
formCreateInject?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ant Design Vue Cascader 的 fieldNames 配置
|
||||||
|
const fieldNames = {
|
||||||
|
label: 'name',
|
||||||
|
value: 'id',
|
||||||
|
children: 'children',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 地区树形数据
|
||||||
|
const areaTree = ref<AreaVO[]>([]);
|
||||||
|
// 当前选中值
|
||||||
|
const selectedValue = ref<number[] | undefined>();
|
||||||
|
// 加载状态
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
// 加载地区树形数据
|
||||||
|
async function loadAreaTree(): Promise<void> {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const data = await getAreaTree();
|
||||||
|
|
||||||
|
// 根据 level 限制层级
|
||||||
|
areaTree.value = filterTreeByLevel((data || []) as AreaVO[], props.level);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[AreaSelect] 加载地区数据失败:', error);
|
||||||
|
areaTree.value = [];
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据层级过滤树形数据
|
||||||
|
function filterTreeByLevel(tree: AreaVO[], maxLevel: number): AreaVO[] {
|
||||||
|
if (maxLevel <= 0) return [];
|
||||||
|
|
||||||
|
return tree.map((node) => {
|
||||||
|
const newNode = { ...node };
|
||||||
|
|
||||||
|
// 如果当前是最后一层,移除 children
|
||||||
|
if (maxLevel === 1) {
|
||||||
|
delete newNode.children;
|
||||||
|
} else if (node.children && node.children.length > 0) {
|
||||||
|
// 递归处理子节点
|
||||||
|
newNode.children = filterTreeByLevel(node.children, maxLevel - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNode;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理选中值变化
|
||||||
|
function handleChange(value: any): void {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
emit('update:modelValue', undefined);
|
||||||
|
emit('update:value', undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update:modelValue', value);
|
||||||
|
emit('update:value', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步 modelValue 或 value 到内部选中值
|
||||||
|
function syncSelectedValue(): void {
|
||||||
|
const newValue = props.modelValue || props.value;
|
||||||
|
|
||||||
|
if (newValue === undefined || newValue === null) {
|
||||||
|
selectedValue.value = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保是数组格式
|
||||||
|
selectedValue.value = Array.isArray(newValue)
|
||||||
|
? (newValue as number[])
|
||||||
|
: [newValue as number];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听 modelValue 和 value 变化
|
||||||
|
watch(() => props.modelValue || props.value, syncSelectedValue, {
|
||||||
|
immediate: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组件挂载时加载数据
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadAreaTree();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Cascader
|
||||||
|
v-model:value="selectedValue"
|
||||||
|
class="w-full"
|
||||||
|
:options="areaTree"
|
||||||
|
:field-names="fieldNames"
|
||||||
|
:disabled="disabled"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:allow-clear="clearable"
|
||||||
|
:show-search="true"
|
||||||
|
:change-on-select="true"
|
||||||
|
:loading="loading"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
<!-- 部门选择器 - 树形结构显示 -->
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { useUserStore } from '@vben/stores';
|
||||||
|
import { handleTree } from '@vben/utils';
|
||||||
|
|
||||||
|
import { TreeSelect } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
defineOptions({ name: 'DeptSelect' });
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
multiple: false,
|
||||||
|
returnType: 'id',
|
||||||
|
defaultCurrentDept: false,
|
||||||
|
disabled: false,
|
||||||
|
placeholder: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(
|
||||||
|
e: 'update:modelValue',
|
||||||
|
value: number | number[] | string | string[] | undefined,
|
||||||
|
): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// todo @puhui999:是不是可以简化,使用 api 的;
|
||||||
|
/** 部门数据接口 */
|
||||||
|
interface DeptVO {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
parentId: number;
|
||||||
|
sort?: number;
|
||||||
|
leaderUserId?: number;
|
||||||
|
phone?: string;
|
||||||
|
email?: string;
|
||||||
|
status?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 接受父组件参数 */
|
||||||
|
interface Props {
|
||||||
|
// eslint-disable-next-line vue/require-default-prop
|
||||||
|
modelValue?: number | number[] | string | string[];
|
||||||
|
multiple?: boolean;
|
||||||
|
returnType?: 'id' | 'name';
|
||||||
|
defaultCurrentDept?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
placeholder?: string;
|
||||||
|
// eslint-disable-next-line vue/require-default-prop
|
||||||
|
formCreateInject?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deptTree = ref<any[]>([]); // 部门树形数据
|
||||||
|
const deptList = ref<DeptVO[]>([]); // 原始部门列表(用于 returnType='name' 时查找名称)
|
||||||
|
const selectedValue = ref<number | number[] | undefined>(); // 当前选中值
|
||||||
|
|
||||||
|
/** 加载部门树形数据 */
|
||||||
|
async function loadDeptTree(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const data = await requestClient.get<DeptVO[]>('/system/dept/simple-list');
|
||||||
|
deptList.value = data;
|
||||||
|
deptTree.value = handleTree(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[DeptSelect] 加载部门数据失败:', error);
|
||||||
|
deptTree.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据 ID 获取部门名称 */
|
||||||
|
function getDeptNameById(id: number): string | undefined {
|
||||||
|
const dept = deptList.value.find((item: DeptVO) => item.id === id);
|
||||||
|
if (!dept) {
|
||||||
|
console.warn(`[DeptSelect] 未找到 ID 为 ${id} 的部门`);
|
||||||
|
}
|
||||||
|
return dept?.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据名称获取部门 ID */
|
||||||
|
function getDeptIdByName(name: string): number | undefined {
|
||||||
|
const dept = deptList.value.find((item: DeptVO) => item.name === name);
|
||||||
|
return dept?.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理选中值变化 */
|
||||||
|
function handleChange(value: number | number[] | undefined): void {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
emit('update:modelValue', props.multiple ? [] : undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据 returnType 决定返回值类型
|
||||||
|
if (props.returnType === 'name') {
|
||||||
|
if (props.multiple && Array.isArray(value)) {
|
||||||
|
const names = value
|
||||||
|
.map((id) => getDeptNameById(id))
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
emit('update:modelValue', names);
|
||||||
|
} else if (!props.multiple && typeof value === 'number') {
|
||||||
|
const name = getDeptNameById(value);
|
||||||
|
emit('update:modelValue', name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emit('update:modelValue', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 树节点过滤方法(支持搜索过滤) */
|
||||||
|
function filterTreeNode(inputValue: string, treeNode: any): boolean {
|
||||||
|
if (!inputValue) return true;
|
||||||
|
return treeNode.name?.toLowerCase().includes(inputValue.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 同步 modelValue 到内部选中值 */
|
||||||
|
function syncSelectedValue(): void {
|
||||||
|
const newValue = props.modelValue;
|
||||||
|
if (newValue === undefined || newValue === null) {
|
||||||
|
selectedValue.value = props.multiple ? [] : undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 returnType 是 'name',需要将名称转换为 ID 用于树选择器显示
|
||||||
|
if (props.returnType === 'name') {
|
||||||
|
// 只有在 deptList 加载完成后才能进行转换
|
||||||
|
if (deptList.value.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.multiple && Array.isArray(newValue)) {
|
||||||
|
selectedValue.value = (newValue as string[])
|
||||||
|
.map((name) => getDeptIdByName(name))
|
||||||
|
.filter(Boolean) as number[];
|
||||||
|
} else if (!props.multiple && typeof newValue === 'string') {
|
||||||
|
selectedValue.value = getDeptIdByName(newValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedValue.value = newValue as number | number[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 监听 modelValue 变化,同步到内部选中值 */
|
||||||
|
watch(() => props.modelValue, syncSelectedValue, { immediate: true });
|
||||||
|
|
||||||
|
/** 监听 deptList 变化,重新同步选中值(解决数据加载完成后的回显问题) */
|
||||||
|
watch(() => deptList.value, syncSelectedValue);
|
||||||
|
|
||||||
|
/** 检查是否有有效的预设值 */
|
||||||
|
function hasValidPresetValue(): boolean {
|
||||||
|
const value = props.modelValue;
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.length > 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 设置默认值(当前用户部门) */
|
||||||
|
function setDefaultValue(): void {
|
||||||
|
// 仅当 defaultCurrentDept 为 true 时处理
|
||||||
|
if (!props.defaultCurrentDept) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 检查是否已有预设值(预设值优先级高于默认当前部门)
|
||||||
|
if (hasValidPresetValue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前用户的部门 ID
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const deptId = userStore.userInfo?.deptId as number | undefined;
|
||||||
|
// 处理 deptId 为空或 0 的边界情况
|
||||||
|
if (!deptId || deptId === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据多选模式决定默认值格式
|
||||||
|
const defaultValue = props.multiple ? [deptId] : deptId;
|
||||||
|
emit('update:modelValue', defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 组件挂载时加载数据并设置默认值 */
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadDeptTree();
|
||||||
|
// 数据加载完成后设置默认值
|
||||||
|
setDefaultValue();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TreeSelect
|
||||||
|
v-model:value="selectedValue"
|
||||||
|
class="w-full"
|
||||||
|
:tree-data="deptTree"
|
||||||
|
:field-names="{ label: 'name', value: 'id', children: 'children' }"
|
||||||
|
:multiple="multiple"
|
||||||
|
:disabled="disabled"
|
||||||
|
:placeholder="placeholder || '请选择部门'"
|
||||||
|
:tree-checkable="multiple"
|
||||||
|
:show-search="true"
|
||||||
|
:filter-tree-node="filterTreeNode"
|
||||||
|
:allow-clear="true"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
<!-- 网页 iframe 组件 (Ant Design Vue 版本) -->
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { isUrl } from '#/utils';
|
||||||
|
|
||||||
|
defineOptions({ name: 'IframeComponent' });
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
modelValue: '',
|
||||||
|
value: '',
|
||||||
|
url: '',
|
||||||
|
height: '500px',
|
||||||
|
width: '100%',
|
||||||
|
frameborder: '0',
|
||||||
|
allowfullscreen: true,
|
||||||
|
loading: 'lazy',
|
||||||
|
sandbox: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 接受父组件参数
|
||||||
|
interface Props {
|
||||||
|
modelValue?: string;
|
||||||
|
value?: string;
|
||||||
|
url?: string;
|
||||||
|
height?: string;
|
||||||
|
width?: string;
|
||||||
|
frameborder?: string;
|
||||||
|
allowfullscreen?: boolean;
|
||||||
|
loading?: 'eager' | 'lazy';
|
||||||
|
sandbox?: string;
|
||||||
|
// eslint-disable-next-line vue/require-default-prop
|
||||||
|
formCreateInject?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示的 URL(优先使用 url prop,其次使用 value 或 modelValue)
|
||||||
|
const displayUrl = computed(
|
||||||
|
() => props.url || props.value || props.modelValue || '',
|
||||||
|
);
|
||||||
|
|
||||||
|
// 是否显示预览
|
||||||
|
const showPreview = computed(() => {
|
||||||
|
return displayUrl.value && isUrl(displayUrl.value);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="iframe-component">
|
||||||
|
<!-- iframe 预览 -->
|
||||||
|
<div v-if="showPreview" class="iframe-preview">
|
||||||
|
<iframe
|
||||||
|
:src="displayUrl"
|
||||||
|
:width="width"
|
||||||
|
:height="height"
|
||||||
|
:frameborder="frameborder"
|
||||||
|
:allowfullscreen="allowfullscreen"
|
||||||
|
:loading="loading"
|
||||||
|
:sandbox="sandbox || undefined"
|
||||||
|
class="iframe-content"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 无 URL 或无效 URL 提示 -->
|
||||||
|
<div v-else class="iframe-placeholder">
|
||||||
|
<a-empty description="请在右侧属性面板配置 URL 地址" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.iframe-component {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframe-preview {
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframe-content {
|
||||||
|
display: block;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframe-placeholder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 200px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
border: 1px dashed #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -2,6 +2,7 @@ import type { ApiSelectProps } from '#/components/form-create/typing';
|
||||||
|
|
||||||
import { defineComponent, onMounted, ref, useAttrs } from 'vue';
|
import { defineComponent, onMounted, ref, useAttrs } from 'vue';
|
||||||
|
|
||||||
|
import { useUserStore } from '@vben/stores';
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
@ -74,12 +75,46 @@ export function useApiSelect(option: ApiSelectProps) {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'id',
|
default: 'id',
|
||||||
},
|
},
|
||||||
|
// 是否默认选中当前用户(仅用于 UserSelect)
|
||||||
|
defaultCurrentUser: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props, { emit }) {
|
||||||
const attrs = useAttrs();
|
const attrs = useAttrs();
|
||||||
const options = ref<any[]>([]); // 下拉数据
|
const options = ref<any[]>([]); // 下拉数据
|
||||||
const loading = ref(false); // 是否正在从远程获取数据
|
const loading = ref(false); // 是否正在从远程获取数据
|
||||||
const queryParam = ref<any>(); // 当前输入的值
|
const queryParam = ref<any>(); // 当前输入的值
|
||||||
|
|
||||||
|
// 检查是否有有效的预设值
|
||||||
|
function hasValidPresetValue(): boolean {
|
||||||
|
const value = attrs.modelValue;
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.length > 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置默认当前用户
|
||||||
|
function setDefaultCurrentUser(): void {
|
||||||
|
if (option.name !== 'UserSelect' || !props.defaultCurrentUser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasValidPresetValue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const currentUserId = userStore.userInfo?.id;
|
||||||
|
if (currentUserId) {
|
||||||
|
const defaultValue = props.multiple ? [currentUserId] : currentUserId;
|
||||||
|
emit('update:modelValue', defaultValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getOptions = async () => {
|
const getOptions = async () => {
|
||||||
options.value = [];
|
options.value = [];
|
||||||
// 接口选择器
|
// 接口选择器
|
||||||
|
|
@ -158,7 +193,8 @@ export function useApiSelect(option: ApiSelectProps) {
|
||||||
let parse: any = null;
|
let parse: any = null;
|
||||||
if (props.parseFunc) {
|
if (props.parseFunc) {
|
||||||
// 解析字符串函数
|
// 解析字符串函数
|
||||||
// eslint-disable-next-line no-new-func
|
// oxlint-disable-next-line typescript/no-implied-eval
|
||||||
|
// oxlint-disable-next-line no-new-func, typescript/no-implied-eval
|
||||||
parse = new Function(`return ${props.parseFunc}`)();
|
parse = new Function(`return ${props.parseFunc}`)();
|
||||||
}
|
}
|
||||||
return parse;
|
return parse;
|
||||||
|
|
@ -199,6 +235,8 @@ export function useApiSelect(option: ApiSelectProps) {
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getOptions();
|
await getOptions();
|
||||||
|
// 设置默认当前用户(仅用于 UserSelect)
|
||||||
|
setDefaultCurrentUser();
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildSelect = () => {
|
const buildSelect = () => {
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,36 @@ export function useImagesUpload() {
|
||||||
return defineComponent({
|
return defineComponent({
|
||||||
name: 'ImagesUpload',
|
name: 'ImagesUpload',
|
||||||
props: {
|
props: {
|
||||||
multiple: {
|
accept: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ['image/jpeg', 'image/png', 'image/gif'],
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: false,
|
||||||
},
|
},
|
||||||
maxNumber: {
|
maxNumber: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 5,
|
default: 5,
|
||||||
},
|
},
|
||||||
|
maxSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 5,
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup(props) {
|
||||||
// TODO: @puhui999:@dhb52 其实还是靠 props 默认参数起作用,没能从 formCreate 传递
|
return () => (
|
||||||
return (props: { maxNumber?: number; multiple?: boolean }) => (
|
<ImageUpload
|
||||||
<ImageUpload maxNumber={props.maxNumber} multiple={props.multiple} />
|
accept={props.accept as string[]}
|
||||||
|
disabled={props.disabled}
|
||||||
|
maxNumber={props.maxNumber}
|
||||||
|
maxSize={props.maxSize}
|
||||||
|
multiple={props.multiple}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,10 @@ import formCreate from '@form-create/ant-design-vue';
|
||||||
import { apiSelectRule } from '#/components/form-create/rules/data';
|
import { apiSelectRule } from '#/components/form-create/rules/data';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
useAreaSelectRule,
|
||||||
useDictSelectRule,
|
useDictSelectRule,
|
||||||
useEditorRule,
|
useEditorRule,
|
||||||
|
useIframeRule,
|
||||||
useSelectRule,
|
useSelectRule,
|
||||||
useUploadFileRule,
|
useUploadFileRule,
|
||||||
useUploadImageRule,
|
useUploadImageRule,
|
||||||
|
|
@ -160,6 +162,8 @@ export async function useFormCreateDesigner(designer: Ref) {
|
||||||
const uploadFileRule = useUploadFileRule();
|
const uploadFileRule = useUploadFileRule();
|
||||||
const uploadImageRule = useUploadImageRule();
|
const uploadImageRule = useUploadImageRule();
|
||||||
const uploadImagesRule = useUploadImagesRule();
|
const uploadImagesRule = useUploadImagesRule();
|
||||||
|
const iframeRule = useIframeRule();
|
||||||
|
const areaSelectRule = useAreaSelectRule();
|
||||||
|
|
||||||
/** 构建表单组件 */
|
/** 构建表单组件 */
|
||||||
function buildFormComponents() {
|
function buildFormComponents() {
|
||||||
|
|
@ -172,6 +176,8 @@ export async function useFormCreateDesigner(designer: Ref) {
|
||||||
uploadFileRule,
|
uploadFileRule,
|
||||||
uploadImageRule,
|
uploadImageRule,
|
||||||
uploadImagesRule,
|
uploadImagesRule,
|
||||||
|
iframeRule,
|
||||||
|
areaSelectRule,
|
||||||
];
|
];
|
||||||
components.forEach((component) => {
|
components.forEach((component) => {
|
||||||
// 插入组件规则
|
// 插入组件规则
|
||||||
|
|
@ -189,6 +195,14 @@ export async function useFormCreateDesigner(designer: Ref) {
|
||||||
name: 'UserSelect',
|
name: 'UserSelect',
|
||||||
label: '用户选择器',
|
label: '用户选择器',
|
||||||
icon: 'icon-eye',
|
icon: 'icon-eye',
|
||||||
|
props: [
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'defaultCurrentUser',
|
||||||
|
title: '默认选中当前用户',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
const deptSelectRule = useSelectRule({
|
const deptSelectRule = useSelectRule({
|
||||||
name: 'DeptSelect',
|
name: 'DeptSelect',
|
||||||
|
|
@ -205,6 +219,12 @@ export async function useFormCreateDesigner(designer: Ref) {
|
||||||
{ label: '部门名称', value: 'name' },
|
{ label: '部门名称', value: 'name' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'defaultCurrentDept',
|
||||||
|
title: '默认选中当前部门',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const dictSelectRule = useDictSelectRule();
|
const dictSelectRule = useDictSelectRule();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
/* eslint-disable no-template-curly-in-string */
|
|
||||||
const selectRule = [
|
const selectRule = [
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
|
|
@ -121,7 +120,7 @@ const apiSelectRule = [
|
||||||
field: 'data',
|
field: 'data',
|
||||||
title: '请求参数 JSON 格式',
|
title: '请求参数 JSON 格式',
|
||||||
props: {
|
props: {
|
||||||
autoSize: true,
|
autoSize: true, // 特殊:ele 里是 autosize,antd 里是 autoSize
|
||||||
type: 'textarea',
|
type: 'textarea',
|
||||||
placeholder: '{"type": 1}',
|
placeholder: '{"type": 1}',
|
||||||
},
|
},
|
||||||
|
|
@ -134,7 +133,7 @@ const apiSelectRule = [
|
||||||
type: 'input',
|
type: 'input',
|
||||||
field: 'labelField',
|
field: 'labelField',
|
||||||
title: 'label 属性',
|
title: 'label 属性',
|
||||||
info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}',
|
info: `可以使用 el 表达式:\${属性},来实现复杂数据组合。如:\${nickname}-\${id}`,
|
||||||
props: {
|
props: {
|
||||||
placeholder: 'nickname',
|
placeholder: 'nickname',
|
||||||
},
|
},
|
||||||
|
|
@ -143,7 +142,7 @@ const apiSelectRule = [
|
||||||
type: 'input',
|
type: 'input',
|
||||||
field: 'valueField',
|
field: 'valueField',
|
||||||
title: 'value 属性',
|
title: 'value 属性',
|
||||||
info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}',
|
info: `可以使用 el 表达式:\${属性},来实现复杂数据组合。如:\${nickname}-\${id}`,
|
||||||
props: {
|
props: {
|
||||||
placeholder: 'id',
|
placeholder: 'id',
|
||||||
},
|
},
|
||||||
|
|
@ -155,7 +154,7 @@ const apiSelectRule = [
|
||||||
info: `data 为接口返回值,需要写一个匿名函数解析返回值为选择器 options 列表
|
info: `data 为接口返回值,需要写一个匿名函数解析返回值为选择器 options 列表
|
||||||
(data: any)=>{ label: string; value: any }[]`,
|
(data: any)=>{ label: string; value: any }[]`,
|
||||||
props: {
|
props: {
|
||||||
autoSize: true,
|
autoSize: true, // 特殊:ele 里是 autosize,antd 里是 autoSize
|
||||||
rows: { minRows: 2, maxRows: 6 },
|
rows: { minRows: 2, maxRows: 6 },
|
||||||
type: 'textarea',
|
type: 'textarea',
|
||||||
placeholder: `
|
placeholder: `
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
export { useAreaSelectRule } from './use-area-select-rule';
|
||||||
export { useDictSelectRule } from './use-dict-select';
|
export { useDictSelectRule } from './use-dict-select';
|
||||||
export { useEditorRule } from './use-editor-rule';
|
export { useEditorRule } from './use-editor-rule';
|
||||||
|
export { useIframeRule } from './use-iframe-rule';
|
||||||
export { useSelectRule } from './use-select-rule';
|
export { useSelectRule } from './use-select-rule';
|
||||||
export { useUploadFileRule } from './use-upload-file-rule';
|
export { useUploadFileRule } from './use-upload-file-rule';
|
||||||
export { useUploadImageRule } from './use-upload-image-rule';
|
export { useUploadImageRule } from './use-upload-image-rule';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { AreaLevelEnum } from '@vben/constants';
|
||||||
|
|
||||||
|
import {
|
||||||
|
localeProps,
|
||||||
|
makeRequiredRule,
|
||||||
|
} from '#/components/form-create/helpers';
|
||||||
|
|
||||||
|
/** 省市区选择器规则 */
|
||||||
|
export function useAreaSelectRule() {
|
||||||
|
const label = '省市区选择器';
|
||||||
|
const name = 'AreaSelect';
|
||||||
|
|
||||||
|
return {
|
||||||
|
icon: 'icon-location',
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
rule() {
|
||||||
|
return {
|
||||||
|
type: name,
|
||||||
|
field: `area_${Date.now()}`,
|
||||||
|
title: label,
|
||||||
|
info: '',
|
||||||
|
$required: false,
|
||||||
|
modelField: 'value', // 特殊:ele 里是 model-value,antd 里是 value
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props(_: any, { t }: any) {
|
||||||
|
return localeProps(t, `${name}.props`, [
|
||||||
|
makeRequiredRule(),
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'level',
|
||||||
|
title: '选择层级',
|
||||||
|
value: AreaLevelEnum.DISTRICT,
|
||||||
|
options: [
|
||||||
|
{ label: '省', value: AreaLevelEnum.PROVINCE },
|
||||||
|
{ label: '省/市', value: AreaLevelEnum.CITY },
|
||||||
|
{ label: '省/市/区', value: AreaLevelEnum.DISTRICT },
|
||||||
|
],
|
||||||
|
info: '限制可选择的地区层级',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'placeholder',
|
||||||
|
title: '占位符',
|
||||||
|
value: '请选择省市区',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'clearable',
|
||||||
|
title: '是否可清空',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'showAllLevels',
|
||||||
|
title: '显示完整路径',
|
||||||
|
value: true,
|
||||||
|
info: '输入框中是否显示选中值的完整路径',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'separator',
|
||||||
|
title: '分隔符',
|
||||||
|
value: '/',
|
||||||
|
info: '选项分隔符',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'disabled',
|
||||||
|
title: '是否禁用',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -39,7 +39,7 @@ export function useDictSelectRule() {
|
||||||
title: label,
|
title: label,
|
||||||
info: '',
|
info: '',
|
||||||
$required: false,
|
$required: false,
|
||||||
modelField: 'value',
|
modelField: 'value', // 特殊:ele 里是 model-value,antd 里是 value
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
props(_: any, { t }: any) {
|
props(_: any, { t }: any) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { buildUUID } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
localeProps,
|
||||||
|
makeRequiredRule,
|
||||||
|
} from '#/components/form-create/helpers';
|
||||||
|
|
||||||
|
/** iframe 组件规则 */
|
||||||
|
export function useIframeRule() {
|
||||||
|
const label = '网页 iframe';
|
||||||
|
const name = 'IframeComponent';
|
||||||
|
|
||||||
|
return {
|
||||||
|
icon: 'icon-link',
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
rule() {
|
||||||
|
return {
|
||||||
|
type: name,
|
||||||
|
field: buildUUID(),
|
||||||
|
title: label,
|
||||||
|
info: '',
|
||||||
|
$required: false,
|
||||||
|
modelField: 'value', // 特殊:ele 里是 model-value,antd 里是 value
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props(_: any, { t }: any) {
|
||||||
|
return localeProps(t, `${name}.props`, [
|
||||||
|
makeRequiredRule(),
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'url',
|
||||||
|
title: 'URL 地址',
|
||||||
|
value: '',
|
||||||
|
info: '请输入完整的 HTTP 或 HTTPS 地址',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'height',
|
||||||
|
title: 'iframe 高度',
|
||||||
|
value: '500px',
|
||||||
|
info: '支持 px、%、vh 等单位',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'width',
|
||||||
|
title: 'iframe 宽度',
|
||||||
|
value: '100%',
|
||||||
|
info: '支持 px、%、vw 等单位',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'loading',
|
||||||
|
title: '加载方式',
|
||||||
|
value: 'lazy',
|
||||||
|
options: [
|
||||||
|
{ label: '懒加载', value: 'lazy' },
|
||||||
|
{ label: '立即加载', value: 'eager' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'allowfullscreen',
|
||||||
|
title: '允许全屏',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'sandbox',
|
||||||
|
title: 'sandbox 属性',
|
||||||
|
value: '',
|
||||||
|
info: '安全沙箱限制,如:allow-scripts allow-same-origin',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -23,13 +23,24 @@ export function useSelectRule(option: SelectRuleOption) {
|
||||||
name,
|
name,
|
||||||
event: option.event,
|
event: option.event,
|
||||||
rule() {
|
rule() {
|
||||||
return {
|
// 构建基础规则
|
||||||
|
const baseRule: any = {
|
||||||
type: name,
|
type: name,
|
||||||
field: buildUUID(),
|
field: buildUUID(),
|
||||||
title: label,
|
title: label,
|
||||||
info: '',
|
info: '',
|
||||||
$required: false,
|
$required: false,
|
||||||
};
|
};
|
||||||
|
// 将自定义 props 的默认值添加到 rule 的 props 中
|
||||||
|
if (option.props && option.props.length > 0) {
|
||||||
|
baseRule.props = {};
|
||||||
|
option.props.forEach((prop: any) => {
|
||||||
|
if (prop.field && prop.value !== undefined) {
|
||||||
|
baseRule.props[prop.field] = prop.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return baseRule;
|
||||||
},
|
},
|
||||||
props(_: any, { t }: any) {
|
props(_: any, { t }: any) {
|
||||||
if (!option.props) {
|
if (!option.props) {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export function useUploadFileRule() {
|
||||||
makeRequiredRule(),
|
makeRequiredRule(),
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
field: 'fileType',
|
field: 'accept',
|
||||||
title: '文件类型',
|
title: '文件类型',
|
||||||
value: ['doc', 'xls', 'ppt', 'txt', 'pdf'],
|
value: ['doc', 'xls', 'ppt', 'txt', 'pdf'],
|
||||||
options: [
|
options: [
|
||||||
|
|
@ -40,12 +40,6 @@ export function useUploadFileRule() {
|
||||||
mode: 'multiple',
|
mode: 'multiple',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: 'switch',
|
|
||||||
field: 'autoUpload',
|
|
||||||
title: '是否在选取文件后立即进行上传',
|
|
||||||
value: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'switch',
|
type: 'switch',
|
||||||
field: 'drag',
|
field: 'drag',
|
||||||
|
|
@ -54,23 +48,23 @@ export function useUploadFileRule() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'switch',
|
type: 'switch',
|
||||||
field: 'isShowTip',
|
field: 'showDescription',
|
||||||
title: '是否显示提示',
|
title: '是否显示提示',
|
||||||
value: true,
|
value: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'inputNumber',
|
type: 'inputNumber',
|
||||||
field: 'fileSize',
|
field: 'maxSize',
|
||||||
title: '大小限制(MB)',
|
title: '大小限制(MB)',
|
||||||
value: 5,
|
value: 5,
|
||||||
props: { min: 0 },
|
props: { min: 0 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'inputNumber',
|
type: 'inputNumber',
|
||||||
field: 'limit',
|
field: 'maxNumber',
|
||||||
title: '数量限制',
|
title: '数量限制',
|
||||||
value: 5,
|
value: 5,
|
||||||
props: { min: 0 },
|
props: { min: 1 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'switch',
|
type: 'switch',
|
||||||
|
|
|
||||||
|
|
@ -24,15 +24,9 @@ export function useUploadImageRule() {
|
||||||
props(_: any, { t }: any) {
|
props(_: any, { t }: any) {
|
||||||
return localeProps(t, `${name}.props`, [
|
return localeProps(t, `${name}.props`, [
|
||||||
makeRequiredRule(),
|
makeRequiredRule(),
|
||||||
{
|
|
||||||
type: 'switch',
|
|
||||||
field: 'drag',
|
|
||||||
title: '拖拽上传',
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
field: 'fileType',
|
field: 'accept',
|
||||||
title: '图片类型限制',
|
title: '图片类型限制',
|
||||||
value: ['image/jpeg', 'image/png', 'image/gif'],
|
value: ['image/jpeg', 'image/png', 'image/gif'],
|
||||||
options: [
|
options: [
|
||||||
|
|
@ -52,40 +46,16 @@ export function useUploadImageRule() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'inputNumber',
|
type: 'inputNumber',
|
||||||
field: 'fileSize',
|
field: 'maxSize',
|
||||||
title: '大小限制(MB)',
|
title: '大小限制(MB)',
|
||||||
value: 5,
|
value: 5,
|
||||||
props: { min: 0 },
|
props: { min: 0 },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: 'input',
|
|
||||||
field: 'height',
|
|
||||||
title: '组件高度',
|
|
||||||
value: '150px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'input',
|
|
||||||
field: 'width',
|
|
||||||
title: '组件宽度',
|
|
||||||
value: '150px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'input',
|
|
||||||
field: 'borderradius',
|
|
||||||
title: '组件边框圆角',
|
|
||||||
value: '8px',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'switch',
|
type: 'switch',
|
||||||
field: 'disabled',
|
field: 'disabled',
|
||||||
title: '是否显示删除按钮',
|
title: '是否禁用',
|
||||||
value: true,
|
value: false,
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'switch',
|
|
||||||
field: 'showBtnText',
|
|
||||||
title: '是否显示按钮文字',
|
|
||||||
value: true,
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -24,15 +24,9 @@ export function useUploadImagesRule() {
|
||||||
props(_: any, { t }: any) {
|
props(_: any, { t }: any) {
|
||||||
return localeProps(t, `${name}.props`, [
|
return localeProps(t, `${name}.props`, [
|
||||||
makeRequiredRule(),
|
makeRequiredRule(),
|
||||||
{
|
|
||||||
type: 'switch',
|
|
||||||
field: 'drag',
|
|
||||||
title: '拖拽上传',
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
field: 'fileType',
|
field: 'accept',
|
||||||
title: '图片类型限制',
|
title: '图片类型限制',
|
||||||
value: ['image/jpeg', 'image/png', 'image/gif'],
|
value: ['image/jpeg', 'image/png', 'image/gif'],
|
||||||
options: [
|
options: [
|
||||||
|
|
@ -48,40 +42,27 @@ export function useUploadImagesRule() {
|
||||||
],
|
],
|
||||||
props: {
|
props: {
|
||||||
mode: 'multiple',
|
mode: 'multiple',
|
||||||
maxNumber: 5,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'inputNumber',
|
type: 'inputNumber',
|
||||||
field: 'fileSize',
|
field: 'maxSize',
|
||||||
title: '大小限制(MB)',
|
title: '大小限制(MB)',
|
||||||
value: 5,
|
value: 5,
|
||||||
props: { min: 0 },
|
props: { min: 0 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'inputNumber',
|
type: 'inputNumber',
|
||||||
field: 'limit',
|
field: 'maxNumber',
|
||||||
title: '数量限制',
|
title: '数量限制',
|
||||||
value: 5,
|
value: 5,
|
||||||
props: { min: 0 },
|
props: { min: 1 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'switch',
|
||||||
field: 'height',
|
field: 'disabled',
|
||||||
title: '组件高度',
|
title: '是否禁用',
|
||||||
value: '150px',
|
value: false,
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'input',
|
|
||||||
field: 'width',
|
|
||||||
title: '组件宽度',
|
|
||||||
value: '150px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'input',
|
|
||||||
field: 'borderradius',
|
|
||||||
title: '组件边框圆角',
|
|
||||||
value: '8px',
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { default as MapDialog } from './src/map-dialog.vue';
|
||||||
|
|
||||||
|
export { loadBaiduMapSdk } from './src/utils';
|
||||||
|
|
@ -0,0 +1,287 @@
|
||||||
|
<!-- 地图选择弹窗组件:基于百度地图 GL 实现 -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { nextTick, reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button, Form, Input, Select, Space } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { loadBaiduMapSdk } from './utils';
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
confirm: [
|
||||||
|
data: {
|
||||||
|
address: string;
|
||||||
|
latitude: string;
|
||||||
|
longitude: string;
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const mapContainerRef = ref<HTMLElement>();
|
||||||
|
const state = reactive({
|
||||||
|
lonLat: '', // 经纬度字符串,格式为 "经度,纬度"
|
||||||
|
address: '', // 地址信息
|
||||||
|
loading: false, // 地址搜索加载状态
|
||||||
|
latitude: '', // 纬度
|
||||||
|
longitude: '', // 经度
|
||||||
|
map: null as any, // 百度地图实例
|
||||||
|
mapAddressOptions: [] as any[], // 地址搜索选项
|
||||||
|
mapMarker: null as any, // 地图标记点
|
||||||
|
geocoder: null as any, // 地理编码器实例
|
||||||
|
mapContainerReady: false, // 地图容器是否准备好
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始经纬度(打开弹窗时传入)
|
||||||
|
const initLongitude = ref<number | undefined>();
|
||||||
|
const initLatitude = ref<number | undefined>();
|
||||||
|
|
||||||
|
/** 弹窗打开动画完成后初始化地图 */
|
||||||
|
async function handleDialogOpened() {
|
||||||
|
// 先显示地图容器
|
||||||
|
state.mapContainerReady = true;
|
||||||
|
|
||||||
|
// 等待下一个 DOM 更新周期,确保地图容器已渲染
|
||||||
|
await nextTick();
|
||||||
|
// 加载百度地图 SDK
|
||||||
|
await loadBaiduMapSdk();
|
||||||
|
initMapInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 弹窗关闭后清理地图 */
|
||||||
|
function handleDialogClosed() {
|
||||||
|
// 销毁地图实例
|
||||||
|
if (state.map) {
|
||||||
|
state.map.destroy?.();
|
||||||
|
state.map = null;
|
||||||
|
}
|
||||||
|
state.mapMarker = null;
|
||||||
|
state.geocoder = null;
|
||||||
|
state.mapContainerReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化地图实例 */
|
||||||
|
function initMapInstance() {
|
||||||
|
if (!mapContainerRef.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化地图和地理编码器
|
||||||
|
initMap();
|
||||||
|
initGeocoder();
|
||||||
|
|
||||||
|
// 监听地图点击事件
|
||||||
|
state.map.addEventListener('click', (e: any) => {
|
||||||
|
const point = e.latlng;
|
||||||
|
state.lonLat = `${point.lng},${point.lat}`;
|
||||||
|
regeoCode(state.lonLat);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果有初始经纬度,加载标记点
|
||||||
|
if (initLongitude.value && initLatitude.value) {
|
||||||
|
const lonLat = `${initLongitude.value},${initLatitude.value}`;
|
||||||
|
regeoCode(lonLat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化地图 */
|
||||||
|
function initMap() {
|
||||||
|
state.map = new window.BMapGL.Map(mapContainerRef.value);
|
||||||
|
state.map.centerAndZoom(new window.BMapGL.Point(116.404, 39.915), 11);
|
||||||
|
state.map.enableScrollWheelZoom();
|
||||||
|
state.map.disableDoubleClickZoom();
|
||||||
|
|
||||||
|
state.map.addControl(new window.BMapGL.NavigationControl());
|
||||||
|
state.map.addControl(new window.BMapGL.ScaleControl());
|
||||||
|
state.map.addControl(new window.BMapGL.ZoomControl());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化地理编码器 */
|
||||||
|
function initGeocoder() {
|
||||||
|
state.geocoder = new window.BMapGL.Geocoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索地址 */
|
||||||
|
function autoSearch(queryValue: string) {
|
||||||
|
if (!queryValue) {
|
||||||
|
state.mapAddressOptions = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.loading = true;
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
const localSearch = new window.BMapGL.LocalSearch(state.map, {
|
||||||
|
onSearchComplete: (results: any) => {
|
||||||
|
state.loading = false;
|
||||||
|
const temp: any[] = [];
|
||||||
|
|
||||||
|
if (results && results._pois) {
|
||||||
|
results._pois.forEach((p: any) => {
|
||||||
|
const point = p.point;
|
||||||
|
if (point && point.lng && point.lat) {
|
||||||
|
temp.push({
|
||||||
|
name: p.title,
|
||||||
|
value: `${point.lng},${point.lat}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
state.mapAddressOptions = temp;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
localSearch.search(queryValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理地址选择 */
|
||||||
|
function handleAddressSelect(value: string) {
|
||||||
|
if (value) {
|
||||||
|
regeoCode(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加标记点 */
|
||||||
|
function setMarker(lnglat: string[]) {
|
||||||
|
if (!lnglat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.mapMarker !== null) {
|
||||||
|
state.map.removeOverlay(state.mapMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
const point = new window.BMapGL.Point(lnglat[0], lnglat[1]);
|
||||||
|
state.mapMarker = new window.BMapGL.Marker(point);
|
||||||
|
|
||||||
|
state.map.addOverlay(state.mapMarker);
|
||||||
|
state.map.centerAndZoom(point, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 经纬度转地址、添加标记点 */
|
||||||
|
function regeoCode(lonLat: string) {
|
||||||
|
if (!lonLat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lnglat = lonLat.split(',');
|
||||||
|
if (lnglat.length !== 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.longitude = lnglat[0]!;
|
||||||
|
state.latitude = lnglat[1]!;
|
||||||
|
const point = new window.BMapGL.Point(lnglat[0], lnglat[1]);
|
||||||
|
state.map.centerAndZoom(point, 16);
|
||||||
|
|
||||||
|
setMarker(lnglat);
|
||||||
|
getAddress(lnglat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据经纬度获取地址信息 */
|
||||||
|
function getAddress(lnglat: string[]) {
|
||||||
|
const point = new window.BMapGL.Point(lnglat[0], lnglat[1]);
|
||||||
|
state.geocoder.getLocation(point, (result: any) => {
|
||||||
|
if (result && result.address) {
|
||||||
|
state.address = result.address;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 确认选择 */
|
||||||
|
function handleConfirm() {
|
||||||
|
if (state.longitude && state.latitude) {
|
||||||
|
emit('confirm', {
|
||||||
|
longitude: state.longitude,
|
||||||
|
latitude: state.latitude,
|
||||||
|
address: state.address,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
modalApi.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
onOpenChange(isOpen: boolean) {
|
||||||
|
if (isOpen) {
|
||||||
|
handleDialogOpened();
|
||||||
|
} else {
|
||||||
|
handleDialogClosed();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
function open(longitude?: number, latitude?: number) {
|
||||||
|
initLongitude.value = longitude;
|
||||||
|
initLatitude.value = latitude;
|
||||||
|
state.longitude = longitude ? String(longitude) : '';
|
||||||
|
state.latitude = latitude ? String(latitude) : '';
|
||||||
|
state.address = '';
|
||||||
|
state.mapAddressOptions = [];
|
||||||
|
modalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :footer="false" class="w-[700px]" title="百度地图">
|
||||||
|
<div class="w-full">
|
||||||
|
<!-- 第一行:位置搜索 -->
|
||||||
|
<Form :label-col="{ span: 4 }">
|
||||||
|
<Form.Item label="定位位置">
|
||||||
|
<Select
|
||||||
|
v-model:value="state.address"
|
||||||
|
:filter-option="false"
|
||||||
|
:loading="state.loading"
|
||||||
|
:options="
|
||||||
|
state.mapAddressOptions.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.value,
|
||||||
|
}))
|
||||||
|
"
|
||||||
|
allow-clear
|
||||||
|
class="w-full"
|
||||||
|
placeholder="可输入地址查询经纬度"
|
||||||
|
show-search
|
||||||
|
@search="autoSearch"
|
||||||
|
@select="handleAddressSelect"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<!-- 第二行:坐标显示 -->
|
||||||
|
<Form.Item label="当前坐标">
|
||||||
|
<Space>
|
||||||
|
<Input
|
||||||
|
:value="state.longitude"
|
||||||
|
addon-before="经度"
|
||||||
|
disabled
|
||||||
|
style="width: 180px"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
:value="state.latitude"
|
||||||
|
addon-before="纬度"
|
||||||
|
disabled
|
||||||
|
style="width: 180px"
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
<!-- 第三行:地图 -->
|
||||||
|
<div
|
||||||
|
v-if="state.mapContainerReady"
|
||||||
|
ref="mapContainerRef"
|
||||||
|
class="mt-[10px] h-[400px] w-full"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="mt-[10px] flex h-[400px] w-full items-center justify-center"
|
||||||
|
>
|
||||||
|
<span class="text-gray-400">地图加载中...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 flex justify-end gap-2">
|
||||||
|
<Button type="primary" @click="handleConfirm">确 定</Button>
|
||||||
|
<Button @click="modalApi.close()">取 消</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-dynamic-delete */
|
||||||
|
/**
|
||||||
|
* 百度地图 SDK 加载工具
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 扩展 Window 接口以包含百度地图 GL API
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
BMapGL: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全局回调名称
|
||||||
|
const CALLBACK_NAME = '__BAIDU_MAP_LOAD_CALLBACK__';
|
||||||
|
|
||||||
|
// SDK 加载状态
|
||||||
|
let loadPromise: null | Promise<void> = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载百度地图 GL SDK
|
||||||
|
* @param timeout 超时时间(毫秒),默认 10000
|
||||||
|
* @returns Promise<void>
|
||||||
|
*/
|
||||||
|
export const loadBaiduMapSdk = (timeout = 10_000): Promise<void> => {
|
||||||
|
// 已加载完成
|
||||||
|
if (window.BMapGL) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正在加载中,返回同一个 Promise
|
||||||
|
if (loadPromise) {
|
||||||
|
return loadPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPromise = new Promise((resolve, reject) => {
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
loadPromise = null;
|
||||||
|
reject(new Error('百度地图 SDK 加载超时'));
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
// 全局回调
|
||||||
|
(window as any)[CALLBACK_NAME] = () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
delete (window as any)[CALLBACK_NAME];
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建 script 标签
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = `https://api.map.baidu.com/api?v=1.0&type=webgl&ak=${
|
||||||
|
import.meta.env.VITE_BAIDU_MAP_KEY
|
||||||
|
}&callback=${CALLBACK_NAME}`;
|
||||||
|
script.addEventListener('onerror', () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
loadPromise = null;
|
||||||
|
delete (window as any)[CALLBACK_NAME];
|
||||||
|
reject(new Error('百度地图 SDK 加载失败'));
|
||||||
|
});
|
||||||
|
document.body.append(script);
|
||||||
|
});
|
||||||
|
|
||||||
|
return loadPromise;
|
||||||
|
};
|
||||||
|
|
@ -47,6 +47,7 @@ onMounted(async () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<div ref="contentRef" class="markdown-view" v-html="renderedMarkdown"></div>
|
<div ref="contentRef" class="markdown-view" v-html="renderedMarkdown"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// TODO @xingyu:要不要改成 yudao-ui-admin-vue3/src/components/OperateLogV2/src/OperateLogV2.vue 这种;一行:时间、userType、userName、action
|
|
||||||
import type { OperateLogProps } from './typing';
|
import type { OperateLogProps } from './typing';
|
||||||
|
|
||||||
import { DICT_TYPE } from '@vben/constants';
|
import { DICT_TYPE } from '@vben/constants';
|
||||||
|
|
@ -14,37 +13,46 @@ withDefaults(defineProps<OperateLogProps>(), {
|
||||||
logList: () => [],
|
logList: () => [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 获得 userType 颜色 */
|
||||||
function getUserTypeColor(userType: number) {
|
function getUserTypeColor(userType: number) {
|
||||||
const dict = getDictObj(DICT_TYPE.USER_TYPE, userType);
|
const dict = getDictObj(DICT_TYPE.USER_TYPE, userType);
|
||||||
if (dict && dict.colorType) {
|
switch (dict?.colorType) {
|
||||||
return `hsl(var(--${dict.colorType}))`;
|
case 'danger': {
|
||||||
|
return '#F56C6C';
|
||||||
|
}
|
||||||
|
case 'info': {
|
||||||
|
return '#909399';
|
||||||
|
}
|
||||||
|
case 'success': {
|
||||||
|
return '#67C23A';
|
||||||
|
}
|
||||||
|
case 'warning': {
|
||||||
|
return '#E6A23C';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 'hsl(var(--primary))';
|
return '#409EFF';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="pt-5">
|
||||||
<Timeline>
|
<Timeline>
|
||||||
<Timeline.Item
|
<Timeline.Item v-for="log in logList" :key="log.id">
|
||||||
v-for="log in logList"
|
|
||||||
:key="log.id"
|
|
||||||
:color="getUserTypeColor(log.userType)"
|
|
||||||
>
|
|
||||||
<template #dot>
|
<template #dot>
|
||||||
<p
|
<span
|
||||||
:style="{ backgroundColor: getUserTypeColor(log.userType) }"
|
:style="{ backgroundColor: getUserTypeColor(log.userType) }"
|
||||||
class="absolute left-1 top-0 flex h-5 w-5 items-center justify-center rounded-full text-xs text-white"
|
class="flex h-5 w-5 items-center justify-center rounded-full text-[10px] text-white"
|
||||||
>
|
>
|
||||||
{{ getDictLabel(DICT_TYPE.USER_TYPE, log.userType)[0] }}
|
{{ getDictLabel(DICT_TYPE.USER_TYPE, log.userType)[0] }}
|
||||||
</p>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<p class="ml-2">{{ formatDateTime(log.createTime) }}</p>
|
<div class="ml-2 flex flex-wrap items-center gap-2 leading-[22px]">
|
||||||
<p class="ml-2 mt-2">
|
<span class="w-[140px] shrink-0 text-[13px] text-gray-400">
|
||||||
<Tag :color="getUserTypeColor(log.userType)">
|
{{ formatDateTime(log.createTime) }}
|
||||||
{{ log.userName }}
|
</span>
|
||||||
</Tag>
|
<Tag color="success" class="!mr-0">{{ log.userName }}</Tag>
|
||||||
{{ log.action }}
|
<span>{{ log.action }}</span>
|
||||||
</p>
|
</div>
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
</Timeline>
|
</Timeline>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@ async function handlePreview(file: UploadFile) {
|
||||||
async function handleRemove(file: UploadFile) {
|
async function handleRemove(file: UploadFile) {
|
||||||
if (fileList.value) {
|
if (fileList.value) {
|
||||||
const index = fileList.value.findIndex((item) => item.uid === file.uid);
|
const index = fileList.value.findIndex((item) => item.uid === file.uid);
|
||||||
|
// oxlint-disable-next-line no-unused-expressions
|
||||||
index !== -1 && fileList.value.splice(index, 1);
|
index !== -1 && fileList.value.splice(index, 1);
|
||||||
const value = getValue();
|
const value = getValue();
|
||||||
isInnerOperate.value = true;
|
isInnerOperate.value = true;
|
||||||
|
|
@ -350,6 +351,8 @@ function getValue() {
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.ant-upload-select-picture-card {
|
.ant-upload-select-picture-card {
|
||||||
@apply flex items-center justify-center;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { initPreferences } from '@vben/preferences';
|
import { initPreferences } from '@vben/preferences';
|
||||||
import { unmountGlobalLoading } from '@vben/utils';
|
import { unmountGlobalLoading } from '@vben/utils';
|
||||||
|
|
||||||
import { overridesPreferences } from './preferences';
|
import { overridesPreferences, preferencesExtension } from './preferences';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用初始化完成之后再进行页面加载渲染
|
* 应用初始化完成之后再进行页面加载渲染
|
||||||
|
|
@ -15,6 +15,7 @@ async function initApplication() {
|
||||||
|
|
||||||
// app偏好设置初始化
|
// app偏好设置初始化
|
||||||
await initPreferences({
|
await initPreferences({
|
||||||
|
extension: preferencesExtension,
|
||||||
namespace,
|
namespace,
|
||||||
overrides: overridesPreferences,
|
overrides: overridesPreferences,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,10 @@ import {
|
||||||
|
|
||||||
// ======================= 自定义组件 =======================
|
// ======================= 自定义组件 =======================
|
||||||
import { useApiSelect } from '#/components/form-create';
|
import { useApiSelect } from '#/components/form-create';
|
||||||
|
import AreaSelect from '#/components/form-create/components/area-select.vue';
|
||||||
|
import DeptSelect from '#/components/form-create/components/dept-select.vue';
|
||||||
import DictSelect from '#/components/form-create/components/dict-select.vue';
|
import DictSelect from '#/components/form-create/components/dict-select.vue';
|
||||||
|
import IframeComponent from '#/components/form-create/components/iframe.vue';
|
||||||
import { useImagesUpload } from '#/components/form-create/components/use-images-upload';
|
import { useImagesUpload } from '#/components/form-create/components/use-images-upload';
|
||||||
import { Tinymce } from '#/components/tinymce';
|
import { Tinymce } from '#/components/tinymce';
|
||||||
import { FileUpload, ImageUpload } from '#/components/upload';
|
import { FileUpload, ImageUpload } from '#/components/upload';
|
||||||
|
|
@ -45,12 +48,6 @@ const UserSelect = useApiSelect({
|
||||||
valueField: 'id',
|
valueField: 'id',
|
||||||
url: '/system/user/simple-list',
|
url: '/system/user/simple-list',
|
||||||
});
|
});
|
||||||
const DeptSelect = useApiSelect({
|
|
||||||
name: 'DeptSelect',
|
|
||||||
labelField: 'name',
|
|
||||||
valueField: 'id',
|
|
||||||
url: '/system/dept/simple-list',
|
|
||||||
});
|
|
||||||
const ApiSelect = useApiSelect({
|
const ApiSelect = useApiSelect({
|
||||||
name: 'ApiSelect',
|
name: 'ApiSelect',
|
||||||
});
|
});
|
||||||
|
|
@ -89,6 +86,8 @@ const components = [
|
||||||
Tinymce,
|
Tinymce,
|
||||||
ImageUpload,
|
ImageUpload,
|
||||||
FileUpload,
|
FileUpload,
|
||||||
|
IframeComponent,
|
||||||
|
AreaSelect,
|
||||||
];
|
];
|
||||||
|
|
||||||
// 参考 https://www.form-create.com/v3/ant-design-vue/auto-import 文档
|
// 参考 https://www.form-create.com/v3/ant-design-vue/auto-import 文档
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,14 @@
|
||||||
import { defineOverridesPreferences } from '@vben/preferences';
|
import {
|
||||||
|
defineOverridesPreferences,
|
||||||
|
definePreferencesExtension,
|
||||||
|
} from '@vben/preferences';
|
||||||
|
|
||||||
|
interface WebAntdPreferencesExtension {
|
||||||
|
defaultTableSize: number;
|
||||||
|
enableFormFullscreen: boolean;
|
||||||
|
reportTitle: string;
|
||||||
|
tenantMode: 'multi' | 'single';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 项目配置文件
|
* @description 项目配置文件
|
||||||
|
|
@ -23,3 +33,52 @@ export const overridesPreferences = defineOverridesPreferences({
|
||||||
companySiteLink: 'https://gitee.com/yudaocode/yudao-ui-admin-vben',
|
companySiteLink: 'https://gitee.com/yudaocode/yudao-ui-admin-vben',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const preferencesExtension =
|
||||||
|
definePreferencesExtension<WebAntdPreferencesExtension>({
|
||||||
|
tabLabel: 'preferences.antd.tabLabel',
|
||||||
|
title: 'preferences.antd.title',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
component: 'switch',
|
||||||
|
defaultValue: true,
|
||||||
|
key: 'enableFormFullscreen',
|
||||||
|
label: 'preferences.antd.fields.enableFormFullscreen.label',
|
||||||
|
tip: 'preferences.antd.fields.enableFormFullscreen.tip',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'select',
|
||||||
|
defaultValue: 'single',
|
||||||
|
key: 'tenantMode',
|
||||||
|
label: 'preferences.antd.fields.tenantMode.label',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'preferences.antd.fields.tenantMode.options.single.label',
|
||||||
|
value: 'single',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'preferences.antd.fields.tenantMode.options.multi.label',
|
||||||
|
value: 'multi',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'number',
|
||||||
|
componentProps: {
|
||||||
|
max: 200,
|
||||||
|
min: 10,
|
||||||
|
step: 10,
|
||||||
|
},
|
||||||
|
defaultValue: 20,
|
||||||
|
key: 'defaultTableSize',
|
||||||
|
label: 'preferences.antd.fields.defaultTableSize.label',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'input',
|
||||||
|
defaultValue: '',
|
||||||
|
key: 'reportTitle',
|
||||||
|
label: 'preferences.antd.fields.reportTitle.label',
|
||||||
|
placeholder: 'preferences.antd.fields.reportTitle.placeholder',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,21 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 用于 bpm 移动端流程表单 web-view 的嵌入
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
component: () => import('#/views/bpm/form/mobile/index.vue'),
|
||||||
|
meta: {
|
||||||
|
hideInBreadcrumb: true,
|
||||||
|
hideInMenu: true,
|
||||||
|
hideInTab: true,
|
||||||
|
ignoreAccess: true,
|
||||||
|
title: '移动端流程表单展示',
|
||||||
|
},
|
||||||
|
name: 'BpmMobileFormPreview',
|
||||||
|
path: '/bpm/mobile/form-preview',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export { coreRoutes, fallbackNotFoundRoute };
|
export { coreRoutes, fallbackNotFoundRoute };
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
if (accessStore.loginExpired) {
|
if (accessStore.loginExpired) {
|
||||||
accessStore.setLoginExpired(false);
|
accessStore.setLoginExpired(false);
|
||||||
} else {
|
} else {
|
||||||
|
// oxlint-disable-next-line no-unused-expressions
|
||||||
onSuccess
|
onSuccess
|
||||||
? await onSuccess?.()
|
? await onSuccess?.()
|
||||||
: await router.push(
|
: await router.push(
|
||||||
|
|
@ -132,6 +133,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
|
|
||||||
async function fetchUserInfo() {
|
async function fetchUserInfo() {
|
||||||
// 加载
|
// 加载
|
||||||
|
// eslint-disable-next-line no-useless-assignment
|
||||||
let authPermissionInfo: AuthPermissionInfo | null = null;
|
let authPermissionInfo: AuthPermissionInfo | null = null;
|
||||||
authPermissionInfo = await getAuthPermissionInfoApi();
|
authPermissionInfo = await getAuthPermissionInfoApi();
|
||||||
// userStore
|
// userStore
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ import type { Recordable } from '@vben/types';
|
||||||
export * from './rangePickerProps';
|
export * from './rangePickerProps';
|
||||||
export * from './routerHelper';
|
export * from './routerHelper';
|
||||||
|
|
||||||
|
// 从共享包导出 URL 工具函数
|
||||||
|
export { isUrl } from '@vben/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查找数组对象的某个下标
|
* 查找数组对象的某个下标
|
||||||
* @param {Array} ary 查找的数组
|
* @param {Array} ary 查找的数组
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,19 @@ import type { SystemUserProfileApi } from '#/api/system/user/profile';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
import { useUserStore } from '@vben/stores';
|
||||||
|
|
||||||
import { Card, Tabs } from 'ant-design-vue';
|
import { Card, Tabs } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getAuthPermissionInfoApi } from '#/api';
|
||||||
import { getUserProfile } from '#/api/system/user/profile';
|
import { getUserProfile } from '#/api/system/user/profile';
|
||||||
import { useAuthStore } from '#/store';
|
|
||||||
|
|
||||||
import BaseInfo from './modules/base-info.vue';
|
import BaseInfo from './modules/base-info.vue';
|
||||||
import ProfileUser from './modules/profile-user.vue';
|
import ProfileUser from './modules/profile-user.vue';
|
||||||
import ResetPwd from './modules/reset-pwd.vue';
|
import ResetPwd from './modules/reset-pwd.vue';
|
||||||
import UserSocial from './modules/user-social.vue';
|
import UserSocial from './modules/user-social.vue';
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const userStore = useUserStore();
|
||||||
const activeName = ref('basicInfo');
|
const activeName = ref('basicInfo');
|
||||||
|
|
||||||
/** 加载个人信息 */
|
/** 加载个人信息 */
|
||||||
|
|
@ -30,7 +31,8 @@ async function refreshProfile() {
|
||||||
await loadProfile();
|
await loadProfile();
|
||||||
|
|
||||||
// 更新 store
|
// 更新 store
|
||||||
await authStore.fetchUserInfo();
|
const authPermissionInfo = await getAuthPermissionInfoApi();
|
||||||
|
userStore.setUserInfo(authPermissionInfo.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
toolbarConfig: {
|
toolbarConfig: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
} as VxeTableGridOptions<SystemSocialUserApi.SocialUser>,
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 解绑账号 */
|
/** 解绑账号 */
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { ProfilePasswordSetting, z } from '@vben/common-ui';
|
import { ProfilePasswordSetting, z } from '@vben/common-ui';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
const profilePasswordSettingRef = ref();
|
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
@ -58,7 +56,6 @@ function handleSubmit() {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<ProfilePasswordSetting
|
<ProfilePasswordSetting
|
||||||
ref="profilePasswordSettingRef"
|
|
||||||
class="w-1/3"
|
class="w-1/3"
|
||||||
:form-schema="formSchema"
|
:form-schema="formSchema"
|
||||||
@submit="handleSubmit"
|
@submit="handleSubmit"
|
||||||
|
|
|
||||||
|
|
@ -571,6 +571,7 @@ onMounted(async () => {
|
||||||
size="small"
|
size="small"
|
||||||
@click="openChatConversationUpdateForm"
|
@click="openChatConversationUpdateForm"
|
||||||
>
|
>
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<span v-html="activeConversation?.modelName"></span>
|
<span v-html="activeConversation?.modelName"></span>
|
||||||
<IconifyIcon icon="lucide:settings" class="ml-2 size-4" />
|
<IconifyIcon icon="lucide:settings" class="ml-2 size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ const imageListRef = ref<any>(); // image 列表 ref
|
||||||
const dall3Ref = ref<any>(); // dall3(openai) ref
|
const dall3Ref = ref<any>(); // dall3(openai) ref
|
||||||
const midjourneyRef = ref<any>(); // midjourney ref
|
const midjourneyRef = ref<any>(); // midjourney ref
|
||||||
const stableDiffusionRef = ref<any>(); // stable diffusion ref
|
const stableDiffusionRef = ref<any>(); // stable diffusion ref
|
||||||
|
// @ts-expect-error: template ref is retained for future provider expansion
|
||||||
const commonRef = ref<any>(); // stable diffusion ref
|
const commonRef = ref<any>(); // stable diffusion ref
|
||||||
|
|
||||||
const selectPlatform = ref('common'); // 选中的平台
|
const selectPlatform = ref('common'); // 选中的平台
|
||||||
|
|
@ -45,7 +46,9 @@ const platformOptions = [
|
||||||
const models = ref<AiModelModelApi.Model[]>([]); // 模型列表
|
const models = ref<AiModelModelApi.Model[]>([]); // 模型列表
|
||||||
|
|
||||||
/** 绘画 start */
|
/** 绘画 start */
|
||||||
async function handleDrawStart() {}
|
function handleDrawStart() {
|
||||||
|
// drawing state is handled by child components
|
||||||
|
}
|
||||||
|
|
||||||
/** 绘画 complete */
|
/** 绘画 complete */
|
||||||
async function handleDrawComplete() {
|
async function handleDrawComplete() {
|
||||||
|
|
|
||||||
|
|
@ -150,10 +150,12 @@ defineExpose({
|
||||||
ref="mdContainerRef"
|
ref="mdContainerRef"
|
||||||
class="wh-full overflow-y-auto"
|
class="wh-full overflow-y-auto"
|
||||||
>
|
>
|
||||||
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-center justify-center"
|
class="flex flex-col items-center justify-center"
|
||||||
v-html="html"
|
v-html="html"
|
||||||
></div>
|
></div>
|
||||||
|
<!-- eslint-enable vue/no-v-html -->
|
||||||
</div>
|
</div>
|
||||||
<div ref="mindMapRef" class="wh-full">
|
<div ref="mindMapRef" class="wh-full">
|
||||||
<svg
|
<svg
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ const currentSong = inject<any>('currentSong', {});
|
||||||
{{ currentSong.date }}
|
{{ currentSong.date }}
|
||||||
</div>
|
</div>
|
||||||
<Button size="small" shape="round" class="my-2">信息复用</Button>
|
<Button size="small" shape="round" class="my-2">信息复用</Button>
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<div class="text-xs" v-html="currentSong.lyric"></div>
|
<div class="text-xs" v-html="currentSong.lyric"></div>
|
||||||
</Card>
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,9 @@ async function goRun() {
|
||||||
try {
|
try {
|
||||||
convertedParams[paramKey] = convertParamValue(value, dataType);
|
convertedParams[paramKey] = convertParamValue(value, dataType);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
throw new Error(`参数 ${paramKey} 转换失败: ${error.message}`);
|
throw new Error(`参数 ${paramKey} 转换失败: ${error.message}`, {
|
||||||
|
cause: error,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,7 +177,7 @@ function convertParamValue(value: string, dataType: string) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(value);
|
return JSON.parse(value);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
throw new Error(`JSON格式错误: ${error.message}`);
|
throw new Error(`JSON格式错误: ${error.message}`, { cause: error });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import {
|
||||||
|
|
||||||
import { Button, ButtonGroup, message, Modal, Tooltip } from 'ant-design-vue';
|
import { Button, ButtonGroup, message, Modal, Tooltip } from 'ant-design-vue';
|
||||||
// 模拟流转流程
|
// 模拟流转流程
|
||||||
// @ts-ignore
|
// @ts-expect-error: token simulation package does not ship compatible types
|
||||||
import tokenSimulation from 'bpmn-js-token-simulation';
|
import tokenSimulation from 'bpmn-js-token-simulation';
|
||||||
import BpmnModeler from 'bpmn-js/lib/Modeler';
|
import BpmnModeler from 'bpmn-js/lib/Modeler';
|
||||||
// 代码高亮插件
|
// 代码高亮插件
|
||||||
|
|
@ -132,6 +132,7 @@ const emit = defineEmits([
|
||||||
'element-click',
|
'element-click',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// @ts-expect-error: file input ref is set imperatively by the template
|
||||||
const bpmnCanvas = ref();
|
const bpmnCanvas = ref();
|
||||||
const refFile = ref();
|
const refFile = ref();
|
||||||
|
|
||||||
|
|
@ -178,6 +179,7 @@ const additionalModules = computed(() => {
|
||||||
) {
|
) {
|
||||||
Modules.push(...(props.additionalModel as any[]));
|
Modules.push(...(props.additionalModel as any[]));
|
||||||
} else {
|
} else {
|
||||||
|
// oxlint-disable-next-line no-unused-expressions
|
||||||
props.additionalModel && Modules.push(props.additionalModel);
|
props.additionalModel && Modules.push(props.additionalModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -417,6 +419,7 @@ const processSimulation = () => {
|
||||||
// bpmnModeler.get('toggleMode', 'strict'),
|
// bpmnModeler.get('toggleMode', 'strict'),
|
||||||
// "bpmnModeler.get('toggleMode')",
|
// "bpmnModeler.get('toggleMode')",
|
||||||
// );
|
// );
|
||||||
|
// oxlint-disable-next-line no-unused-expressions
|
||||||
props.simulation && bpmnModeler.get('toggleMode', 'strict').toggleMode();
|
props.simulation && bpmnModeler.get('toggleMode', 'strict').toggleMode();
|
||||||
};
|
};
|
||||||
const processRedo = () => {
|
const processRedo = () => {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { hasPrimaryModifier } from 'diagram-js/lib/util/Mouse';
|
||||||
/**
|
/**
|
||||||
* A provider for BPMN 2.0 elements context pad
|
* A provider for BPMN 2.0 elements context pad
|
||||||
*/
|
*/
|
||||||
export default function ContextPadProvider(
|
function ContextPadProvider(
|
||||||
config,
|
config,
|
||||||
injector,
|
injector,
|
||||||
eventBus,
|
eventBus,
|
||||||
|
|
@ -57,6 +57,8 @@ export default function ContextPadProvider(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ContextPadProvider;
|
||||||
|
|
||||||
ContextPadProvider.$inject = [
|
ContextPadProvider.$inject = [
|
||||||
'config.contextPad',
|
'config.contextPad',
|
||||||
'injector',
|
'injector',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import PaletteProvider from 'bpmn-js/lib/features/palette/PaletteProvider';
|
import PaletteProvider from 'bpmn-js/lib/features/palette/PaletteProvider';
|
||||||
|
|
||||||
export default function CustomPalette(
|
function CustomPalette(
|
||||||
palette,
|
palette,
|
||||||
create,
|
create,
|
||||||
elementFactory,
|
elementFactory,
|
||||||
|
|
@ -24,11 +24,21 @@ export default function CustomPalette(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const F = function () {}; // 核心,利用空对象作为中介;
|
CustomPalette.$inject = [
|
||||||
F.prototype = PaletteProvider.prototype; // 核心,将父类的原型赋值给空对象F;
|
'palette',
|
||||||
|
'create',
|
||||||
|
'elementFactory',
|
||||||
|
'spaceTool',
|
||||||
|
'lassoTool',
|
||||||
|
'handTool',
|
||||||
|
'globalConnect',
|
||||||
|
'translate',
|
||||||
|
];
|
||||||
|
|
||||||
// 利用中介函数重写原型链方法
|
CustomPalette.prototype = Object.create(PaletteProvider.prototype);
|
||||||
F.prototype.getPaletteEntries = function () {
|
CustomPalette.prototype.constructor = CustomPalette;
|
||||||
|
|
||||||
|
CustomPalette.prototype.getPaletteEntries = function () {
|
||||||
const actions = {};
|
const actions = {};
|
||||||
const create = this._create;
|
const create = this._create;
|
||||||
const elementFactory = this._elementFactory;
|
const elementFactory = this._elementFactory;
|
||||||
|
|
@ -94,8 +104,7 @@ F.prototype.getPaletteEntries = function () {
|
||||||
'hand-tool': {
|
'hand-tool': {
|
||||||
group: 'tools',
|
group: 'tools',
|
||||||
className: 'bpmn-icon-hand-tool',
|
className: 'bpmn-icon-hand-tool',
|
||||||
title: '激活抓手工具',
|
title: translate('Activate the hand tool'),
|
||||||
// title: translate("Activate the hand tool"),
|
|
||||||
action: {
|
action: {
|
||||||
click(event) {
|
click(event) {
|
||||||
handTool.activateHand(event);
|
handTool.activateHand(event);
|
||||||
|
|
@ -219,16 +228,4 @@ F.prototype.getPaletteEntries = function () {
|
||||||
return actions;
|
return actions;
|
||||||
};
|
};
|
||||||
|
|
||||||
CustomPalette.$inject = [
|
export default CustomPalette;
|
||||||
'palette',
|
|
||||||
'create',
|
|
||||||
'elementFactory',
|
|
||||||
'spaceTool',
|
|
||||||
'lassoTool',
|
|
||||||
'handTool',
|
|
||||||
'globalConnect',
|
|
||||||
'translate',
|
|
||||||
];
|
|
||||||
|
|
||||||
CustomPalette.prototype = new F(); // 核心,将 F的实例赋值给子类;
|
|
||||||
CustomPalette.prototype.constructor = CustomPalette; // 修复子类CustomPalette的构造器指向,防止原型链的混乱;
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* A palette provider for BPMN 2.0 elements.
|
* A palette provider for BPMN 2.0 elements.
|
||||||
*/
|
*/
|
||||||
export default function PaletteProvider(
|
function PaletteProvider(
|
||||||
palette,
|
palette,
|
||||||
create,
|
create,
|
||||||
elementFactory,
|
elementFactory,
|
||||||
|
|
@ -23,6 +23,8 @@ export default function PaletteProvider(
|
||||||
palette.registerProvider(this);
|
palette.registerProvider(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default PaletteProvider;
|
||||||
|
|
||||||
PaletteProvider.$inject = [
|
PaletteProvider.$inject = [
|
||||||
'palette',
|
'palette',
|
||||||
'create',
|
'create',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
/* eslint-disable no-template-curly-in-string */
|
|
||||||
/**
|
/**
|
||||||
* This is a sample file that should be replaced with the actual translation.
|
* This is a sample file that should be replaced with the actual translation.
|
||||||
*
|
*
|
||||||
|
|
@ -238,10 +237,8 @@ export default {
|
||||||
'Due Date': '到期时间',
|
'Due Date': '到期时间',
|
||||||
'Follow Up Date': '跟踪日期',
|
'Follow Up Date': '跟踪日期',
|
||||||
Priority: '优先级',
|
Priority: '优先级',
|
||||||
'The follow up date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)':
|
[`The follow up date as an EL expression (e.g. \${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)`]: `跟踪日期必须符合EL表达式,如: \${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00`,
|
||||||
'跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00',
|
[`The due date as an EL expression (e.g. \${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)`]: `跟踪日期必须符合EL表达式,如: \${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00`,
|
||||||
'The due date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)':
|
|
||||||
'跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00',
|
|
||||||
Variables: '变量',
|
Variables: '变量',
|
||||||
'Candidate Starter Configuration': '候选人起动器配置',
|
'Candidate Starter Configuration': '候选人起动器配置',
|
||||||
'Candidate Starter Groups': '候选人起动器组',
|
'Candidate Starter Groups': '候选人起动器组',
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ watch(
|
||||||
val +=
|
val +=
|
||||||
props.businessObject.eventDefinitions[0]?.$type.split(':')[1] || '';
|
props.businessObject.eventDefinitions[0]?.$type.split(':')[1] || '';
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-expect-error: async component registry is indexed dynamically
|
||||||
customConfigComponent.value = (
|
customConfigComponent.value = (
|
||||||
CustomConfigMap as Record<string, { component: Component }>
|
CustomConfigMap as Record<string, { component: Component }>
|
||||||
)[val]?.component;
|
)[val]?.component;
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ const resetCustomConfigList = () => {
|
||||||
approveType.value =
|
approveType.value =
|
||||||
elExtensionElements.value.values?.find(
|
elExtensionElements.value.values?.find(
|
||||||
(ex: any) => ex.$type === `${prefix}:ApproveType`,
|
(ex: any) => ex.$type === `${prefix}:ApproveType`,
|
||||||
)?.[0] ||
|
) ||
|
||||||
bpmnInstances().moddle.create(`${prefix}:ApproveType`, {
|
bpmnInstances().moddle.create(`${prefix}:ApproveType`, {
|
||||||
value: ApproveType.USER,
|
value: ApproveType.USER,
|
||||||
});
|
});
|
||||||
|
|
@ -184,7 +184,7 @@ const resetCustomConfigList = () => {
|
||||||
assignStartUserHandlerTypeEl.value =
|
assignStartUserHandlerTypeEl.value =
|
||||||
elExtensionElements.value.values?.find(
|
elExtensionElements.value.values?.find(
|
||||||
(ex: any) => ex.$type === `${prefix}:AssignStartUserHandlerType`,
|
(ex: any) => ex.$type === `${prefix}:AssignStartUserHandlerType`,
|
||||||
)?.[0] ||
|
) ||
|
||||||
bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, {
|
bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, {
|
||||||
value: 1,
|
value: 1,
|
||||||
});
|
});
|
||||||
|
|
@ -194,13 +194,13 @@ const resetCustomConfigList = () => {
|
||||||
rejectHandlerTypeEl.value =
|
rejectHandlerTypeEl.value =
|
||||||
elExtensionElements.value.values?.find(
|
elExtensionElements.value.values?.find(
|
||||||
(ex: any) => ex.$type === `${prefix}:RejectHandlerType`,
|
(ex: any) => ex.$type === `${prefix}:RejectHandlerType`,
|
||||||
)?.[0] ||
|
) ||
|
||||||
bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 });
|
bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 });
|
||||||
rejectHandlerType.value = rejectHandlerTypeEl.value.value;
|
rejectHandlerType.value = rejectHandlerTypeEl.value.value;
|
||||||
returnNodeIdEl.value =
|
returnNodeIdEl.value =
|
||||||
elExtensionElements.value.values?.find(
|
elExtensionElements.value.values?.find(
|
||||||
(ex: any) => ex.$type === `${prefix}:RejectReturnTaskId`,
|
(ex: any) => ex.$type === `${prefix}:RejectReturnTaskId`,
|
||||||
)?.[0] ||
|
) ||
|
||||||
bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, {
|
bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, {
|
||||||
value: '',
|
value: '',
|
||||||
});
|
});
|
||||||
|
|
@ -210,7 +210,7 @@ const resetCustomConfigList = () => {
|
||||||
assignEmptyHandlerTypeEl.value =
|
assignEmptyHandlerTypeEl.value =
|
||||||
elExtensionElements.value.values?.find(
|
elExtensionElements.value.values?.find(
|
||||||
(ex: any) => ex.$type === `${prefix}:AssignEmptyHandlerType`,
|
(ex: any) => ex.$type === `${prefix}:AssignEmptyHandlerType`,
|
||||||
)?.[0] ||
|
) ||
|
||||||
bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, {
|
bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, {
|
||||||
value: 1,
|
value: 1,
|
||||||
});
|
});
|
||||||
|
|
@ -218,7 +218,7 @@ const resetCustomConfigList = () => {
|
||||||
assignEmptyUserIdsEl.value =
|
assignEmptyUserIdsEl.value =
|
||||||
elExtensionElements.value.values?.find(
|
elExtensionElements.value.values?.find(
|
||||||
(ex: any) => ex.$type === `${prefix}:AssignEmptyUserIds`,
|
(ex: any) => ex.$type === `${prefix}:AssignEmptyUserIds`,
|
||||||
)?.[0] ||
|
) ||
|
||||||
bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, {
|
bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, {
|
||||||
value: '',
|
value: '',
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import ProcessListenerSelectModal from '#/views/bpm/processListener/components/process-listener-select-modal.vue';
|
import { ProcessListenerSelectModal } from '#/views/bpm/processListener/components';
|
||||||
|
|
||||||
import { createListenerObject, updateElementExtensions } from '../../utils';
|
import { createListenerObject, updateElementExtensions } from '../../utils';
|
||||||
import ListenerFieldModal from './ListenerFieldModal.vue';
|
import ListenerFieldModal from './ListenerFieldModal.vue';
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import ProcessListenerSelectModal from '#/views/bpm/processListener/components/process-listener-select-modal.vue';
|
import { ProcessListenerSelectModal } from '#/views/bpm/processListener/components';
|
||||||
|
|
||||||
import { createListenerObject, updateElementExtensions } from '../../utils';
|
import { createListenerObject, updateElementExtensions } from '../../utils';
|
||||||
import ListenerFieldModal from './ListenerFieldModal.vue';
|
import ListenerFieldModal from './ListenerFieldModal.vue';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
<!-- eslint-disable no-unused-vars -->
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { inject, nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
|
import { inject, nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
|
||||||
|
|
||||||
|
|
@ -66,13 +65,13 @@ const bpmnElement = ref<any>(null);
|
||||||
const multiLoopInstance = ref<any>(null);
|
const multiLoopInstance = ref<any>(null);
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
// @ts-ignore
|
|
||||||
bpmnInstances?: () => any;
|
bpmnInstances?: () => any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||||
|
|
||||||
|
// @ts-expect-error: retained for legacy multi-instance mode compatibility
|
||||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||||
const getElementLoop = (businessObject: any): void => {
|
const getElementLoop = (businessObject: any): void => {
|
||||||
if (!businessObject.loopCharacteristics) {
|
if (!businessObject.loopCharacteristics) {
|
||||||
|
|
@ -141,8 +140,7 @@ const changeLoopCharacteristicsType = (type: any): void => {
|
||||||
isSequential: true,
|
isSequential: true,
|
||||||
})
|
})
|
||||||
: bpmnInstances().moddle.create('bpmn:MultiInstanceLoopCharacteristics', {
|
: bpmnInstances().moddle.create('bpmn:MultiInstanceLoopCharacteristics', {
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
collection: `\${coll_userList}`,
|
||||||
collection: '${coll_userList}',
|
|
||||||
});
|
});
|
||||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||||
loopCharacteristics: toRaw(multiLoopInstance.value),
|
loopCharacteristics: toRaw(multiLoopInstance.value),
|
||||||
|
|
@ -233,7 +231,7 @@ const updateLoopAsync = (key: any): void => {
|
||||||
extensionElements: null,
|
extensionElements: null,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// @ts-ignore
|
// @ts-expect-error: dynamic async flags are assigned by runtime key
|
||||||
asyncAttr[key] = loopInstanceForm.value[key];
|
asyncAttr[key] = loopInstanceForm.value[key];
|
||||||
}
|
}
|
||||||
bpmnInstances().modeling.updateModdleProperties(
|
bpmnInstances().modeling.updateModdleProperties(
|
||||||
|
|
@ -247,23 +245,23 @@ const changeConfig = (config: string): void => {
|
||||||
switch (config) {
|
switch (config) {
|
||||||
case '会签': {
|
case '会签': {
|
||||||
changeLoopCharacteristicsType('ParallelMultiInstance');
|
changeLoopCharacteristicsType('ParallelMultiInstance');
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
|
||||||
updateLoopCondition('${ nrOfCompletedInstances >= nrOfInstances }');
|
updateLoopCondition(`\${ nrOfCompletedInstances >= nrOfInstances }`);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '依次审批': {
|
case '依次审批': {
|
||||||
changeLoopCharacteristicsType('SequentialMultiInstance');
|
changeLoopCharacteristicsType('SequentialMultiInstance');
|
||||||
updateLoopCardinality('1');
|
updateLoopCardinality('1');
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
|
||||||
updateLoopCondition('${ nrOfCompletedInstances >= nrOfInstances }');
|
updateLoopCondition(`\${ nrOfCompletedInstances >= nrOfInstances }`);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '或签': {
|
case '或签': {
|
||||||
changeLoopCharacteristicsType('ParallelMultiInstance');
|
changeLoopCharacteristicsType('ParallelMultiInstance');
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
|
||||||
updateLoopCondition('${ nrOfCompletedInstances > 0 }');
|
updateLoopCondition(`\${ nrOfCompletedInstances > 0 }`);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -331,8 +329,8 @@ const updateLoopCharacteristics = (): void => {
|
||||||
if (approveMethod.value === ApproveMethodType.APPROVE_BY_RATIO) {
|
if (approveMethod.value === ApproveMethodType.APPROVE_BY_RATIO) {
|
||||||
multiLoopInstance.value = bpmnInstances().moddle.create(
|
multiLoopInstance.value = bpmnInstances().moddle.create(
|
||||||
'bpmn:MultiInstanceLoopCharacteristics',
|
'bpmn:MultiInstanceLoopCharacteristics',
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
|
||||||
{ isSequential: false, collection: '${coll_userList}' },
|
{ isSequential: false, collection: `\${coll_userList}` },
|
||||||
);
|
);
|
||||||
multiLoopInstance.value.completionCondition =
|
multiLoopInstance.value.completionCondition =
|
||||||
bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||||
|
|
@ -344,20 +342,19 @@ const updateLoopCharacteristics = (): void => {
|
||||||
if (approveMethod.value === ApproveMethodType.ANY_APPROVE) {
|
if (approveMethod.value === ApproveMethodType.ANY_APPROVE) {
|
||||||
multiLoopInstance.value = bpmnInstances().moddle.create(
|
multiLoopInstance.value = bpmnInstances().moddle.create(
|
||||||
'bpmn:MultiInstanceLoopCharacteristics',
|
'bpmn:MultiInstanceLoopCharacteristics',
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
|
||||||
{ isSequential: false, collection: '${coll_userList}' },
|
{ isSequential: false, collection: `\${coll_userList}` },
|
||||||
);
|
);
|
||||||
multiLoopInstance.value.completionCondition =
|
multiLoopInstance.value.completionCondition =
|
||||||
bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
body: `\${ nrOfCompletedInstances > 0 }`,
|
||||||
body: '${ nrOfCompletedInstances > 0 }',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (approveMethod.value === ApproveMethodType.SEQUENTIAL_APPROVE) {
|
if (approveMethod.value === ApproveMethodType.SEQUENTIAL_APPROVE) {
|
||||||
multiLoopInstance.value = bpmnInstances().moddle.create(
|
multiLoopInstance.value = bpmnInstances().moddle.create(
|
||||||
'bpmn:MultiInstanceLoopCharacteristics',
|
'bpmn:MultiInstanceLoopCharacteristics',
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
|
||||||
{ isSequential: true, collection: '${coll_userList}' },
|
{ isSequential: true, collection: `\${coll_userList}` },
|
||||||
);
|
);
|
||||||
multiLoopInstance.value.loopCardinality = bpmnInstances().moddle.create(
|
multiLoopInstance.value.loopCardinality = bpmnInstances().moddle.create(
|
||||||
'bpmn:FormalExpression',
|
'bpmn:FormalExpression',
|
||||||
|
|
@ -367,8 +364,7 @@ const updateLoopCharacteristics = (): void => {
|
||||||
);
|
);
|
||||||
multiLoopInstance.value.completionCondition =
|
multiLoopInstance.value.completionCondition =
|
||||||
bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
body: `\${ nrOfCompletedInstances >= nrOfInstances }`,
|
||||||
body: '${ nrOfCompletedInstances >= nrOfInstances }',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ watch(
|
||||||
() => props.type,
|
() => props.type,
|
||||||
() => {
|
() => {
|
||||||
if (props.type) {
|
if (props.type) {
|
||||||
// @ts-ignore
|
// @ts-expect-error: installed task component map is indexed dynamically
|
||||||
witchTaskComponent.value = installedComponent[props.type].component;
|
witchTaskComponent.value = installedComponent[props.type].component;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ const initCallActivity = () => {
|
||||||
|
|
||||||
// 初始化所有配置项
|
// 初始化所有配置项
|
||||||
Object.keys(formData.value).forEach((key: string) => {
|
Object.keys(formData.value).forEach((key: string) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error: form state is updated through dynamic schema keys
|
||||||
formData.value[key] =
|
formData.value[key] =
|
||||||
bpmnElement.value.businessObject[key] ??
|
bpmnElement.value.businessObject[key] ??
|
||||||
formData.value[key as keyof FormData];
|
formData.value[key as keyof FormData];
|
||||||
|
|
@ -183,6 +183,7 @@ const updateElementExtensions = () => {
|
||||||
watch(
|
watch(
|
||||||
() => props.id,
|
() => props.id,
|
||||||
(val) => {
|
(val) => {
|
||||||
|
// oxlint-disable-next-line no-unused-expressions
|
||||||
val &&
|
val &&
|
||||||
val.length > 0 &&
|
val.length > 0 &&
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,6 @@ onMounted(() => {
|
||||||
bpmnRootElements.value
|
bpmnRootElements.value
|
||||||
.filter((el: any) => el.$type === 'bpmn:Message')
|
.filter((el: any) => el.$type === 'bpmn:Message')
|
||||||
.forEach((m: any) => {
|
.forEach((m: any) => {
|
||||||
// @ts-ignore
|
|
||||||
if (bpmnMessageRefsMap.value) {
|
if (bpmnMessageRefsMap.value) {
|
||||||
bpmnMessageRefsMap.value[m.id] = m;
|
bpmnMessageRefsMap.value[m.id] = m;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||||
|
|
||||||
const resetTaskForm = () => {
|
const resetTaskForm = () => {
|
||||||
for (const key in defaultTaskForm.value) {
|
for (const key in defaultTaskForm.value) {
|
||||||
// @ts-ignore
|
|
||||||
scriptTaskForm.value[key] =
|
scriptTaskForm.value[key] =
|
||||||
bpmnElement.value?.businessObject[
|
bpmnElement.value?.businessObject[
|
||||||
key as keyof typeof defaultTaskForm.value
|
key as keyof typeof defaultTaskForm.value
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
<!-- eslint-disable unicorn/no-nested-ternary -->
|
||||||
<!-- eslint-disable prettier/prettier -->
|
<!-- eslint-disable prettier/prettier -->
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { inject, nextTick, onBeforeUnmount, ref, watch } from 'vue';
|
import { inject, nextTick, onBeforeUnmount, ref, watch } from 'vue';
|
||||||
|
|
@ -206,9 +207,9 @@ const updateHttpExtensions = (force = false) => {
|
||||||
|
|
||||||
const persisted = HTTP_BOOLEAN_FIELDS.has(name)
|
const persisted = HTTP_BOOLEAN_FIELDS.has(name)
|
||||||
? String(!!rawValue)
|
? String(!!rawValue)
|
||||||
: (rawValue === undefined
|
: rawValue === undefined
|
||||||
? ''
|
? ''
|
||||||
: rawValue.toString());
|
: rawValue.toString();
|
||||||
|
|
||||||
desiredEntries.push([name, persisted]);
|
desiredEntries.push([name, persisted]);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ import {
|
||||||
MULTI_LEVEL_DEPT,
|
MULTI_LEVEL_DEPT,
|
||||||
} from '#/views/bpm/components/simple-process-design/consts';
|
} from '#/views/bpm/components/simple-process-design/consts';
|
||||||
import { useFormFieldsPermission } from '#/views/bpm/components/simple-process-design/helpers';
|
import { useFormFieldsPermission } from '#/views/bpm/components/simple-process-design/helpers';
|
||||||
import ProcessExpressionSelectModal from '#/views/bpm/processExpression/components/process-expression-select-modal.vue';
|
import { ProcessExpressionSelectModal } from '#/views/bpm/processExpression/components';
|
||||||
|
|
||||||
defineOptions({ name: 'UserTask' });
|
defineOptions({ name: 'UserTask' });
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
@ -70,6 +70,7 @@ const deptTreeOptions = ref<any>(); // 部门树
|
||||||
const postOptions = ref<SystemPostApi.Post[]>([]); // 岗位列表
|
const postOptions = ref<SystemPostApi.Post[]>([]); // 岗位列表
|
||||||
const userOptions = ref<SystemUserApi.User[]>([]); // 用户列表
|
const userOptions = ref<SystemUserApi.User[]>([]); // 用户列表
|
||||||
const userGroupOptions = ref<BpmUserGroupApi.UserGroup[]>([]); // 用户组列表
|
const userGroupOptions = ref<BpmUserGroupApi.UserGroup[]>([]); // 用户组列表
|
||||||
|
// @ts-expect-error: tree ref instance type is provided by the UI library at runtime
|
||||||
const treeRef = ref<any>();
|
const treeRef = ref<any>();
|
||||||
|
|
||||||
const { formFieldOptions } = useFormFieldsPermission(FieldPermissionType.READ);
|
const { formFieldOptions } = useFormFieldsPermission(FieldPermissionType.READ);
|
||||||
|
|
@ -128,7 +129,7 @@ const resetTaskForm = () => {
|
||||||
// eslint-disable-next-line unicorn/prefer-switch
|
// eslint-disable-next-line unicorn/prefer-switch
|
||||||
if (userTaskForm.value.candidateStrategy === CandidateStrategy.EXPRESSION) {
|
if (userTaskForm.value.candidateStrategy === CandidateStrategy.EXPRESSION) {
|
||||||
// 特殊:流程表达式,只有一个 input 输入框
|
// 特殊:流程表达式,只有一个 input 输入框
|
||||||
// @ts-ignore
|
// @ts-expect-error: expression strategy stores a scalar in an array-shaped field
|
||||||
userTaskForm.value.candidateParam = [candidateParamStr];
|
userTaskForm.value.candidateParam = [candidateParamStr];
|
||||||
} else if (
|
} else if (
|
||||||
userTaskForm.value.candidateStrategy ===
|
userTaskForm.value.candidateStrategy ===
|
||||||
|
|
@ -152,7 +153,7 @@ const resetTaskForm = () => {
|
||||||
userTaskForm.value.candidateStrategy ===
|
userTaskForm.value.candidateStrategy ===
|
||||||
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
|
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
|
||||||
) {
|
) {
|
||||||
// @ts-ignore
|
// @ts-expect-error: dynamic candidate param shape varies by strategy
|
||||||
userTaskForm.value.candidateParam = +candidateParamStr;
|
userTaskForm.value.candidateParam = +candidateParamStr;
|
||||||
deptLevel.value = +candidateParamStr;
|
deptLevel.value = +candidateParamStr;
|
||||||
} else if (
|
} else if (
|
||||||
|
|
@ -303,7 +304,7 @@ const openProcessExpressionDialog = async () => {
|
||||||
const selectProcessExpression = (
|
const selectProcessExpression = (
|
||||||
expression: BpmProcessExpressionApi.ProcessExpression,
|
expression: BpmProcessExpressionApi.ProcessExpression,
|
||||||
) => {
|
) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error: modal helper exposes runtime methods outside static typing
|
||||||
userTaskForm.value.candidateParam = [expression.expression];
|
userTaskForm.value.candidateParam = [expression.expression];
|
||||||
updateElementTask();
|
updateElementTask();
|
||||||
};
|
};
|
||||||
|
|
@ -311,7 +312,7 @@ const selectProcessExpression = (
|
||||||
const handleFormUserChange = (e: any) => {
|
const handleFormUserChange = (e: any) => {
|
||||||
if (e === 'PROCESS_START_USER_ID') {
|
if (e === 'PROCESS_START_USER_ID') {
|
||||||
userTaskForm.value.candidateParam = [];
|
userTaskForm.value.candidateParam = [];
|
||||||
// @ts-ignore
|
// @ts-expect-error: modal helper exposes runtime methods outside static typing
|
||||||
userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER;
|
userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER;
|
||||||
}
|
}
|
||||||
updateElementTask();
|
updateElementTask();
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
.element-listener-item {
|
.element-listener-item {
|
||||||
display: inline-grid;
|
display: inline-grid;
|
||||||
grid-template-columns: 16px auto 32px 32px;
|
grid-template-columns: 16px auto 32px 32px;
|
||||||
grid-column-gap: 8px;
|
column-gap: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import BpmnRenderer from 'bpmn-js/lib/draw/BpmnRenderer';
|
import BpmnRenderer from 'bpmn-js/lib/draw/BpmnRenderer';
|
||||||
|
|
||||||
export default function CustomRenderer(
|
function CustomRenderer(
|
||||||
config,
|
config,
|
||||||
eventBus,
|
eventBus,
|
||||||
styles,
|
styles,
|
||||||
|
|
@ -19,12 +19,10 @@ export default function CustomRenderer(
|
||||||
2000,
|
2000,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.handlers.label = function () {
|
this.handlers.label = () => null;
|
||||||
return null;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const F = function () {}; // 核心,利用空对象作为中介;
|
CustomRenderer.prototype = Object.create(BpmnRenderer.prototype);
|
||||||
F.prototype = BpmnRenderer.prototype; // 核心,将父类的原型赋值给空对象F;
|
CustomRenderer.prototype.constructor = CustomRenderer;
|
||||||
CustomRenderer.prototype = new F(); // 核心,将 F的实例赋值给子类;
|
|
||||||
CustomRenderer.prototype.constructor = CustomRenderer; // 修复子类CustomRenderer的构造器指向,防止原型链的混乱;
|
export default CustomRenderer;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import BpmnRules from 'bpmn-js/lib/features/rules/BpmnRules';
|
import BpmnRules from 'bpmn-js/lib/features/rules/BpmnRules';
|
||||||
import inherits from 'inherits';
|
import inherits from 'inherits';
|
||||||
|
|
||||||
export default function CustomRules(eventBus) {
|
function CustomRules(eventBus) {
|
||||||
BpmnRules.call(this, eventBus);
|
BpmnRules.call(this, eventBus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -14,3 +14,5 @@ CustomRules.prototype.canDrop = function () {
|
||||||
CustomRules.prototype.canMove = function () {
|
CustomRules.prototype.canMove = function () {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default CustomRules;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
function xmlStr2XmlObj(xmlStr) {
|
function xmlStr2XmlObj(xmlStr) {
|
||||||
|
// eslint-disable-next-line no-useless-assignment
|
||||||
let xmlObj = {};
|
let xmlObj = {};
|
||||||
if (document.all) {
|
if (document.all) {
|
||||||
const xmlDom = new window.ActiveXObject('Microsoft.XMLDOM');
|
const xmlDom = new window.ActiveXObject('Microsoft.XMLDOM');
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import {
|
||||||
|
|
||||||
import { getForm } from '#/api/bpm/form';
|
import { getForm } from '#/api/bpm/form';
|
||||||
import { getModelList } from '#/api/bpm/model';
|
import { getModelList } from '#/api/bpm/model';
|
||||||
|
import { parseFormFields } from '#/components/form-create';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE,
|
CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE,
|
||||||
|
|
@ -42,12 +43,7 @@ import {
|
||||||
TIME_UNIT_TYPES,
|
TIME_UNIT_TYPES,
|
||||||
TimeUnitType,
|
TimeUnitType,
|
||||||
} from '../../consts';
|
} from '../../consts';
|
||||||
import {
|
import { useFormFields, useNodeName, useWatchNode } from '../../helpers';
|
||||||
parseFormFields,
|
|
||||||
useFormFields,
|
|
||||||
useNodeName,
|
|
||||||
useWatchNode,
|
|
||||||
} from '../../helpers';
|
|
||||||
import { convertTimeUnit } from './utils';
|
import { convertTimeUnit } from './utils';
|
||||||
|
|
||||||
defineOptions({ name: 'ChildProcessNodeConfig' });
|
defineOptions({ name: 'ChildProcessNodeConfig' });
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ function changeNodeName() {
|
||||||
defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Drawer class="w-1/3">
|
<Drawer class="w-2/5">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Input
|
<Input
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
// 当前节点
|
// 当前节点
|
||||||
const currentNode = useWatchNode(props);
|
const currentNode = useWatchNode(props);
|
||||||
// 节点名称
|
// 节点名称
|
||||||
|
// @ts-expect-error: composable typing does not preserve this node schema exactly
|
||||||
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
|
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
|
||||||
useNodeName(BpmNodeTypeEnum.TRIGGER_NODE);
|
useNodeName(BpmNodeTypeEnum.TRIGGER_NODE);
|
||||||
// 触发器表单配置
|
// 触发器表单配置
|
||||||
|
|
@ -383,7 +384,7 @@ onMounted(() => {
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Drawer class="w-1/3">
|
<Drawer class="w-2/5">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="config-header">
|
<div class="config-header">
|
||||||
<Input
|
<Input
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue