Compare commits
1021 Commits
v2.0.9
...
feat-unify
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa84ee778c | ||
|
|
0eb75fbba4 | ||
|
|
6858f5bd39 | ||
|
|
b9f6ed95ab | ||
|
|
8039213380 | ||
|
|
c5a704767d | ||
|
|
20be7f2e45 | ||
|
|
fa476b6186 | ||
|
|
9c9bc98216 | ||
|
|
362456bdea | ||
|
|
ffceed63ca | ||
|
|
3cb4dbb12b | ||
|
|
5a981109de | ||
|
|
e6b7a868a2 | ||
|
|
0aa3f68f6d | ||
|
|
796bfd2c98 | ||
|
|
df0728ea12 | ||
|
|
7c745c5613 | ||
|
|
73090a6f1e | ||
|
|
e46ee17914 | ||
|
|
93e5b38491 | ||
|
|
37593bc257 | ||
|
|
b2eb54ca4b | ||
|
|
bfbc7edd31 | ||
|
|
a43b9fbdec | ||
|
|
5c4df5c824 | ||
|
|
5cc10c4198 | ||
|
|
b6d366e86d | ||
|
|
6e1740d2e2 | ||
|
|
2c6493105a | ||
|
|
c290d407a3 | ||
|
|
43713ba7dc | ||
|
|
5e28c513a4 | ||
|
|
d43823794d | ||
|
|
318a90f709 | ||
|
|
5326405a38 | ||
|
|
f8ec88ea64 | ||
|
|
0d5e6d6e53 | ||
|
|
c24e77d3af | ||
|
|
02e4ebf884 | ||
|
|
03b22f4af1 | ||
|
|
324edc27e1 | ||
|
|
cf63d5d086 | ||
|
|
5e09105a70 | ||
|
|
f5c3f51db2 | ||
|
|
7d2a98ab85 | ||
|
|
2d302bb468 | ||
|
|
789328b02e | ||
|
|
db9dca09e8 | ||
|
|
b93a2a516a | ||
|
|
3e10592a74 | ||
|
|
3e5e1a9b41 | ||
|
|
9ee62dffca | ||
|
|
778be20ddc | ||
|
|
23e76a93f4 | ||
|
|
29eee8fa48 | ||
|
|
8bf3c3bb75 | ||
|
|
4c2ca2b635 | ||
|
|
a20f540027 | ||
|
|
bd7d3fe23e | ||
|
|
f0f8c86b0d | ||
|
|
0138db1b4f | ||
|
|
a7d433b62d | ||
|
|
6f93b1f599 | ||
|
|
2a9983047a | ||
|
|
e81d7e8b20 | ||
|
|
2c0658ce50 | ||
|
|
f97112f117 | ||
|
|
7cf39a0e58 | ||
|
|
4c290d64ba | ||
|
|
9707e4af0f | ||
|
|
9161374320 | ||
|
|
8d81ed3870 | ||
|
|
ef7dbba3ce | ||
|
|
e9e5b798e7 | ||
|
|
d0f3490bd5 | ||
|
|
2036a509f0 | ||
|
|
3c802d44f4 | ||
|
|
96def0107d | ||
|
|
f96a9c9d6f | ||
|
|
5220cae94a | ||
|
|
4048d4a418 | ||
|
|
872382b4a3 | ||
|
|
77a763db75 | ||
|
|
73ce3a8551 | ||
|
|
a56ddc9415 | ||
|
|
122635248e | ||
|
|
01bf37ce38 | ||
|
|
4a75cc4d11 | ||
|
|
d0260041ce | ||
|
|
e3d5860a41 | ||
|
|
650ee93368 | ||
|
|
ac85b72be8 | ||
|
|
fd8aeba700 | ||
|
|
dad4d41894 | ||
|
|
144ef06cd1 | ||
|
|
885387cb5a | ||
|
|
dd13a0844b | ||
|
|
b66205eda9 | ||
|
|
6ea3f0701e | ||
|
|
1aade9c4b8 | ||
|
|
6f5d1d9459 | ||
|
|
a8211c6a41 | ||
|
|
752cb744c0 | ||
|
|
c22d559195 | ||
|
|
2c834c1d62 | ||
|
|
cc97672fcc | ||
|
|
e49cf3dea2 | ||
|
|
abbf678e89 | ||
|
|
634b5c2bea | ||
|
|
e559ca928a | ||
|
|
c8662537ca | ||
|
|
04539d605d | ||
|
|
ddf067560a | ||
|
|
7e036261e8 | ||
|
|
b083bd9724 | ||
|
|
0481d42f36 | ||
|
|
6bac8e10ba | ||
|
|
5fd6870cfe | ||
|
|
ce9d59896c | ||
|
|
e6da8d8b9f | ||
|
|
d8fcf4e7d3 | ||
|
|
56c3b9f935 | ||
|
|
7c871c775c | ||
|
|
c0e38e64db | ||
|
|
26eb3aef27 | ||
|
|
5d8d2cde3f | ||
|
|
369614af1e | ||
|
|
55d5f160a5 | ||
|
|
f25217c394 | ||
|
|
d7bc1b6d5c | ||
|
|
f101b476fb | ||
|
|
44a5e2dc4b | ||
|
|
1ece3072a0 | ||
|
|
8e1b56d74c | ||
|
|
ba0cc9cd82 | ||
|
|
1b5d0ee955 | ||
|
|
ae42dec906 | ||
|
|
31be6737f2 | ||
|
|
5987e83a12 | ||
|
|
73955aef09 | ||
|
|
0ddbdfce57 | ||
|
|
3af6bf3a4f | ||
|
|
3b8280ed3a | ||
|
|
644d761cba | ||
|
|
4d68a81ab4 | ||
|
|
b9e3053fe3 | ||
|
|
a48bdd08ff | ||
|
|
ad29bf5f26 | ||
|
|
e0bc13cccb | ||
|
|
aabc907864 | ||
|
|
df75da275b | ||
|
|
9004a55b8c | ||
|
|
3087606a5d | ||
|
|
c653006045 | ||
|
|
a6e7158800 | ||
|
|
97e81d8d99 | ||
|
|
99cad606e7 | ||
|
|
c4a7c673f8 | ||
|
|
c349e6384e | ||
|
|
b23d8504d2 | ||
|
|
662c2fd4dd | ||
|
|
ae77a32c87 | ||
|
|
d1b9c1764d | ||
|
|
4e298efaa0 | ||
|
|
deca505b9c | ||
|
|
185936467d | ||
|
|
c7cbb9b72a | ||
|
|
c3d1c4312c | ||
|
|
55119bee93 | ||
|
|
bd5e06350d | ||
|
|
be992ab9b1 | ||
|
|
352a97d5c1 | ||
|
|
491246205e | ||
|
|
f2087205c1 | ||
|
|
4f55b881db | ||
|
|
c311dd08e3 | ||
|
|
504a1f4703 | ||
|
|
2f24548cb1 | ||
|
|
a67cc9f876 | ||
|
|
92c7e1fce5 | ||
|
|
a2e2373dfd | ||
|
|
e0d2e8031d | ||
|
|
ef4beff52a | ||
|
|
7121a6b2f8 | ||
|
|
489560e670 | ||
|
|
bad3e31b0a | ||
|
|
854f7f15a4 | ||
|
|
d3d3cb440e | ||
|
|
c04f53a9c4 | ||
|
|
cfc2055af8 | ||
|
|
94ae80419d | ||
|
|
988153aaad | ||
|
|
77076136a6 | ||
|
|
628d66e80e | ||
|
|
d4ba828d08 | ||
|
|
05849a3d92 | ||
|
|
e7c227f4ae | ||
|
|
04cf44a528 | ||
|
|
c1d9702c58 | ||
|
|
884a70168d | ||
|
|
e24a029a78 | ||
|
|
c7d6403a09 | ||
|
|
6804a52e76 | ||
|
|
82a55b75b8 | ||
|
|
47aba057fd | ||
|
|
e6211cd523 | ||
|
|
9c9ab6c734 | ||
|
|
abfcbe27de | ||
|
|
bcec26374f | ||
|
|
a2bd41cd57 | ||
|
|
cab509b9a3 | ||
|
|
a5981a4387 | ||
|
|
0d26dce512 | ||
|
|
ee1b55cad9 | ||
|
|
27020b2920 | ||
|
|
99aea4b9d3 | ||
|
|
e1521a31ac | ||
|
|
97df12ee5f | ||
|
|
b9783b168f | ||
|
|
7b3012d8da | ||
|
|
455a9735e7 | ||
|
|
b277bd63c6 | ||
|
|
1c170bbb8a | ||
|
|
2234861cff | ||
|
|
0f30ac9694 | ||
|
|
b618ecfef2 | ||
|
|
56b96bfe44 | ||
|
|
c0bc170422 | ||
|
|
0f24b44269 | ||
|
|
0064a39403 | ||
|
|
4004b2e45e | ||
|
|
932aa038bb | ||
|
|
73488e9704 | ||
|
|
88764d77a8 | ||
|
|
94295a35bc | ||
|
|
530bb98782 | ||
|
|
47adc1378f | ||
|
|
a2389a5d99 | ||
|
|
b618f17978 | ||
|
|
563680f066 | ||
|
|
b462bd6f25 | ||
|
|
7cac99dd80 | ||
|
|
87d5c9ffb7 | ||
|
|
7fa4319bd1 | ||
|
|
05a048e5a8 | ||
|
|
a5dcc3d360 | ||
|
|
0193431c6d | ||
|
|
6c8d12f4e1 | ||
|
|
ceffd2ef0c | ||
|
|
1aa56d844a | ||
|
|
101a4ea27c | ||
|
|
8b1c775178 | ||
|
|
b3c1d83aed | ||
|
|
3adda1e3a4 | ||
|
|
610a8170ef | ||
|
|
9e21a1d036 | ||
|
|
305be7a187 | ||
|
|
1652bfedae | ||
|
|
6ebdceeafb | ||
|
|
5815ea9a0a | ||
|
|
c1bd7168e2 | ||
|
|
a10e9db14f | ||
|
|
50e789ee11 | ||
|
|
c48b7ddc10 | ||
|
|
6359ec15b3 | ||
|
|
b818df2ba0 | ||
|
|
7f49e83557 | ||
|
|
6d38d61816 | ||
|
|
f0e860f306 | ||
|
|
31f67232ae | ||
|
|
7fdc53e921 | ||
|
|
0624c60010 | ||
|
|
32ca138e6a | ||
|
|
171e0aa408 | ||
|
|
b1062d4673 | ||
|
|
f691bb8437 | ||
|
|
52009a415b | ||
|
|
80fc026483 | ||
|
|
b512328a70 | ||
|
|
709d8196fc | ||
|
|
5e2621ca7c | ||
|
|
f6086f40e0 | ||
|
|
520f46adc3 | ||
|
|
da809f13b3 | ||
|
|
be3e7b58b3 | ||
|
|
801aa1f82f | ||
|
|
f23433091a | ||
|
|
8fdb068023 | ||
|
|
0db49d055f | ||
|
|
72c26b71f3 | ||
|
|
3b8181ae10 | ||
|
|
a7edef16f4 | ||
|
|
c2a9642393 | ||
|
|
1b01fd81a8 | ||
|
|
3c04f81147 | ||
|
|
ec28ccf09c | ||
|
|
fa34300401 | ||
|
|
32743391f8 | ||
|
|
6aa439c991 | ||
|
|
33ed0fa468 | ||
|
|
c1b1436473 | ||
|
|
006b28ba38 | ||
|
|
c987dd8816 | ||
|
|
23bbac7318 | ||
|
|
4b21e598db | ||
|
|
26e77dd46c | ||
|
|
88728357d8 | ||
|
|
580f5b1a3f | ||
|
|
2236100e3d | ||
|
|
5dd6f88a42 | ||
|
|
57be88e8aa | ||
|
|
26d249f942 | ||
|
|
0d3585503b | ||
|
|
f70576bbdc | ||
|
|
dcb7806847 | ||
|
|
8832da10c9 | ||
|
|
46f93ffb86 | ||
|
|
96e427ee08 | ||
|
|
dd824a8ca6 | ||
|
|
ca2d70df6e | ||
|
|
7eba381512 | ||
|
|
498743e965 | ||
|
|
4460a3269b | ||
|
|
d2d35e717d | ||
|
|
774c005306 | ||
|
|
e51a635445 | ||
|
|
643eed4858 | ||
|
|
ceac00f3eb | ||
|
|
12e85b61d2 | ||
|
|
f539b09361 | ||
|
|
0aac4efa1b | ||
|
|
1fac9e5254 | ||
|
|
41454cda38 | ||
|
|
31a84e4d87 | ||
|
|
42659a7360 | ||
|
|
83f73aab8d | ||
|
|
2030fcc711 | ||
|
|
32488a03cd | ||
|
|
6d4cfe29ba | ||
|
|
451913f087 | ||
|
|
7d3267959f | ||
|
|
e6209cc008 | ||
|
|
f506ccd2e4 | ||
|
|
3eb3527226 | ||
|
|
f379f7652c | ||
|
|
2de57e93f5 | ||
|
|
0067d4fd5a | ||
|
|
e8760c81ac | ||
|
|
f47c890d2f | ||
|
|
a9bb294f60 | ||
|
|
2096665118 | ||
|
|
102e50be8f | ||
|
|
7ed8fbfcbf | ||
|
|
93b85f09d8 | ||
|
|
c05f851e02 | ||
|
|
072918fc6f | ||
|
|
7218fe57fc | ||
|
|
942ff14d82 | ||
|
|
dc5186fb69 | ||
|
|
857879d700 | ||
|
|
144b2aa81c | ||
|
|
3bfdfea016 | ||
|
|
e3984e672d | ||
|
|
6875bcdbb8 | ||
|
|
ca587c9ee3 | ||
|
|
2abbac8b52 | ||
|
|
0415a68352 | ||
|
|
5a259269ce | ||
|
|
ace8c1c411 | ||
|
|
b196b5aee2 | ||
|
|
3db217e687 | ||
|
|
4107477b9b | ||
|
|
ce0c439f06 | ||
|
|
5d9b3d5a9b | ||
|
|
20b196a12a | ||
|
|
54555ece08 | ||
|
|
4c38abd198 | ||
|
|
5e502eed6d | ||
|
|
1c54bd836a | ||
|
|
46f80ea907 | ||
|
|
0b5f35752b | ||
|
|
1688c6bf38 | ||
|
|
56a98c4d11 | ||
|
|
88bb915161 | ||
|
|
594c8f215f | ||
|
|
15166b6490 | ||
|
|
0bd22db048 | ||
|
|
7322817cb5 | ||
|
|
ec572f8f04 | ||
|
|
8654a45e9b | ||
|
|
10c4729383 | ||
|
|
437ad1bd9f | ||
|
|
74a03b905c | ||
|
|
a295cb44f0 | ||
|
|
cd20e68480 | ||
|
|
35968475b9 | ||
|
|
84c9e73e7b | ||
|
|
6ba668c5f8 | ||
|
|
55dcb43329 | ||
|
|
5ed7374592 | ||
|
|
578c177141 | ||
|
|
bd63169cbb | ||
|
|
831ae3a3db | ||
|
|
5cbd15992a | ||
|
|
d00ba1fdaa | ||
|
|
6a5fe679be | ||
|
|
6da5c613de | ||
|
|
8d0615bc95 | ||
|
|
503c9d6fca | ||
|
|
8a10a9cf82 | ||
|
|
78eb3ccc3f | ||
|
|
30b43cdb82 | ||
|
|
1d1dd07c2f | ||
|
|
7cccbabca4 | ||
|
|
c2aeb420b2 | ||
|
|
b10bc2edc4 | ||
|
|
cdccfa3e12 | ||
|
|
e8c5455748 | ||
|
|
2561c18dec | ||
|
|
ce6f618ba8 | ||
|
|
2d05e8054c | ||
|
|
3ad3d8d9a0 | ||
|
|
0e83597ddc | ||
|
|
8f4ae6f3ba | ||
|
|
3c65fa806e | ||
|
|
48a6b14537 | ||
|
|
de0404705b | ||
|
|
f13b3ca630 | ||
|
|
84361cb5f5 | ||
|
|
34e0b6e610 | ||
|
|
6c941ffed5 | ||
|
|
012eed6f67 | ||
|
|
3eac0d1592 | ||
|
|
ea8a2b59d9 | ||
|
|
8e40416e72 | ||
|
|
d058361e01 | ||
|
|
2491f7ff57 | ||
|
|
f84c72dc71 | ||
|
|
d735bc93ce | ||
|
|
4355c766fd | ||
|
|
2b47817d26 | ||
|
|
5f5b7bf2e3 | ||
|
|
c5908f6d92 | ||
|
|
7e64d30258 | ||
|
|
64d9fbb989 | ||
|
|
437818abce | ||
|
|
ffe7911d4b | ||
|
|
aad72ec5b6 | ||
|
|
d0436ec478 | ||
|
|
5568c3cd2d | ||
|
|
5197eb9292 | ||
|
|
a84f8f4411 | ||
|
|
3cf01f0c51 | ||
|
|
db815661e8 | ||
|
|
fcdc26e9eb | ||
|
|
fa061c42ad | ||
|
|
6edbb7c8a7 | ||
|
|
799c445c4b | ||
|
|
70fbf5de3e | ||
|
|
db3c38a333 | ||
|
|
73c7697443 | ||
|
|
919117c408 | ||
|
|
8092edab81 | ||
|
|
ef4f45ddb0 | ||
|
|
2ff867ba5f | ||
|
|
4f26fc2763 | ||
|
|
0c5b5f96f1 | ||
|
|
3f5f827550 | ||
|
|
3e969e4ae5 | ||
|
|
bcd1dfd7bd | ||
|
|
c1dfd78d18 | ||
|
|
39aa1645c9 | ||
|
|
797a3e3f5b | ||
|
|
53e740bb1d | ||
|
|
11d09c4d63 | ||
|
|
76bb4c4038 | ||
|
|
ccac647ecc | ||
|
|
39692e3b8b | ||
|
|
7aeebff5df | ||
|
|
62dc625363 | ||
|
|
1784e79c83 | ||
|
|
a5374ee746 | ||
|
|
a71bd16103 | ||
|
|
d3afea8c5e | ||
|
|
bfa16ec438 | ||
|
|
2499cbdd44 | ||
|
|
3cc3714e94 | ||
|
|
5abfcfdd1b | ||
|
|
ca8cac3f1a | ||
|
|
3dc745d4ad | ||
|
|
6c871b071f | ||
|
|
87bc0aaced | ||
|
|
3584c1c95c | ||
|
|
be0aa6d099 | ||
|
|
34d27c1004 | ||
|
|
6ee2bdcddb | ||
|
|
2a9d8e0845 | ||
|
|
47296f2607 | ||
|
|
6b913545db | ||
|
|
3836808d97 | ||
|
|
85f3b4e9e3 | ||
|
|
85beabe7c8 | ||
|
|
0109bcbfb9 | ||
|
|
0fb9a79df9 | ||
|
|
b39bcd864c | ||
|
|
6b805fbdfb | ||
|
|
abe76aeb59 | ||
|
|
5f49bc1aa8 | ||
|
|
54d2125067 | ||
|
|
7c86560f68 | ||
|
|
c7f5508e07 | ||
|
|
b7618693b4 | ||
|
|
b1870296c0 | ||
|
|
d3e1cd2fdb | ||
|
|
79a35423c7 | ||
|
|
71807be01d | ||
|
|
e74f796203 | ||
|
|
041d7da9d8 | ||
|
|
a99d949b29 | ||
|
|
c2a9e2a469 | ||
|
|
2e6c1f2431 | ||
|
|
80b05db764 | ||
|
|
ce28a96504 | ||
|
|
340cab11b7 | ||
|
|
712233c215 | ||
|
|
a84e922bc6 | ||
|
|
947ffb45d7 | ||
|
|
5dd14761b7 | ||
|
|
b9b915e913 | ||
|
|
044143571f | ||
|
|
06fe805736 | ||
|
|
c149ea9449 | ||
|
|
c003ce76ad | ||
|
|
077881e499 | ||
|
|
f655a57e61 | ||
|
|
0b04bf4929 | ||
|
|
2fcd3b1132 | ||
|
|
a8c2a989cb | ||
|
|
f287cf68ab | ||
|
|
190d2c60a8 | ||
|
|
b981640eae | ||
|
|
568fd6c105 | ||
|
|
221351ec11 | ||
|
|
6ab34a0996 | ||
|
|
6b551ae68b | ||
|
|
2917cde2e5 | ||
|
|
294a4a6cdf | ||
|
|
6ecd12ed07 | ||
|
|
8ec1626c1e | ||
|
|
23381fea37 | ||
|
|
1e678b7492 | ||
|
|
2ceca7287f | ||
|
|
7f4dd5295b | ||
|
|
cf1e4a0f8a | ||
|
|
ffd628fa82 | ||
|
|
d639e445eb | ||
|
|
b263681dda | ||
|
|
e16413b570 | ||
|
|
63b5186c3e | ||
|
|
3c6f6e70ef | ||
|
|
ba20316761 | ||
|
|
437dd1c411 | ||
|
|
674f748d20 | ||
|
|
d0d5f5b77d | ||
|
|
c095e15d6c | ||
|
|
efd9228cc9 | ||
|
|
ebd6132385 | ||
|
|
9098443c83 | ||
|
|
423890a4df | ||
|
|
15fc418fc4 | ||
|
|
fb2edeb7d2 | ||
|
|
b861587f01 | ||
|
|
c3738fab45 | ||
|
|
24fba75f28 | ||
|
|
00b6b63e1e | ||
|
|
5dab8fa7d9 | ||
|
|
2460fe4e86 | ||
|
|
9a5d71fb5c | ||
|
|
c930efba0c | ||
|
|
908f33c849 | ||
|
|
ff7c820b1f | ||
|
|
11b5eee86a | ||
|
|
4252725d9d | ||
|
|
deeb3a472c | ||
|
|
763173de44 | ||
|
|
f46e6ad8c8 | ||
|
|
09b31547e1 | ||
|
|
dee2ff8dee | ||
|
|
fc26544a85 | ||
|
|
0c4974ca1d | ||
|
|
7f315b95ce | ||
|
|
093e7d1edb | ||
|
|
00ee150c5d | ||
|
|
a4e18f8893 | ||
|
|
c754a36ee5 | ||
|
|
a07d4daed5 | ||
|
|
39023832e3 | ||
|
|
5b8dda60bd | ||
|
|
f7ade54e93 | ||
|
|
ea40cce9a4 | ||
|
|
2524b0ad2d | ||
|
|
4ae7e9dd2e | ||
|
|
8220f4b366 | ||
|
|
7e237b508a | ||
|
|
28bb82e579 | ||
|
|
bcabcb0831 | ||
|
|
c24b03f3da | ||
|
|
63dd03e755 | ||
|
|
f3f1f57b01 | ||
|
|
8da1a074a2 | ||
|
|
8fd7561971 | ||
|
|
c69f9b09ec | ||
|
|
875c267181 | ||
|
|
acd705da88 | ||
|
|
6ade215ea5 | ||
|
|
cabd46268e | ||
|
|
93540d4234 | ||
|
|
7564f816fc | ||
|
|
278782b0f8 | ||
|
|
d604f97b4c | ||
|
|
18a1304e5c | ||
|
|
ce308cf772 | ||
|
|
cb3b7e424e | ||
|
|
18f799cfd6 | ||
|
|
1c6c32f5f7 | ||
|
|
5983fa4de0 | ||
|
|
74c6443a86 | ||
|
|
4bb3739588 | ||
|
|
7f5153f5fa | ||
|
|
2c0b1955af | ||
|
|
290150fc5e | ||
|
|
6e1921d9eb | ||
|
|
d75697af0c | ||
|
|
01bb9a8939 | ||
|
|
df7e07c3e8 | ||
|
|
50907035b8 | ||
|
|
b4da5a925d | ||
|
|
00f392eae0 | ||
|
|
eb14531567 | ||
|
|
18b6acaed1 | ||
|
|
b2437e56ca | ||
|
|
348de945e3 | ||
|
|
3ec36194c7 | ||
|
|
f0f414400f | ||
|
|
79a438bc96 | ||
|
|
caf8586c7e | ||
|
|
c168eaa58e | ||
|
|
0ef0b65b02 | ||
|
|
d3caa77ba3 | ||
|
|
84864b759c | ||
|
|
6169d841ee | ||
|
|
e9363df523 | ||
|
|
99517313f7 | ||
|
|
219f57f037 | ||
|
|
d183d1120e | ||
|
|
1cafb9e84b | ||
|
|
e4776a1522 | ||
|
|
56e82d96c6 | ||
|
|
57dba3f9a2 | ||
|
|
3836131a27 | ||
|
|
dd8ad88f96 | ||
|
|
7b262d8b5b | ||
|
|
a151d12a99 | ||
|
|
a913fd9b2f | ||
|
|
c67df66098 | ||
|
|
5badafce62 | ||
|
|
5d48eea29b | ||
|
|
b6893d1208 | ||
|
|
b402969a44 | ||
|
|
3d1ed95bed | ||
|
|
4b2370f229 | ||
|
|
ab93fa0adf | ||
|
|
b608897742 | ||
|
|
0681e9217d | ||
|
|
6ed7c130cb | ||
|
|
7b8d996ef6 | ||
|
|
0423d66596 | ||
|
|
829a98e4e1 | ||
|
|
4e7e1e62b6 | ||
|
|
b678e389df | ||
|
|
5b88c7dae5 | ||
|
|
ca7bd08b57 | ||
|
|
afaccad16c | ||
|
|
98ce49ee03 | ||
|
|
66b4c457bf | ||
|
|
f0751499b3 | ||
|
|
a43d5778cb | ||
|
|
9d3f53c315 | ||
|
|
e02dad5e2c | ||
|
|
b4972eed39 | ||
|
|
f641889edd | ||
|
|
a0d0a5e214 | ||
|
|
2222af12c3 | ||
|
|
9ba6d71780 | ||
|
|
8c297737c0 | ||
|
|
8e815e90ab | ||
|
|
03628fd46b | ||
|
|
d067560776 | ||
|
|
b4aa89e810 | ||
|
|
0f0c631f2a | ||
|
|
28c4a133a6 | ||
|
|
5abdddc2e6 | ||
|
|
defb71569d | ||
|
|
efba045ad0 | ||
|
|
77a6b50307 | ||
|
|
dc7e3f5a06 | ||
|
|
3c6c059e2a | ||
|
|
00ba6554b1 | ||
|
|
dc347aeb0f | ||
|
|
15564813bc | ||
|
|
7ebe986b17 | ||
|
|
9f0ee71310 | ||
|
|
6f22bc6127 | ||
|
|
e9c263728b | ||
|
|
100ec84d57 | ||
|
|
8a30a763f0 | ||
|
|
295115b6be | ||
|
|
383b7c3cd5 | ||
|
|
895d1c8ac8 | ||
|
|
0b2a7920ae | ||
|
|
3a698e96be | ||
|
|
a5f54fd31f | ||
|
|
123b5a6073 | ||
|
|
d1bb15b883 | ||
|
|
c7a9c24871 | ||
|
|
0d5a592e1d | ||
|
|
68a37cc95d | ||
|
|
6706710d3c | ||
|
|
8be1eff5b4 | ||
|
|
69fca60836 | ||
|
|
5904d04e2e | ||
|
|
5e6ecfc3e5 | ||
|
|
879c1f3332 | ||
|
|
b36cf2bc7e | ||
|
|
70e606c071 | ||
|
|
77aae79bac | ||
|
|
2fafb3d7de | ||
|
|
38a49d68e5 | ||
|
|
525941512e | ||
|
|
079fc2bdb9 | ||
|
|
34177e00af | ||
|
|
bb2b11edf2 | ||
|
|
a41104118e | ||
|
|
f2d32d8256 | ||
|
|
5d585994ca | ||
|
|
536faa2778 | ||
|
|
793f4098b7 | ||
|
|
0d482ad9c4 | ||
|
|
eee2611c4e | ||
|
|
9d04515977 | ||
|
|
19c62d7b78 | ||
|
|
5861e38056 | ||
|
|
6cd2a06974 | ||
|
|
f0b21b3bf9 | ||
|
|
7654d29d10 | ||
|
|
c03b4c0263 | ||
|
|
daf5537aea | ||
|
|
b2ed8a57a7 | ||
|
|
7a1d64c7e2 | ||
|
|
504c461c57 | ||
|
|
2725f136fe | ||
|
|
00af3f90d8 | ||
|
|
a4c1b08ca1 | ||
|
|
8f419cbbbb | ||
|
|
8110173d8a | ||
|
|
70b4af4a65 | ||
|
|
851a4d58ec | ||
|
|
cd360a1833 | ||
|
|
96ea54a413 | ||
|
|
0d873e4ffa | ||
|
|
4b21f3a753 | ||
|
|
7d5f607b92 | ||
|
|
d7185522c0 | ||
|
|
f5ca879bff | ||
|
|
ddb541ade9 | ||
|
|
4cb9597ad9 | ||
|
|
1ae22cafa3 | ||
|
|
06f80a4645 | ||
|
|
7d744bb621 | ||
|
|
bb8f7c5ed4 | ||
|
|
aec646b5b0 | ||
|
|
91e041ba97 | ||
|
|
ab487d2a1d | ||
|
|
0faf7e24af | ||
|
|
ef74947572 | ||
|
|
b5e862e740 | ||
|
|
0053daed2f | ||
|
|
43b9b72369 | ||
|
|
55c500f34b | ||
|
|
10c6176a16 | ||
|
|
b98aeb1976 | ||
|
|
6b8b1e43ae | ||
|
|
2d7db3f0f6 | ||
|
|
c42e9ad0fb | ||
|
|
559bf2fe57 | ||
|
|
75291a9feb | ||
|
|
9ca618c3c5 | ||
|
|
bf020e3d3e | ||
|
|
d9ee41c892 | ||
|
|
0cb3cb3eae | ||
|
|
3d72d58e87 | ||
|
|
bdf7527824 | ||
|
|
2651b21b45 | ||
|
|
285fff6add | ||
|
|
80c5856732 | ||
|
|
15b908773d | ||
|
|
1b133890b0 | ||
|
|
b8db165f6e | ||
|
|
a45036c935 | ||
|
|
fe4184c6c4 | ||
|
|
5f7ebcf233 | ||
|
|
94672221e5 | ||
|
|
09e3140c0d | ||
|
|
c4971a9954 | ||
|
|
b5d54ed173 | ||
|
|
f4df648e5a | ||
|
|
b60843d3ed | ||
|
|
db0641c937 | ||
|
|
109a1a8d7a | ||
|
|
8cb8e6407a | ||
|
|
859024056a | ||
|
|
58688e6e93 | ||
|
|
e2e72ff9fd | ||
|
|
c3e5117b31 | ||
|
|
d37f316a86 | ||
|
|
41e7dc7ab1 | ||
|
|
524bea8744 | ||
|
|
ea75f161a7 | ||
|
|
e6a7ad9776 | ||
|
|
da38db1f4e | ||
|
|
adff55f89a | ||
|
|
55c8314613 | ||
|
|
a96df3cc13 | ||
|
|
4c6ed77806 | ||
|
|
5b5f59da91 | ||
|
|
5694f61e4d | ||
|
|
87b033c633 | ||
|
|
06caa046fa | ||
|
|
98100be99d | ||
|
|
f25ec3d6a7 | ||
|
|
a33e2f29e3 | ||
|
|
9bd14ebc0c | ||
|
|
5c595fdedf | ||
|
|
b9b7c75627 | ||
|
|
c2e01b3907 | ||
|
|
9295b93116 | ||
|
|
b0f5fd826c | ||
|
|
fb89f9b85b | ||
|
|
5c797b738e | ||
|
|
b817ee7f80 | ||
|
|
33174f8208 | ||
|
|
41798a2ec6 | ||
|
|
bea36accff | ||
|
|
6f74838e1e | ||
|
|
b2e255bb42 | ||
|
|
1028ea0302 | ||
|
|
f767e9aaba | ||
|
|
620c83b1e0 | ||
|
|
19b7853057 | ||
|
|
d1db7ad494 | ||
|
|
5298b8267f | ||
|
|
7f729d141f | ||
|
|
1f405b5055 | ||
|
|
516723e252 | ||
|
|
6e0ab15e47 | ||
|
|
977f1cade0 | ||
|
|
4075064384 | ||
|
|
26315f3424 | ||
|
|
f2af017925 | ||
|
|
e3575d0f43 | ||
|
|
cee302f208 | ||
|
|
922afd8310 | ||
|
|
cab18e29f5 | ||
|
|
6dbe603fcd | ||
|
|
9ed848e21f | ||
|
|
4e9e492c29 | ||
|
|
f8095ca52a | ||
|
|
211e7b08c4 | ||
|
|
6678b2858d | ||
|
|
8e9905816e | ||
|
|
edfdcee3a4 | ||
|
|
8fce610ec7 | ||
|
|
3923b49c9e | ||
|
|
1b7ee5cb2e | ||
|
|
166b2bb98f | ||
|
|
2690b06eff | ||
|
|
14010cce8f | ||
|
|
2bdc489827 | ||
|
|
b8639ceb7e | ||
|
|
eb685d3af4 | ||
|
|
6f68cc58d3 | ||
|
|
b550c3e541 | ||
|
|
40a129aafd | ||
|
|
c071cb5a49 | ||
|
|
adb5f353bf | ||
|
|
52418d7f70 | ||
|
|
3745e2afcd | ||
|
|
aa71bb545e | ||
|
|
d74fb21c9e | ||
|
|
b980c79ab4 | ||
|
|
f91b1d7f8a | ||
|
|
0b58dd4d17 | ||
|
|
c968853092 | ||
|
|
b32e06e03b | ||
|
|
69faf8db75 | ||
|
|
d689698ddd | ||
|
|
7e4eb8eed2 | ||
|
|
7d01528703 | ||
|
|
88f9f9efe1 | ||
|
|
23f09d33a2 | ||
|
|
de1652973c | ||
|
|
830d475215 | ||
|
|
c8437fd3ea | ||
|
|
df2c9eea98 | ||
|
|
09e253e833 | ||
|
|
9d541d5ac3 | ||
|
|
6d04bb8eea | ||
|
|
ec5e74019b | ||
|
|
8f15b0a32f | ||
|
|
0ce954588e | ||
|
|
0a7d817e60 | ||
|
|
483d1eb5e8 | ||
|
|
27f416a46e | ||
|
|
5e2f0ab6df | ||
|
|
c3096ed486 | ||
|
|
abaf8f07ad | ||
|
|
47a7ce9d6e | ||
|
|
41711b174f | ||
|
|
9876c7a802 | ||
|
|
cd91047b6e | ||
|
|
c5b1380299 | ||
|
|
7ec0af42b8 | ||
|
|
b9501cee37 | ||
|
|
c92510863f | ||
|
|
47456fb801 | ||
|
|
c6a7be352b | ||
|
|
0a223afab2 | ||
|
|
9f4e893290 | ||
|
|
ce6843f6a2 | ||
|
|
ae78ced3dd | ||
|
|
1749680268 | ||
|
|
379a6d259f | ||
|
|
ab8c84e1b9 | ||
|
|
b3d723d323 | ||
|
|
9659027502 | ||
|
|
9a1e23c661 | ||
|
|
66bcf066e0 | ||
|
|
5e07c2ca6d | ||
|
|
f57fa702d9 | ||
|
|
f44d119f16 | ||
|
|
be28b2990f | ||
|
|
a71180b71b | ||
|
|
1e915bc873 | ||
|
|
8faa6d516c | ||
|
|
43e557253f | ||
|
|
7a3af81d7a | ||
|
|
bbeba4a8d8 | ||
|
|
953b8c3486 | ||
|
|
0fc4d44ba2 | ||
|
|
e1a41a2853 | ||
|
|
3e4b3001ef | ||
|
|
5db0119bb6 | ||
|
|
4fc4cbf593 | ||
|
|
a8338ec49e | ||
|
|
20a1866923 | ||
|
|
cb14424110 | ||
|
|
56d00831d3 | ||
|
|
ec5fccb945 | ||
|
|
7262c4af22 | ||
|
|
d54c9d2388 | ||
|
|
5550e75893 | ||
|
|
6af050efb2 | ||
|
|
fec6c6f23e | ||
|
|
5843388d0b | ||
|
|
81881a8ed4 | ||
|
|
4bf319dcb4 | ||
|
|
d2fabeae5f | ||
|
|
b7a372e50f | ||
|
|
6850dd467d | ||
|
|
c4953e7afd | ||
|
|
0f7fbfcce8 | ||
|
|
8bdacd82c7 | ||
|
|
b03a495712 | ||
|
|
95e2cdc689 | ||
|
|
ea32cb1810 | ||
|
|
3a87f0dd30 | ||
|
|
5030cfe099 | ||
|
|
67b613dabf | ||
|
|
e8057361d6 | ||
|
|
7200242386 | ||
|
|
d5d5a72126 | ||
|
|
c582f72ea7 | ||
|
|
f665b75fc4 | ||
|
|
a0fdd752d4 | ||
|
|
072b8668fd | ||
|
|
9c74c26219 | ||
|
|
8c64abbe51 | ||
|
|
3d9cd8013d | ||
|
|
dcb2214c04 | ||
|
|
b349f3440b | ||
|
|
c8098f429d | ||
|
|
7c1bba409a | ||
|
|
fcc6b20fb6 | ||
|
|
698b5264ba | ||
|
|
a1d743aa74 | ||
|
|
1a659d4a60 | ||
|
|
2194ca5cd6 | ||
|
|
d9641d6690 | ||
|
|
291290d7f1 | ||
|
|
5cbdbf137d | ||
|
|
5cfdcc68b7 | ||
|
|
8951dcb60d | ||
|
|
4647caf383 | ||
|
|
1ae9197d89 | ||
|
|
0881225fda | ||
|
|
3ee85b7c7b | ||
|
|
11763d14c7 | ||
|
|
cf4e15c31e | ||
|
|
035dac762c | ||
|
|
a8de985e88 |
175
README.md
@@ -4,124 +4,71 @@
|
||||
|
||||
**`master-fetch` 分支是带有后端接口请求的分支**
|
||||
|
||||
**后端项目地址:[https://gitee.com/MTrun/go-view-serve](https://gitee.com/MTrun/go-view-serve)**
|
||||
|
||||
**接口说明地址:[https://docs.apipost.cn/preview/5aa85d10a59d66ce/ddb813732007ad2b?target_id=84dbc5b0-158f-4bcb-8f74-793ac604ada3#3e053622-1e76-43f9-a039-756aee822dbb](https://docs.apipost.cn/preview/5aa85d10a59d66ce/ddb813732007ad2b?target_id=84dbc5b0-158f-4bcb-8f74-793ac604ada3#3e053622-1e76-43f9-a039-756aee822dbb)**
|
||||
### feat-unify-test 分支目标
|
||||
+ 实现 backend 后端工厂
|
||||
将后端业务逻辑集中到 backend 了,控制 BackEndFactory 就可以适配不同的后端。
|
||||
伪代码如下:
|
||||
export const BackEndFactory = ():IBackend=>{
|
||||
switch(项目后端配置){
|
||||
case "无数据库":
|
||||
return new MockBackend() // 等同: -master ,没有存储
|
||||
case "indexdb":
|
||||
return new IndexDbBackend() // 这次开发的,用 indexdb 做测试
|
||||
case "java":
|
||||
return new JavaBackend() // 等同: -fetch, 没 java 环境,还没做
|
||||
case "python":
|
||||
return new PythonBackend() // 自定义开发的后端
|
||||
。。。 其他 oss 、云平台的后端 。。。
|
||||
}}
|
||||
意义:
|
||||
1 unify 统一 -fetch 和 master 分支,消除分支之间的差异。
|
||||
2 方便接入不同的自定义后端平台。
|
||||
3 前端存储功能让测试工作更加方便
|
||||
|
||||
## 使用
|
||||
+ 完善事件处理机制
|
||||
在事件中修改图表配置
|
||||
在事件中修改图表数据
|
||||
在事件中调用图表 exposed 函数
|
||||
数据驱动界面
|
||||
|
||||
所有的接口地址位置:`src\api\path\*`
|
||||
### 试验功能1:Backend 后端工厂
|
||||
+ 对比 -fetch 分支,梳理后端逻辑到 backend 目录的 ibackend 接口
|
||||
+ 登录 - login
|
||||
+ 登出 - logout
|
||||
+ 预览,token 注入或单点登陆 - checkToken
|
||||
+ 显示项目列表和分页 - projectList
|
||||
+ 保存、发布、修改名称 - updateProject
|
||||
+ 复制项目 - copyProject
|
||||
+ 图表内的图片上传 - uploadFile
|
||||
+ 上传图片显示处理 - getFileUrl
|
||||
+ IndexDbBackend 用indexdb浏览器数据库实现了 project 相关所有功能。
|
||||
+ Todo: 统一后端错误处理
|
||||
+ Todo:开发 javabackend,适配现有的后端
|
||||
|
||||
接口地址修改:`.env`
|
||||
### 试验功能2:事件处理机制
|
||||
+ 实现最常用的互动:找到图表元素、显示或隐藏、修改数据
|
||||
+ 核心代码:useLifeHandler.hook.ts
|
||||
+ 在事件代码中通过 runtime 实现运行时刻的图表管理,提供基础函数:
|
||||
+ selectComponents 选择多个图表
|
||||
+ selectOneComponent 选择一个图表
|
||||
+ getChartConfig 读取图表
|
||||
+ setChartConfig 设置图表
|
||||
+ callExposed 调用图表 exposed 的函数
|
||||
+ 以下例子可以在点击事件中加入代码并预览,测试效果。
|
||||
|
||||
### 🤯 后端项目
|
||||
+ 例子1 切换显示名称为 饼图 和 柱状图 的图表:
|
||||
const range = runtime.fn.selectComponents("饼图 柱状图")
|
||||
const h = runtime.fn.getChartConfig(range, "hide")
|
||||
runtime.fn.setChartConfig(range, "hide", !h)
|
||||
|
||||
后端项目gitee地址:[https://gitee.com/MTrun/go-view-serve](https://gitee.com/MTrun/go-view-serve)
|
||||
+ 例子2 修改一个名称 柱状图001 组件id 2wolqibrx3c000 的图表数据,以下两句等效
|
||||
runtime.fn.setChartConfig("柱状图001", "dataset", {"dimensions":["product","data1","data2"],"source":[{"product":"Mon","data1":120,"data2":130}]})
|
||||
runtime.fn.setChartConfig("#2wolqibrx3c000", "dataset", {"dimensions":["product","data1","data2"],"source":[{"product":"Mon","data1":120,"data2":230}]})
|
||||
|
||||
接口说明地址:[https://docs.apipost.cn/preview/5aa85d10a59d66ce/ddb813732007ad2b?target_id=84dbc5b0-158f-4bcb-8f74-793ac604ada3#3e053622-1e76-43f9-a039-756aee822dbb](https://docs.apipost.cn/preview/5aa85d10a59d66ce/ddb813732007ad2b?target_id=84dbc5b0-158f-4bcb-8f74-793ac604ada3#3e053622-1e76-43f9-a039-756aee822dbb)
|
||||
|
||||
```shell
|
||||
# port
|
||||
VITE_DEV_PORT = '8080'
|
||||
|
||||
# development path
|
||||
VITE_DEV_PATH = 'http://127.0.0.1:8080'
|
||||
|
||||
# production path
|
||||
VITE_PRO_PATH = 'http://127.0.0.1:8080'
|
||||
```
|
||||
|
||||
公共前缀修改:`src\settings\httpSetting.ts`
|
||||
|
||||
```shell
|
||||
// 请求前缀
|
||||
export const axiosPre = '/api/goview'
|
||||
```
|
||||
|
||||
接口封装:`src\api\http.ts`
|
||||
|
||||
```ts
|
||||
import axiosInstance from './axios'
|
||||
import { RequestHttpEnum, ContentTypeEnum } from '@/enums/httpEnum'
|
||||
|
||||
export const get = (url: string, params?: object) => {
|
||||
return axiosInstance({
|
||||
url: url,
|
||||
method: RequestHttpEnum.GET,
|
||||
params: params,
|
||||
})
|
||||
}
|
||||
|
||||
export const post = (url: string, data?: object, headersType?: string) => {
|
||||
return axiosInstance({
|
||||
url: url,
|
||||
method: RequestHttpEnum.POST,
|
||||
data: data,
|
||||
headers: {
|
||||
'Content-Type': headersType || ContentTypeEnum.JSON
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const put = (url: string, data?: object, headersType?: string) => {
|
||||
return axiosInstance({
|
||||
url: url,
|
||||
method: RequestHttpEnum.PUT,
|
||||
data: data,
|
||||
headers: {
|
||||
'Content-Type': headersType || ContentTypeEnum.JSON
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const del = (url: string, params?: object) => {
|
||||
return axiosInstance({
|
||||
url: url,
|
||||
method: RequestHttpEnum.DELETE,
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 获取请求函数,默认get
|
||||
export const http = (type?: RequestHttpEnum) => {
|
||||
switch (type) {
|
||||
case RequestHttpEnum.GET:
|
||||
return get
|
||||
|
||||
case RequestHttpEnum.POST:
|
||||
return post
|
||||
|
||||
case RequestHttpEnum.PUT:
|
||||
return put
|
||||
|
||||
case RequestHttpEnum.DELETE:
|
||||
return del
|
||||
|
||||
default:
|
||||
return get
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 代码提交
|
||||
|
||||
* feat: 新功能
|
||||
* fix: 修复 Bug
|
||||
* docs: 文档修改
|
||||
* perf: 性能优化
|
||||
* revert: 版本回退
|
||||
* ci: CICD集成相关
|
||||
* test: 添加测试代码
|
||||
* refactor: 代码重构
|
||||
* build: 影响项目构建或依赖修改
|
||||
* style: 不影响程序逻辑的代码修改
|
||||
* chore: 不属于以上类型的其他类型(日常事务)
|
||||
|
||||
## 交流
|
||||
|
||||
QQ 群:1030129384
|
||||
|
||||

|
||||
|
||||

|
||||
+ 例子3 找到一个组并隐藏
|
||||
const c = runtime.fn.selectOneComponent("分组")
|
||||
if(c){
|
||||
console.log(runtime.fn.getChartConfig(c, "isGroup" ))
|
||||
runtime.fn.setChartConfig(c, "hide", true)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "go-view",
|
||||
"version": "2.0.91",
|
||||
"version": "2.1.0",
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
@@ -22,6 +22,7 @@
|
||||
"echarts-liquidfill": "^3.1.0",
|
||||
"echarts-stat": "^1.2.0",
|
||||
"echarts-wordcloud": "^2.0.0",
|
||||
"gsap": "^3.11.3",
|
||||
"highlight.js": "^11.5.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"keymaster": "^1.6.2",
|
||||
@@ -29,6 +30,7 @@
|
||||
"naive-ui": "2.33.4",
|
||||
"pinia": "^2.0.13",
|
||||
"screenfull": "^6.0.1",
|
||||
"three": "^0.145.0",
|
||||
"vue": "^3.2.31",
|
||||
"vue-demi": "^0.13.1",
|
||||
"vue-i18n": "9.1.10",
|
||||
@@ -41,6 +43,7 @@
|
||||
"@commitlint/cli": "^17.0.2",
|
||||
"@commitlint/config-conventional": "^17.0.2",
|
||||
"@types/node": "^16.11.26",
|
||||
"@types/three": "^0.144.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||
"@typescript-eslint/parser": "^5.18.0",
|
||||
"@vicons/carbon": "^0.12.0",
|
||||
|
||||
30
pnpm-lock.yaml
generated
@@ -8,6 +8,7 @@ specifiers:
|
||||
'@types/keymaster': ^1.6.30
|
||||
'@types/lodash': ^4.14.184
|
||||
'@types/node': ^16.11.26
|
||||
'@types/three': ^0.144.0
|
||||
'@typescript-eslint/eslint-plugin': ^5.18.0
|
||||
'@typescript-eslint/parser': ^5.18.0
|
||||
'@vicons/carbon': ^0.12.0
|
||||
@@ -31,6 +32,7 @@ specifiers:
|
||||
eslint-plugin-import: ^2.26.0
|
||||
eslint-plugin-prettier: ^4.0.0
|
||||
eslint-plugin-vue: ^8.5.0
|
||||
gsap: ^3.11.3
|
||||
highlight.js: ^11.5.0
|
||||
html2canvas: ^1.4.1
|
||||
husky: ^8.0.1
|
||||
@@ -45,6 +47,7 @@ specifiers:
|
||||
sass: ^1.49.11
|
||||
sass-loader: ^12.6.0
|
||||
screenfull: ^6.0.1
|
||||
three: ^0.145.0
|
||||
typescript: 4.6.3
|
||||
vite: 2.9.9
|
||||
vite-plugin-compression: ^0.5.1
|
||||
@@ -73,6 +76,7 @@ dependencies:
|
||||
echarts-liquidfill: 3.1.0_echarts@5.3.3
|
||||
echarts-stat: 1.2.0
|
||||
echarts-wordcloud: 2.0.0_echarts@5.3.3
|
||||
gsap: 3.11.3
|
||||
highlight.js: 11.5.1
|
||||
html2canvas: 1.4.1
|
||||
keymaster: 1.6.2
|
||||
@@ -80,6 +84,7 @@ dependencies:
|
||||
naive-ui: 2.33.4_vue@3.2.37
|
||||
pinia: 2.0.14_ub5l46u3nefphax5x2tezui4oq
|
||||
screenfull: 6.0.1
|
||||
three: 0.145.0
|
||||
vue: 3.2.37
|
||||
vue-demi: 0.13.1_vue@3.2.37
|
||||
vue-i18n: 9.1.10_vue@3.2.37
|
||||
@@ -92,6 +97,7 @@ devDependencies:
|
||||
'@commitlint/cli': 17.0.2
|
||||
'@commitlint/config-conventional': 17.0.2
|
||||
'@types/node': 16.11.40
|
||||
'@types/three': 0.144.0
|
||||
'@typescript-eslint/eslint-plugin': 5.28.0_evi7yu7wunhzwb24olrfvzynny
|
||||
'@typescript-eslint/parser': 5.28.0_sfmgizikprcxt7r54j7cnzjamu
|
||||
'@vicons/carbon': 0.12.0
|
||||
@@ -905,7 +911,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/@types/node/17.0.43:
|
||||
resolution: {integrity: sha512-jnUpgw8fL9kP2iszfIDyBQtw5Mf4/XSqy0Loc1J9pI14ejL83XcCEvSf50Gs/4ET0I9VCCDoOfufQysj0S66xA==, registry: https://registry.npm.taobao.org/}
|
||||
resolution: {integrity: sha512-jnUpgw8fL9kP2iszfIDyBQtw5Mf4/XSqy0Loc1J9pI14ejL83XcCEvSf50Gs/4ET0I9VCCDoOfufQysj0S66xA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/@types/node/-/node-17.0.43.tgz}
|
||||
|
||||
/@types/normalize-package-data/2.4.1:
|
||||
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
|
||||
@@ -921,12 +927,22 @@ packages:
|
||||
'@types/node': 17.0.43
|
||||
dev: true
|
||||
|
||||
/@types/three/0.144.0:
|
||||
resolution: {integrity: sha512-psvEs6q5rLN50jUYZ3D4pZMfxTbdt3A243blt0my7/NcL6chaCZpHe2csbCtx0SOD9fI/XnF3wnVUAYZGqCSYg==}
|
||||
dependencies:
|
||||
'@types/webxr': 0.5.0
|
||||
dev: true
|
||||
|
||||
/@types/through/0.0.30:
|
||||
resolution: {integrity: sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==}
|
||||
dependencies:
|
||||
'@types/node': 17.0.43
|
||||
dev: true
|
||||
|
||||
/@types/webxr/0.5.0:
|
||||
resolution: {integrity: sha512-IUMDPSXnYIbEO2IereEFcgcqfDREOgmbGqtrMpVPpACTU6pltYLwHgVkrnYv0XhWEcjio9sYEfIEzgn3c7nDqA==}
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/eslint-plugin/5.28.0_evi7yu7wunhzwb24olrfvzynny:
|
||||
resolution: {integrity: sha512-DXVU6Cg29H2M6EybqSg2A+x8DgO9TCUBRp4QEXQHJceLS7ogVDP0g3Lkg/SZCqcvkAP/RruuQqK0gdlkgmhSUA==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
@@ -1884,7 +1900,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/csstype/2.6.20:
|
||||
resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==, registry: https://registry.npm.taobao.org/}
|
||||
resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/csstype/-/csstype-2.6.20.tgz}
|
||||
dev: false
|
||||
|
||||
/csstype/3.0.11:
|
||||
@@ -2091,7 +2107,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
/echarts-wordcloud/2.0.0_echarts@5.3.3:
|
||||
resolution: {integrity: sha512-K7l6pTklqdW7ZWzT/1CS0KhBSINr/cd7c5N1fVMzZMwLQHEwT7x+nivK7g5hkVh7WNcAv4Dn6/ZS5zMKRozC1g==, registry: https://registry.npm.taobao.org/}
|
||||
resolution: {integrity: sha512-K7l6pTklqdW7ZWzT/1CS0KhBSINr/cd7c5N1fVMzZMwLQHEwT7x+nivK7g5hkVh7WNcAv4Dn6/ZS5zMKRozC1g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/echarts-wordcloud/-/echarts-wordcloud-2.0.0.tgz}
|
||||
peerDependencies:
|
||||
echarts: ^5.0.1
|
||||
dependencies:
|
||||
@@ -3032,6 +3048,10 @@ packages:
|
||||
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
|
||||
dev: true
|
||||
|
||||
/gsap/3.11.3:
|
||||
resolution: {integrity: sha512-xc/iIJy+LWiMbRa4IdMtdnnKa/7PXEK6NNzV71gdOYUVeTZN7UWnLU0fB7Hi1iwiz4ZZoYkBZPPYGg+2+zzFHA==}
|
||||
dev: false
|
||||
|
||||
/handlebars/4.7.7:
|
||||
resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==}
|
||||
engines: {node: '>=0.4.7'}
|
||||
@@ -4825,6 +4845,10 @@ packages:
|
||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||
dev: true
|
||||
|
||||
/three/0.145.0:
|
||||
resolution: {integrity: sha512-EKoHQEtEJ4CB6b2BGMBgLZrfwLjXcSUfoI/MiIXUuRpeYsfK5aPWbYhdtIVWOH+x6X0TouldHKHBuc/LAiFzAw==}
|
||||
dev: false
|
||||
|
||||
/through/2.3.8:
|
||||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||
dev: true
|
||||
|
||||
@@ -1,38 +1,19 @@
|
||||
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios'
|
||||
import { ResultEnum } from "@/enums/httpEnum"
|
||||
import { PageEnum, ErrorPageNameMap } from "@/enums/pageEnum"
|
||||
import { StorageEnum } from '@/enums/storageEnum'
|
||||
import { axiosPre } from '@/settings/httpSetting'
|
||||
import { SystemStoreEnum, SystemStoreUserInfoEnum } from '@/store/modules/systemStore/systemStore.d'
|
||||
import { redirectErrorPage, getLocalStorage, routerTurnByName, httpErrorHandle } from '@/utils'
|
||||
import { fetchAllowList } from './axios.config'
|
||||
import includes from 'lodash/includes'
|
||||
import { ErrorPageNameMap } from "@/enums/pageEnum"
|
||||
import { redirectErrorPage } from '@/utils'
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
baseURL: `${import.meta.env.PROD ? import.meta.env.VITE_PRO_PATH : ''}${axiosPre}`,
|
||||
baseURL: import.meta.env.DEV ? import.meta.env.VITE_DEV_PATH : import.meta.env.VITE_PRO_PATH,
|
||||
timeout: ResultEnum.TIMEOUT,
|
||||
})
|
||||
|
||||
axiosInstance.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
// 白名单校验
|
||||
if (includes(fetchAllowList, config.url)) return config
|
||||
// 获取 token
|
||||
const info = getLocalStorage(StorageEnum.GO_SYSTEM_STORE)
|
||||
// 重新登录
|
||||
if (!info) {
|
||||
routerTurnByName(PageEnum.BASE_LOGIN_NAME)
|
||||
return config
|
||||
}
|
||||
const userInfo = info[SystemStoreEnum.USER_INFO]
|
||||
config.headers = {
|
||||
...config.headers,
|
||||
[userInfo[SystemStoreUserInfoEnum.TOKEN_NAME] || 'token']: userInfo[SystemStoreUserInfoEnum.USER_TOKEN] || ''
|
||||
}
|
||||
return config
|
||||
},
|
||||
(err: AxiosRequestConfig) => {
|
||||
Promise.reject(err)
|
||||
(error: AxiosRequestConfig) => {
|
||||
Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -40,27 +21,9 @@ axiosInstance.interceptors.request.use(
|
||||
axiosInstance.interceptors.response.use(
|
||||
(res: AxiosResponse) => {
|
||||
const { code } = res.data as { code: number }
|
||||
|
||||
// 成功
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
return Promise.resolve(res.data)
|
||||
}
|
||||
|
||||
// 登录过期
|
||||
if (code === ResultEnum.TOKEN_OVERDUE) {
|
||||
window['$message'].error(window['$t']('http.token_overdue_message'))
|
||||
routerTurnByName(PageEnum.BASE_LOGIN_NAME)
|
||||
return Promise.resolve(res.data)
|
||||
}
|
||||
|
||||
// 固定错误码重定向
|
||||
if (ErrorPageNameMap.get(code)) {
|
||||
redirectErrorPage(code)
|
||||
return Promise.resolve(res.data)
|
||||
}
|
||||
|
||||
// 提示错误
|
||||
window['$message'].error(window['$t']((res.data as any).msg))
|
||||
if (code === ResultEnum.SUCCESS) return Promise.resolve(res.data)
|
||||
// 重定向
|
||||
if (ErrorPageNameMap.get(code)) redirectErrorPage(code)
|
||||
return Promise.resolve(res.data)
|
||||
},
|
||||
(err: AxiosResponse) => {
|
||||
|
||||
@@ -13,7 +13,7 @@ export const get = (url: string, params?: object) => {
|
||||
return axiosInstance({
|
||||
url: url,
|
||||
method: RequestHttpEnum.GET,
|
||||
params: params,
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export const scatterBasicUrl = '/mock/scatterBasic'
|
||||
export const mapUrl = '/mock/map'
|
||||
export const wordCloudUrl = '/mock/wordCloud'
|
||||
export const treemapUrl = '/mock/treemap'
|
||||
export const threeEarth01Url = '/mock/threeEarth01Data'
|
||||
|
||||
const mockObject: MockMethod[] = [
|
||||
{
|
||||
@@ -91,6 +92,11 @@ const mockObject: MockMethod[] = [
|
||||
method: RequestHttpEnum.GET,
|
||||
response: () => test.fetchTreemap
|
||||
},
|
||||
{
|
||||
url: threeEarth01Url,
|
||||
method: RequestHttpEnum.GET,
|
||||
response: () => test.threeEarth01Data
|
||||
},
|
||||
]
|
||||
|
||||
export default mockObject
|
||||
|
||||
@@ -254,4 +254,21 @@ export default {
|
||||
msg: '请求成功',
|
||||
data: tTreemapJson
|
||||
},
|
||||
// 三维地球
|
||||
threeEarth01Data: {
|
||||
code: 0,
|
||||
status: 200,
|
||||
msg: '请求成功',
|
||||
data: [
|
||||
{
|
||||
startArray: { name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
|
||||
endArray: [
|
||||
{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
|
||||
{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
|
||||
{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
|
||||
{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/assets/images/chart/decorates/threeEarth01.png
Normal file
|
After Width: | Height: | Size: 176 KiB |
BIN
src/assets/images/chart/informations/text_barrage.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
src/assets/images/project/moke-20211219181327.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
src/assets/videos/earth.mp4
Normal file
146
src/backend/ibackend.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
|
||||
/**
|
||||
* 后端接口,相关功能对应表:
|
||||
* 登录 - login
|
||||
* 登出 - logout
|
||||
* 预览,token 注入或单点登陆 - checkToken
|
||||
* 显示项目列表和分页 - projectList
|
||||
* 保存、发布、修改名称 - updateProject
|
||||
* 复制项目 - copyProject
|
||||
* 图表内的图片上传 - uploadFile
|
||||
* 上传图片显示处理 - getFileUrl
|
||||
* 所有接口返回格式:MyResponseType
|
||||
*/
|
||||
import { IndexDbBackend } from "./indexdb/indexdbbackend";
|
||||
// import { PythonBackend } from "./python/pythonbackend";
|
||||
|
||||
export interface MyResponseType {
|
||||
code: number; // 状态:200 表示接口调用成功,参考:HttpEnum
|
||||
msg: string; // 提示信息,配合 data 和 code
|
||||
data: any; // data = null 表示接口结果错误,错误原因放在 msg
|
||||
}
|
||||
|
||||
export class MyResponse implements MyResponseType {
|
||||
code: number = 200;
|
||||
msg: string = "";
|
||||
data: any = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现 IBackend 后端接口
|
||||
* 错误处理:
|
||||
*/
|
||||
export interface IBackend {
|
||||
/**
|
||||
* 初始化后端系统,测试后端连接,oss地址等
|
||||
* @param data 可选,备用
|
||||
*/
|
||||
init(data:any):any
|
||||
|
||||
/**
|
||||
* 登陆
|
||||
* @param data {} .username .password
|
||||
* @return MyResponseType
|
||||
* .data 须包含:
|
||||
* token:{tokenValue:"", tokenName:""},
|
||||
* userinfo:{nickname:"", username: "", id: 用户ID}
|
||||
* 错误处理:
|
||||
* 1 接口错误 .code 不为 200 .msg 可选,后端反馈错误信息
|
||||
* 2 登陆错误 .code=200 .data = null, msg 可选,反馈不能登陆的原因
|
||||
* 登陆信息.data 记录:
|
||||
* setLocalStorage(GO_LOGIN_INFO_STORE, res.data)
|
||||
*/
|
||||
login(data:any):any
|
||||
|
||||
/**
|
||||
* 通知后端登出
|
||||
*/
|
||||
logout():any
|
||||
|
||||
/**
|
||||
* 检查Token是否有效,配合预览页面和单点登陆,备用
|
||||
* @param data {tokenValue, tokenName}
|
||||
* @return 同 login()
|
||||
*/
|
||||
checkToken(data:any):any
|
||||
|
||||
/**
|
||||
* 项目列表
|
||||
* @param data {} .page, .limit
|
||||
* @return [项目],字段名称需要进行 map
|
||||
* id: projectId
|
||||
* title:projectName
|
||||
* release,
|
||||
* label:remarks
|
||||
* image:indexImage 如果需要挂刷新,在这里处理。如果需要拼接 url(getFileUrl),也在这里处理好。
|
||||
*/
|
||||
projectList(data:any):any
|
||||
|
||||
/**
|
||||
* 新增项目
|
||||
* @param data
|
||||
* .projectName
|
||||
* @return id 新项目 ID
|
||||
*/
|
||||
createProject(data: any):any
|
||||
|
||||
/**
|
||||
* 获取项目
|
||||
* @param data .projectId
|
||||
* @return
|
||||
id:projectId
|
||||
projectName,
|
||||
state: release,
|
||||
remarks,
|
||||
content
|
||||
*/
|
||||
fetchProject(data: any):any
|
||||
|
||||
/**
|
||||
* 修改项目
|
||||
* @param data
|
||||
* .projectId 必须
|
||||
* .projectName 可选
|
||||
* .release 可选
|
||||
* .content 可选
|
||||
* .object File 可选 对象
|
||||
* .remarks 可选
|
||||
* @return
|
||||
*/
|
||||
updateProject(data: any):any
|
||||
|
||||
/**
|
||||
* 复制项目
|
||||
* @param data
|
||||
* .copyId 需要复制的项目ID
|
||||
* .projectName
|
||||
* @return id 新项目ID
|
||||
*/
|
||||
copyProject(data: any):any
|
||||
|
||||
/**
|
||||
* 删除项目
|
||||
* @param data
|
||||
* .projectId
|
||||
* @return
|
||||
*/
|
||||
deleteProject(data: any):any
|
||||
|
||||
/**
|
||||
* 文件上传
|
||||
* @param file File 图片对象
|
||||
* @param params 备用 Todo: 上传文件可带上项目ID和其他附加信息,以便后端文件管理
|
||||
* @return .uri 文件对象 uri。建议在图表中保存相对地址,通过 getFileUrl 得到完整地址
|
||||
*/
|
||||
uploadFile(file: File, params: any):any
|
||||
/**
|
||||
* 文件地址转换,处理 uploadFile 的返回地址。如果是绝对地址,可以不处理
|
||||
* @param uploadUri 上传返回的 uri
|
||||
* @return 供 image.src 使用的地址信息
|
||||
*/
|
||||
getFileUrl(uploadUri:string):string
|
||||
}
|
||||
|
||||
export const BackEndFactory = new IndexDbBackend();
|
||||
// export const BackEndFactory = new MockBackend();
|
||||
// export const BackEndFactory = new PythonBackend();
|
||||
147
src/backend/indexdb/indexdb.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* IndexDb 帮助类
|
||||
*/
|
||||
|
||||
const win: { [k: string]: any } = window || globalThis;
|
||||
const indexedDB =
|
||||
win.indexedDB || win.mozIndexedDB || win.webkitIndexedDB || win.msIndexedDB;
|
||||
const dbs: { [k: string]: IDBDatabase } = {};
|
||||
let databaseName: string;
|
||||
let request: IDBOpenDBRequest;
|
||||
interface AnyEvent {
|
||||
[k: string]: any;
|
||||
}
|
||||
|
||||
export interface TableOption {
|
||||
storeName: string;
|
||||
option: { [K: string]: any };
|
||||
index: { [K: string]: any }[];
|
||||
}
|
||||
|
||||
export const createDB = (
|
||||
name: string,
|
||||
version?: string,
|
||||
options?: TableOption[],
|
||||
) =>
|
||||
new Promise<IDBDatabase>((resolve, reject) => {
|
||||
if (!indexedDB) reject('浏览器不支持indexedDB');
|
||||
databaseName = name;
|
||||
if (dbs?.[name]) {
|
||||
resolve(dbs[name]);
|
||||
return;
|
||||
}
|
||||
request = indexedDB.open(name, version);
|
||||
createTable(options)?.then((db: IDBDatabase) => resolve(db));
|
||||
request.onsuccess = (event: AnyEvent) => {
|
||||
// IDBDatabase
|
||||
const db = event.target.result;
|
||||
// 缓存起来
|
||||
dbs[name] = db;
|
||||
resolve(db);
|
||||
};
|
||||
request.onerror = (event: AnyEvent) => reject(event);
|
||||
});
|
||||
|
||||
export const createTable = (options?: TableOption[]) => {
|
||||
if (!options) return;
|
||||
return new Promise<IDBDatabase>((resolve) => {
|
||||
request.onupgradeneeded = (event: AnyEvent) => {
|
||||
const db = event.target.result;
|
||||
dbs[databaseName] = db;
|
||||
for (const i in options) {
|
||||
// 判断是否存在表
|
||||
if (!db.objectStoreNames.contains(options[i].storeName)) {
|
||||
const objectStore = db.createObjectStore(
|
||||
options[i].storeName,
|
||||
options[i].option,
|
||||
);
|
||||
for (const j of options[i].index) {
|
||||
objectStore.createIndex(j.name, j.keyPath, {
|
||||
unique: j.unique,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve(db);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const getTransaction = async (name: string, version?: string) => {
|
||||
let db: IDBDatabase;
|
||||
// 先从缓存获取
|
||||
if (dbs[databaseName]) {
|
||||
db = dbs[databaseName];
|
||||
} else {
|
||||
db = await createDB(databaseName, version);
|
||||
}
|
||||
return db.transaction(name, 'readwrite');
|
||||
};
|
||||
|
||||
const getObjectStore = async (
|
||||
name: string,
|
||||
version?: string,
|
||||
): Promise<IDBObjectStore> => {
|
||||
const transaction = await getTransaction(name, version);
|
||||
return transaction.objectStore(name);
|
||||
};
|
||||
|
||||
const getStore = (name: string, type: string, data: any) =>
|
||||
new Promise<IDBDatabase>((resolve) => {
|
||||
getObjectStore(name).then((objectStore: IDBObjectStore | any) => {
|
||||
const request = objectStore[type](data);
|
||||
request.onsuccess = (event: AnyEvent) =>
|
||||
resolve(event.target.result);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const findStore = (
|
||||
name: string,
|
||||
start: any,
|
||||
end: any,
|
||||
startInclude: any,
|
||||
endInclude: any,
|
||||
) =>
|
||||
new Promise<IDBDatabase>((resolve, reject) => {
|
||||
getObjectStore(name).then((objectStore: IDBObjectStore) => {
|
||||
const request = objectStore.openCursor(
|
||||
IDBKeyRange.bound(start, end, startInclude, endInclude),
|
||||
);
|
||||
request.onsuccess = (event: AnyEvent) =>
|
||||
resolve(event.target.result);
|
||||
request.onerror = (event: AnyEvent) => reject(event);
|
||||
});
|
||||
});
|
||||
|
||||
export interface DBSelect {
|
||||
add: (data: any) => Promise<IDBDatabase>;
|
||||
get: (data: any) => Promise<IDBDatabase>;
|
||||
getAll: () => Promise<IDBDatabase>;
|
||||
del: (data: any) => Promise<IDBDatabase>;
|
||||
clear: (data: any) => Promise<IDBDatabase>;
|
||||
put: (data: any) => Promise<IDBDatabase>;
|
||||
find: (
|
||||
start: any,
|
||||
end: any,
|
||||
startInclude: any,
|
||||
endInclude: any,
|
||||
) => Promise<IDBDatabase>;
|
||||
}
|
||||
// 获取一个store
|
||||
export const onDBSelect = async (
|
||||
name: string,
|
||||
version: string
|
||||
): Promise<DBSelect> => {
|
||||
const add = (data: any) => getStore(name, 'add', data);
|
||||
const get = (data: any) => getStore(name, 'get', data);
|
||||
const getAll = () => getStore(name, 'getAll', null);
|
||||
const del = (data: any) => getStore(name, 'delete', data);
|
||||
const clear = (data: any) => getStore(name, 'clear', data);
|
||||
const put = (data: any) => getStore(name, 'put', data);
|
||||
const find = (start: any, end: any, startInclude: any, endInclude: any) =>
|
||||
findStore(name, start, end, startInclude, endInclude);
|
||||
const options: DBSelect = { add, get, getAll, clear, del, put, find };
|
||||
getObjectStore(name, version);
|
||||
return options;
|
||||
};
|
||||
155
src/backend/indexdb/indexdbbackend.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { MyResponse, IBackend } from '../ibackend'
|
||||
import { createDB, DBSelect, onDBSelect } from '../indexdb/indexdb'
|
||||
import { fileToUrl, fileToBlob } from "@/utils"
|
||||
|
||||
const PROJECT_TABLE = "project"
|
||||
const IMAGE_TABLE = "image" // 保存图片,未实现,Todo
|
||||
const DB_NAME = "goview"
|
||||
const DB_VER = "1"
|
||||
|
||||
export class IndexDbBackend implements IBackend {
|
||||
public async init(data: any) {
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
const db:IDBDatabase = await createDB(DB_NAME, DB_VER, [
|
||||
{
|
||||
storeName: PROJECT_TABLE,
|
||||
option: {
|
||||
keyPath: "projectId", autoIncrement:true
|
||||
},
|
||||
index: [
|
||||
{name: 'projectId', keyPath: "projectId", unique: true},
|
||||
{name: 'projectName', keyPath: "projectName", unique: false},
|
||||
{name: 'release', keyPath: "release", unique: false},
|
||||
{name: 'remarks', keyPath: "remarks", unique: false},
|
||||
{name: 'content', keyPath: "content", unique: false},
|
||||
{name: 'indexImage', keyPath: "indexImage", unique: false}
|
||||
]
|
||||
}
|
||||
])
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async login(data:any) {
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
if(data.password == "123456" && data.username == "admin"){
|
||||
rtn.data = {
|
||||
token:{tokenValue:"mockToken", tokenName:"name"},
|
||||
userinfo:{nickname:"nickname", username:data.username, id:1}
|
||||
}
|
||||
}else{
|
||||
rtn.data = null
|
||||
rtn.msg = "admin 和 123456"
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async logout() {
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async checkToken(data: any) {
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
console.log("CheckToken: " + data.token)
|
||||
rtn.data = {
|
||||
token:{tokenValue:"mockToken", tokenName:"name"},
|
||||
userinfo:{nickname:"nickname", username:data.username, id:1}
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async projectList(data:any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
const db:DBSelect = await onDBSelect(PROJECT_TABLE, DB_VER)
|
||||
const r:any = await db.getAll()
|
||||
rtn.data = []
|
||||
r.map(function (item: any) {
|
||||
let url = ""
|
||||
if(item.indexImage){
|
||||
const Url = URL || window.URL || window.webkitURL
|
||||
url = Url.createObjectURL(item.indexImage)
|
||||
}
|
||||
rtn.data.push({
|
||||
id: item.projectId,
|
||||
title: item.projectName,
|
||||
release: item.release == 1,
|
||||
label:item.remarks,
|
||||
image:url
|
||||
})
|
||||
})
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async createProject(data: any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
const db:DBSelect = await onDBSelect(PROJECT_TABLE, DB_VER)
|
||||
rtn.data.id = await db.add({ projectName:data.projectName })
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async fetchProject(data: any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
const db:DBSelect = await onDBSelect(PROJECT_TABLE, DB_VER)
|
||||
const r:any = await db.get(parseInt(data.projectId))
|
||||
rtn.data = {
|
||||
id:r.projectId,
|
||||
projectName: r.projectName,
|
||||
state: r.release,
|
||||
remarks: r.remarks,
|
||||
content: r.content
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async updateProject(data: any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
const db:DBSelect = await onDBSelect(PROJECT_TABLE, DB_VER)
|
||||
const row:any = await db.get(parseInt(data.projectId))
|
||||
if("content" in data) row.content = data.content
|
||||
if("projectName" in data) row.projectName = data.projectName
|
||||
if("release" in data) row.release = data.release
|
||||
if("remarks" in data) row.remarks = data.remarks
|
||||
if("object" in data) {
|
||||
row.indexImage = await fileToBlob(data.object)
|
||||
}
|
||||
await db.put(row)
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async copyProject(data: any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
const db:DBSelect = await onDBSelect(PROJECT_TABLE, DB_VER)
|
||||
const row:any = await db.get(parseInt(data.copyId))
|
||||
rtn.data.id =await db.add({
|
||||
projectName:data.projectName,
|
||||
content:row.content,
|
||||
indexImage:row.indexImage,
|
||||
remarks:row.remarks
|
||||
})
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async deleteProject(data: any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
const db:DBSelect = await onDBSelect(PROJECT_TABLE, DB_VER)
|
||||
await db.del(parseInt(data.projectId))
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async changeProjectRelease(data: any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async uploadFile(data: File, params:any){
|
||||
// Todo: 图片可以保存在表中
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
rtn.data.uri = fileToUrl(data)
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public getFileUrl(uploadUri:string){
|
||||
return uploadUri;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { ModuleTypeEnum } from '@/enums/httpEnum'
|
||||
export enum ModuleTypeEnum {
|
||||
SYSTEM = 'sys',
|
||||
PROJECT = 'project',
|
||||
}
|
||||
|
||||
// 接口白名单(免登录)
|
||||
export const fetchAllowList = [
|
||||
71
src/backend/java/axios.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios'
|
||||
import { ResultEnum } from "@/enums/httpEnum"
|
||||
import { PageEnum, ErrorPageNameMap } from "@/enums/pageEnum"
|
||||
import { StorageEnum } from '@/enums/storageEnum'
|
||||
import { axiosPre } from '@/settings/httpSetting'
|
||||
import { SystemStoreEnum, SystemStoreUserInfoEnum } from '@/store/modules/systemStore/systemStore.d'
|
||||
import { redirectErrorPage, getLocalStorage, routerTurnByName, httpErrorHandle } from '@/utils'
|
||||
import { fetchAllowList } from './axios.config'
|
||||
import includes from 'lodash/includes'
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
baseURL: `${import.meta.env.PROD ? import.meta.env.VITE_PRO_PATH : ''}${axiosPre}`,
|
||||
timeout: ResultEnum.TIMEOUT,
|
||||
})
|
||||
|
||||
axiosInstance.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
// 白名单校验
|
||||
if (includes(fetchAllowList, config.url)) return config
|
||||
// 获取 token
|
||||
const info = getLocalStorage(StorageEnum.GO_SYSTEM_STORE)
|
||||
// 重新登录
|
||||
if (!info) {
|
||||
routerTurnByName(PageEnum.BASE_LOGIN_NAME)
|
||||
return config
|
||||
}
|
||||
const userInfo = info[SystemStoreEnum.USER_INFO]
|
||||
config.headers = {
|
||||
...config.headers,
|
||||
[userInfo[SystemStoreUserInfoEnum.TOKEN_NAME] || 'token']: userInfo[SystemStoreUserInfoEnum.USER_TOKEN] || ''
|
||||
}
|
||||
return config
|
||||
},
|
||||
(err: AxiosRequestConfig) => {
|
||||
Promise.reject(err)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
axiosInstance.interceptors.response.use(
|
||||
(res: AxiosResponse) => {
|
||||
const { code } = res.data as { code: number }
|
||||
|
||||
// 成功
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
return Promise.resolve(res.data)
|
||||
}
|
||||
|
||||
// 登录过期
|
||||
if (code === ResultEnum.TOKEN_OVERDUE) {
|
||||
window['$message'].error(window['$t']('http.token_overdue_message'))
|
||||
routerTurnByName(PageEnum.BASE_LOGIN_NAME)
|
||||
return Promise.resolve(res.data)
|
||||
}
|
||||
|
||||
// 固定错误码重定向
|
||||
if (ErrorPageNameMap.get(code)) {
|
||||
redirectErrorPage(code)
|
||||
return Promise.resolve(res.data)
|
||||
}
|
||||
|
||||
// 提示错误
|
||||
window['$message'].error(window['$t']((res.data as any).msg))
|
||||
return Promise.resolve(res.data)
|
||||
},
|
||||
(err: AxiosResponse) => {
|
||||
Promise.reject(err)
|
||||
}
|
||||
)
|
||||
|
||||
export default axiosInstance
|
||||
226
src/backend/java/http.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
import axiosInstance from './axios'
|
||||
import {
|
||||
RequestHttpEnum,
|
||||
ContentTypeEnum,
|
||||
RequestBodyEnum,
|
||||
RequestDataTypeEnum,
|
||||
RequestContentTypeEnum,
|
||||
RequestParamsObjType
|
||||
} from '@/enums/httpEnum'
|
||||
import type { RequestGlobalConfigType, RequestConfigType } from '@/store/modules/chartEditStore/chartEditStore.d'
|
||||
|
||||
export const get = (url: string, params?: object) => {
|
||||
return axiosInstance({
|
||||
url: url,
|
||||
method: RequestHttpEnum.GET,
|
||||
params: params,
|
||||
})
|
||||
}
|
||||
|
||||
export const post = (url: string, data?: object, headersType?: string) => {
|
||||
return axiosInstance({
|
||||
url: url,
|
||||
method: RequestHttpEnum.POST,
|
||||
data: data,
|
||||
headers: {
|
||||
'Content-Type': headersType || ContentTypeEnum.JSON
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const patch = (url: string, data?: object, headersType?: string) => {
|
||||
return axiosInstance({
|
||||
url: url,
|
||||
method: RequestHttpEnum.PATCH,
|
||||
data: data,
|
||||
headers: {
|
||||
'Content-Type': headersType || ContentTypeEnum.JSON
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const put = (url: string, data?: object, headersType?: ContentTypeEnum) => {
|
||||
return axiosInstance({
|
||||
url: url,
|
||||
method: RequestHttpEnum.PUT,
|
||||
data: data,
|
||||
headers: {
|
||||
'Content-Type': headersType || ContentTypeEnum.JSON
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const del = (url: string, params?: object) => {
|
||||
return axiosInstance({
|
||||
url: url,
|
||||
method: RequestHttpEnum.DELETE,
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 获取请求函数,默认get
|
||||
export const http = (type?: RequestHttpEnum) => {
|
||||
switch (type) {
|
||||
case RequestHttpEnum.GET:
|
||||
return get
|
||||
|
||||
case RequestHttpEnum.POST:
|
||||
return post
|
||||
|
||||
case RequestHttpEnum.PATCH:
|
||||
return patch
|
||||
|
||||
case RequestHttpEnum.PUT:
|
||||
return put
|
||||
|
||||
case RequestHttpEnum.DELETE:
|
||||
return del
|
||||
|
||||
default:
|
||||
return get
|
||||
}
|
||||
}
|
||||
const prefix = 'javascript:'
|
||||
// 对输入字符进行转义处理
|
||||
export const translateStr = (target: string | object) => {
|
||||
if (typeof target === 'string') {
|
||||
if (target.startsWith(prefix)) {
|
||||
const funcStr = target.split(prefix)[1]
|
||||
let result;
|
||||
try {
|
||||
result = new Function(`${funcStr}`)()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
window['$message'].error('js内容解析有误!')
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
return target
|
||||
}
|
||||
}
|
||||
for (const key in target) {
|
||||
if (Object.prototype.hasOwnProperty.call(target, key)) {
|
||||
const subTarget = (target as any)[key];
|
||||
(target as any)[key] = translateStr(subTarget)
|
||||
}
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
/**
|
||||
* * 自定义请求
|
||||
* @param targetParams 当前组件参数
|
||||
* @param globalParams 全局参数
|
||||
*/
|
||||
export const customizeHttp = (targetParams: RequestConfigType, globalParams: RequestGlobalConfigType) => {
|
||||
if (!targetParams || !globalParams) {
|
||||
return
|
||||
}
|
||||
|
||||
// 全局
|
||||
const {
|
||||
// 全局请求源地址
|
||||
requestOriginUrl,
|
||||
// 全局请求内容
|
||||
requestParams: globalRequestParams
|
||||
} = globalParams
|
||||
|
||||
// 目标组件(优先级 > 全局组件)
|
||||
const {
|
||||
// 请求地址
|
||||
requestUrl,
|
||||
// 普通 / sql
|
||||
requestContentType,
|
||||
// 获取数据的方式
|
||||
requestDataType,
|
||||
// 请求方式 get/post/del/put/patch
|
||||
requestHttpType,
|
||||
// 请求体类型 none / form-data / x-www-form-urlencoded / json /xml
|
||||
requestParamsBodyType,
|
||||
// SQL 请求对象
|
||||
requestSQLContent,
|
||||
// 请求内容 params / cookie / header / body: 同 requestParamsBodyType
|
||||
requestParams: targetRequestParams
|
||||
} = targetParams
|
||||
|
||||
// 静态排除
|
||||
if (requestDataType === RequestDataTypeEnum.STATIC) return
|
||||
|
||||
if (!requestUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
// 处理头部
|
||||
let headers: RequestParamsObjType = {
|
||||
...globalRequestParams.Header,
|
||||
...targetRequestParams.Header
|
||||
}
|
||||
headers = translateStr(headers)
|
||||
|
||||
// data 参数
|
||||
let data: RequestParamsObjType | FormData | string = {}
|
||||
// params 参数
|
||||
let params: RequestParamsObjType = { ...targetRequestParams.Params }
|
||||
params = translateStr(params)
|
||||
// form 类型处理
|
||||
let formData: FormData = new FormData()
|
||||
formData.set('default', 'defaultData')
|
||||
// 类型处理
|
||||
|
||||
switch (requestParamsBodyType) {
|
||||
case RequestBodyEnum.NONE:
|
||||
break
|
||||
|
||||
case RequestBodyEnum.JSON:
|
||||
headers['Content-Type'] = ContentTypeEnum.JSON
|
||||
data = translateStr(JSON.parse(targetRequestParams.Body['json']))
|
||||
// json 赋值给 data
|
||||
break
|
||||
|
||||
case RequestBodyEnum.XML:
|
||||
headers['Content-Type'] = ContentTypeEnum.XML
|
||||
// xml 字符串赋值给 data
|
||||
data = translateStr(targetRequestParams.Body['xml'])
|
||||
break
|
||||
|
||||
case RequestBodyEnum.X_WWW_FORM_URLENCODED: {
|
||||
headers['Content-Type'] = ContentTypeEnum.FORM_URLENCODED
|
||||
const bodyFormData = targetRequestParams.Body['x-www-form-urlencoded']
|
||||
for (const i in bodyFormData) formData.set(i, translateStr(bodyFormData[i]))
|
||||
// FormData 赋值给 data
|
||||
data = formData
|
||||
break
|
||||
}
|
||||
|
||||
case RequestBodyEnum.FORM_DATA: {
|
||||
headers['Content-Type'] = ContentTypeEnum.FORM_DATA
|
||||
const bodyFormUrlencoded = targetRequestParams.Body['form-data']
|
||||
for (const i in bodyFormUrlencoded) {
|
||||
formData.set(i, translateStr(bodyFormUrlencoded[i]))
|
||||
}
|
||||
// FormData 赋值给 data
|
||||
data = formData
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// sql 处理
|
||||
if (requestContentType === RequestContentTypeEnum.SQL) {
|
||||
headers['Content-Type'] = ContentTypeEnum.JSON
|
||||
data = requestSQLContent
|
||||
}
|
||||
|
||||
try {
|
||||
const url = (new Function("return `" + `${requestOriginUrl}${requestUrl}`.trim() + "`"))();
|
||||
return axiosInstance({
|
||||
url,
|
||||
method: requestHttpType,
|
||||
data,
|
||||
params,
|
||||
headers
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
window['$message'].error('URL地址格式有误!')
|
||||
}
|
||||
}
|
||||
130
src/backend/mock/mockbackend.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { MyResponse, IBackend } from './ibackend'
|
||||
import { fileToUrl } from '@/utils'
|
||||
|
||||
|
||||
/**
|
||||
* MockBackend
|
||||
* 模拟纯前端,不会保存,也不报错。
|
||||
*/
|
||||
|
||||
export class MockBackend implements IBackend {
|
||||
public async init(data: any) {
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async login(data:any) {
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
if(data.password == "123456" && data.username == "admin"){
|
||||
rtn.data = {
|
||||
token:{tokenValue:"mockToken", tokenName:"name"},
|
||||
userinfo:{nickname:"nickname", username:data.username, id:1}
|
||||
}
|
||||
}else{
|
||||
rtn.data = null
|
||||
rtn.msg = "用户名或密码错误!"
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async logout() {
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async checkToken(data:any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async projectList(data:any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
rtn.data =[
|
||||
{
|
||||
id: 1,
|
||||
title: '假数据不可用',
|
||||
release: true,
|
||||
label: '官方案例'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '物料2-假数据不可用',
|
||||
release: false,
|
||||
label: '官方案例'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '物料3-假数据不可用',
|
||||
release: false,
|
||||
label: '官方案例'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '物料4-假数据不可用',
|
||||
release: false,
|
||||
label: '官方案例'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '物料5-假数据不可用',
|
||||
release: false,
|
||||
label: '官方案例'
|
||||
}
|
||||
];
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async createProject(data: any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
rtn.data.id = "newId"
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async fetchProject(data: any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
rtn.data = {
|
||||
id:data.projectId,
|
||||
projectName: '假数据不可用',
|
||||
indexImage:'',
|
||||
state: 0,
|
||||
remarks: '官方案例',
|
||||
content: null
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async saveProject(data: object){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async updateProject(data: any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async copyProject(data: any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async deleteProject(data: any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async changeProjectRelease(data: any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public async uploadFile(data: File, params:any){
|
||||
let rtn:MyResponse = new MyResponse;
|
||||
rtn.data.uri = fileToUrl(data)
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public getFileUrl(uploadUri:string){
|
||||
return uploadUri;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,23 @@
|
||||
</setting-item-box>
|
||||
</collapse-item>
|
||||
|
||||
<collapse-item v-if="grid" name="容器">
|
||||
<setting-item-box name="距离">
|
||||
<setting-item name="左侧距离">
|
||||
<n-input v-model:value="grid.left" size="small"></n-input>
|
||||
</setting-item>
|
||||
<setting-item name="右侧距离">
|
||||
<n-input v-model:value="grid.right" size="small"></n-input>
|
||||
</setting-item>
|
||||
<setting-item name="上侧距离">
|
||||
<n-input v-model:value="grid.top" size="small"></n-input>
|
||||
</setting-item>
|
||||
<setting-item name="下侧距离">
|
||||
<n-input v-model:value="grid.bottom" size="small"></n-input>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
</collapse-item>
|
||||
|
||||
<collapse-item v-if="xAxis" name="X轴">
|
||||
<template #header>
|
||||
<n-switch v-model:value="xAxis.show" size="small"></n-switch>
|
||||
@@ -51,6 +68,9 @@
|
||||
<setting-item name="大小">
|
||||
<n-input-number v-model:value="xAxis.axisLabel.fontSize" :min="8" size="small"></n-input-number>
|
||||
</setting-item>
|
||||
<setting-item name="偏移量">
|
||||
<n-input-number v-model:value="xAxis.axisLabel.rotate" :min="-90" :max="90" size="small"></n-input-number>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
<setting-item-box name="轴线">
|
||||
<setting-item name="展示">
|
||||
@@ -140,6 +160,9 @@
|
||||
<setting-item name="大小">
|
||||
<n-input-number v-model:value="yAxis.axisLabel.fontSize" :min="8" size="small"></n-input-number>
|
||||
</setting-item>
|
||||
<setting-item name="偏移量">
|
||||
<n-input-number v-model:value="yAxis.axisLabel.rotate" :min="-90" :max="90" size="small"></n-input-number>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
<setting-item-box name="轴线">
|
||||
<setting-item name="展示">
|
||||
@@ -289,6 +312,10 @@ const legend = computed(() => {
|
||||
return props.optionData.legend
|
||||
})
|
||||
|
||||
const grid = computed(() => {
|
||||
return props.optionData.grid
|
||||
})
|
||||
|
||||
const visualMap = computed(() => {
|
||||
return props.optionData.visualMap
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<setting-item-box name="位置">
|
||||
<setting-item-box v-if="targetData" name="位置">
|
||||
<setting-item :name="`偏移 X:${targetData.left || 0}px`">
|
||||
<n-input-number v-model:value="targetData.left" size="small" step="10"></n-input-number>
|
||||
</setting-item>
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
// 模块 Path 前缀分类
|
||||
export enum ModuleTypeEnum {
|
||||
SYSTEM = 'sys',
|
||||
PROJECT = 'project',
|
||||
}
|
||||
|
||||
// 请求结果集
|
||||
/**
|
||||
* @description: 请求结果集
|
||||
*/
|
||||
export enum ResultEnum {
|
||||
DATA_SUCCESS = 0,
|
||||
SUCCESS = 200,
|
||||
@@ -12,7 +8,7 @@ export enum ResultEnum {
|
||||
SERVER_FORBIDDEN = 403,
|
||||
NOT_FOUND = 404,
|
||||
TOKEN_OVERDUE = 886,
|
||||
TIMEOUT = 60000,
|
||||
TIMEOUT = 60000
|
||||
}
|
||||
|
||||
// 数据相关
|
||||
@@ -37,7 +33,9 @@ export enum RequestHttpHeaderEnum {
|
||||
COOKIE = 'Cookie'
|
||||
}
|
||||
|
||||
// 请求方法
|
||||
/**
|
||||
* @description: 请求方法
|
||||
*/
|
||||
export enum RequestHttpEnum {
|
||||
GET = 'get',
|
||||
POST = 'post',
|
||||
@@ -118,7 +116,9 @@ export type RequestParams = {
|
||||
}
|
||||
}
|
||||
|
||||
// 常用的contentTyp类型
|
||||
/**
|
||||
* @description: 常用的contentTyp类型
|
||||
*/
|
||||
export enum ContentTypeEnum {
|
||||
// json
|
||||
JSON = 'application/json;charset=UTF-8',
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
export enum StorageEnum {
|
||||
// 全局设置
|
||||
GO_SETTING_STORE = 'GO_SETTING',
|
||||
GO_SYSTEM_SETTING_STORE = 'GO_SYSTEM_SETTING',
|
||||
// token 等信息
|
||||
GO_ACCESS_TOKEN_STORE = 'GO_ACCESS_TOKEN',
|
||||
// 登录信息
|
||||
GO_SYSTEM_STORE = 'GO_SYSTEM',
|
||||
GO_LOGIN_INFO_STORE = 'GO_LOGIN_INFO',
|
||||
// 语言
|
||||
GO_LANG_STORE = 'GO_LANG',
|
||||
// 当前选择的主题
|
||||
|
||||
@@ -2,4 +2,4 @@ export * from '@/hooks/useTheme.hook'
|
||||
export * from '@/hooks/usePreviewScale.hook'
|
||||
export * from '@/hooks/useCode.hook'
|
||||
export * from '@/hooks/useChartDataFetch.hook'
|
||||
export * from '@/hooks/useSystemInit.hook'
|
||||
export * from '@/hooks/useLifeHandler.hook'
|
||||
@@ -59,18 +59,18 @@ export const useChartDataFetch = (
|
||||
|
||||
const fetchFn = async () => {
|
||||
const res = await customizeHttp(toRaw(targetComponent.request), toRaw(chartEditStore.requestGlobalConfig))
|
||||
if (res && res.data) {
|
||||
if (res) {
|
||||
try {
|
||||
const filter = targetComponent.filter
|
||||
// eCharts 组件配合 vChart 库更新方式
|
||||
if (chartFrame === ChartFrameEnum.ECHARTS) {
|
||||
if (vChartRef.value) {
|
||||
vChartRef.value.setOption({ dataset: newFunctionHandle(res.data, filter) })
|
||||
vChartRef.value.setOption({ dataset: newFunctionHandle(res?.data, res, filter) })
|
||||
}
|
||||
}
|
||||
// 更新回调函数
|
||||
if (updateCallback) {
|
||||
updateCallback(newFunctionHandle(res.data, filter))
|
||||
updateCallback(newFunctionHandle(res?.data, res, filter))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -90,7 +90,7 @@ export const useChartDataFetch = (
|
||||
}
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
280
src/hooks/useLifeHandler.hook.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
import { CreateComponentType, EventLife } from '@/packages/index.d'
|
||||
import * as echarts from 'echarts'
|
||||
import { BackEndFactory } from '@/backend/ibackend'
|
||||
import { reactive, toRef , watch, computed} from 'vue';
|
||||
|
||||
/**
|
||||
* 事件测试:
|
||||
*
|
||||
切换显示名称为 饼图 和 柱状图 的图标
|
||||
const range = runtime.fn.selectComponents("饼图 柱状图")
|
||||
const h = runtime.fn.getChartConfig(range, "hide")
|
||||
runtime.fn.setChartConfig(range, "hide", !h)
|
||||
|
||||
修改一个名称 柱状图001 组件id 2wolqibrx3c000 的图表数据,以下两句等效
|
||||
runtime.fn.setChartConfig("柱状图001", "dataset", {"dimensions":["product","data1","data2"],"source":[{"product":"Mon","data1":120,"data2":130}]})
|
||||
runtime.fn.setChartConfig("#2wolqibrx3c000", "dataset", {"dimensions":["product","data1","data2"],"source":[{"product":"Mon","data1":120,"data2":230}]})
|
||||
|
||||
找到一个组并隐藏
|
||||
const c = runtime.fn.selectOneComponent("分组")
|
||||
if(c){
|
||||
console.log(runtime.fn.getChartConfig(c, "isGroup" ))
|
||||
runtime.fn.setChartConfig(c, "hide", true)
|
||||
}
|
||||
|
||||
调用组件 exposed 函数的例子
|
||||
组件中增加: defineExpose({ actionTest:actionTest })
|
||||
以下调用名称为 柱状图 组件的 actionTest
|
||||
runtime.fn.callExposed("柱状图", "actionTest")
|
||||
|
||||
|
||||
数据驱动界面:
|
||||
图表A 的 MOUNTED 加入对 status1 的 Watch, = "0" 隐藏
|
||||
watch(()=>runtime.variables.status1, newValue => runtime.fn.setChartConfig(this, "hide", newValue == "0"))
|
||||
图表B 的 MOUNTED 也加入对 status1 的 Watch = "1" 隐藏
|
||||
watch(()=>runtime.variables.status1, newValue => runtime.fn.setChartConfig(this, "hide", newValue == "1"))
|
||||
点击事件代码,实现图表A 和 图表B 的切换显示:
|
||||
if(runtime.variables.status1 == "0"){
|
||||
runtime.variables.status1 = "1"
|
||||
} else{
|
||||
runtime.variables.status1 = "0"
|
||||
}
|
||||
|
||||
图表A 的 MOUNTED 加入对 data1 的 Watch
|
||||
watch(()=>runtime.datasets.data1,
|
||||
newValue => runtime.fn.setChartConfig(this, "dataset", newValue))
|
||||
图表B 的 MOUNTED 加入对 data1 的 Watch
|
||||
watch(()=>runtime.datasets.data1,
|
||||
newValue => runtime.fn.setChartConfig(this, "dataset", newValue))
|
||||
点击事件代码,修改datasets.data1,同时更新图表A 和 图表B 的数据 :
|
||||
runtime.datasets.data1 = {"dimensions":["product","data1","data2"],"source":[{"product":"Mon","data1":120,"data2":230}]}
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// * 初始化
|
||||
export const useSystemInit = async () => {
|
||||
const res = await BackEndFactory.init({}) as any;
|
||||
}
|
||||
|
||||
const getOneChartConfig = (component:any, configName:string, params?:any)=>{
|
||||
let root = null
|
||||
if(component.proxy.chartConfig) root = component.proxy.chartConfig
|
||||
else if (component.proxy.groupData) root = component.proxy.groupData
|
||||
// if(!root) return null
|
||||
switch(configName){
|
||||
case "hide":
|
||||
return root.status.hide
|
||||
break;
|
||||
case "dataset":
|
||||
return root.option.dataset
|
||||
break;
|
||||
case "isGroup":
|
||||
return root.isGroup
|
||||
break;
|
||||
case "key":
|
||||
return root.key
|
||||
break;
|
||||
case "attr":
|
||||
return root.attr
|
||||
break;
|
||||
case "name":
|
||||
return root.chartConfig.title
|
||||
}
|
||||
}
|
||||
|
||||
const setOneChartConfig = (component:any, configName:string, newValue:any, params?:any)=>{
|
||||
let root = null
|
||||
if(component.proxy.chartConfig) root = component.proxy.chartConfig
|
||||
else if (component.proxy.groupData) root = component.proxy.groupData
|
||||
switch(configName){
|
||||
case "hide":
|
||||
root.status.hide = newValue
|
||||
break;
|
||||
case "dataset":
|
||||
root.option.dataset = newValue
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 选择器语法:参考 css selectors
|
||||
* 名称 组件名称,不能有空格和特殊字符(. # 引号等)
|
||||
* [name=名称] Todo
|
||||
* #id 组件编号
|
||||
* .key 组件类型 Todo
|
||||
* @param selectors
|
||||
* @returns []
|
||||
*/
|
||||
const getComponentsBySelectors = (selectors:string):any[]=>{
|
||||
// 返回:数组,可能多个
|
||||
let rtn:any[] = []
|
||||
const ar = selectors.split(" ")
|
||||
for(let a of ar){
|
||||
rtn = rtn.concat(getComponentsBySelector(a))
|
||||
}
|
||||
return rtn
|
||||
}
|
||||
|
||||
const getComponentsBySelector = (selector:string):any[]=>{
|
||||
// 返回:数组,可能多个
|
||||
const rtn:any[] = []
|
||||
if(selector.substring(0,1) == "#")
|
||||
{
|
||||
const key = selector.substring(1)
|
||||
if(key in components){
|
||||
return [components[key]]
|
||||
}
|
||||
return rtn
|
||||
}
|
||||
for (let key in components) {
|
||||
if(getOneChartConfig(components[key], "name") == selector){
|
||||
rtn.push(components[key])
|
||||
}
|
||||
}
|
||||
return rtn
|
||||
}
|
||||
|
||||
|
||||
// 所有图表组件集合对象
|
||||
const components: { [K in string]?: any } = {}
|
||||
|
||||
const runtime = {
|
||||
// 变量,管理各种状态
|
||||
variables:reactive({}),
|
||||
// 数据集
|
||||
datasets:reactive({}),
|
||||
// 组件列表 {}
|
||||
components:components,
|
||||
// 帮助类
|
||||
fn:{
|
||||
/**
|
||||
* 选择一个组件
|
||||
* @param selectors string 选择器语法 | component | [component]
|
||||
* @return 第一个符合要求的 component 或 null
|
||||
*/
|
||||
selectOneComponent:(selectors:any)=>{
|
||||
const cList = runtime.fn.selectComponents(selectors)
|
||||
if(cList.length > 0){
|
||||
return cList[0]
|
||||
}
|
||||
return null
|
||||
},
|
||||
/**
|
||||
* 选择组件
|
||||
* @param selectors string 选择器语法 | component | [component]
|
||||
* @return 要求的 [component] 或 []
|
||||
*/
|
||||
selectComponents:(selectors:any):any[]=>{
|
||||
if(!selectors) return []
|
||||
if(typeof selectors == "string") return getComponentsBySelectors(selectors)
|
||||
if(Array.isArray(selectors)) return selectors
|
||||
return [selectors]
|
||||
},
|
||||
/**
|
||||
* 获取组件的值,如果多个,使用第一个
|
||||
* @param selectors string 选择器语法 | component | [component]
|
||||
* @param configName 配置名称
|
||||
* @param params 备用参数,可选
|
||||
* @returns 配置的值
|
||||
*/
|
||||
getChartConfig:(selectors:any, configName:string, params?:any)=>{
|
||||
const component:any = runtime.fn.selectOneComponent(selectors)
|
||||
if(!component && !component.proxy) return null
|
||||
return getOneChartConfig(component, configName, params)
|
||||
},
|
||||
/**
|
||||
* 设置组件的值,支持多个
|
||||
* @param selectors string 选择器语法 | component | [component]
|
||||
* @param configName 配置名称
|
||||
* @param newValue 新值
|
||||
* @param params 备用参数,可选
|
||||
* @returns 配置的值
|
||||
*/
|
||||
setChartConfig:(selectors:any, configName:string, newValue:any, params?:any)=>{
|
||||
const cList:any[] = runtime.fn.selectComponents(selectors)
|
||||
for(let c of cList){
|
||||
if(!c && !c.proxy) return null
|
||||
setOneChartConfig(c, configName, newValue, params)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 调用组件暴露的函数,组件中使用 defineExpose 进行定义
|
||||
* @param selectors string 选择器语法 | component | [component]
|
||||
* @param action 组件中 defineExpose 的函数名
|
||||
* @param params 调用的参数只支持一个参数或没有参数
|
||||
* @returns 无
|
||||
*/
|
||||
callExposed:(selectors:any, action:string, params?:any)=>{
|
||||
const cList:any[] = runtime.fn.selectComponents(selectors)
|
||||
for(let c of cList){
|
||||
if(!c && !c.exposed) return null
|
||||
if(typeof c.exposed[action] == "function") c.exposed[action](params)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 项目提供的npm 包变量
|
||||
export const npmPkgs = { echarts, toRef , watch, computed, runtime }
|
||||
|
||||
export const useLifeHandler = (chartConfig: CreateComponentType) => {
|
||||
const events = chartConfig.events || {}
|
||||
console.log("chartConfig.events")
|
||||
console.log(chartConfig.events)
|
||||
// 生成生命周期事件
|
||||
let lifeEvents = {
|
||||
[EventLife.BEFORE_MOUNT](e: any) {
|
||||
// 存储组件
|
||||
components[chartConfig.id] = e.component
|
||||
const fnStr = (events[EventLife.BEFORE_MOUNT] || '').trim()
|
||||
generateFunc(fnStr, e, e.component)
|
||||
},
|
||||
[EventLife.MOUNTED](e: any) {
|
||||
const fnStr = (events[EventLife.MOUNTED] || '').trim()
|
||||
generateFunc(fnStr, e, e.component)
|
||||
}
|
||||
}
|
||||
// 遍历,按需侦听
|
||||
for(let key in EventLife)
|
||||
{
|
||||
if(key != "BEFORE_MOUNT" && key != "MOUNTED"){
|
||||
const k = EventLife[key as keyof typeof EventLife]
|
||||
const fnStr = (events[<EventLife>k] || '').trim()
|
||||
if(fnStr){
|
||||
lifeEvents[k as keyof typeof lifeEvents] = (e:any) => {
|
||||
const fnStr = (events[<EventLife>k] || '').trim()
|
||||
generateFunc(fnStr, e, components[chartConfig.id])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return lifeEvents
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fnStr 用户方法体代码
|
||||
* @param e 执行生命周期的动态组件实例
|
||||
*/
|
||||
function generateFunc(fnStr: string, e: any, component:any) {
|
||||
if(fnStr == "") return
|
||||
try {
|
||||
// npmPkgs 便于拷贝 echarts 示例时设置option 的formatter等相关内容
|
||||
Function(`
|
||||
"use strict";
|
||||
return (
|
||||
async function(e, components, node_modules){
|
||||
const {${Object.keys(npmPkgs).join()}} = node_modules;
|
||||
${fnStr}
|
||||
}
|
||||
)`)().bind(component)(e, components, npmPkgs)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { useSystemStore } from '@/store/modules/systemStore/systemStore'
|
||||
import { SystemStoreEnum } from '@/store/modules/systemStore/systemStore.d'
|
||||
import { ResultEnum } from '@/enums/httpEnum'
|
||||
import { ossUrlApi } from '@/api/path/'
|
||||
|
||||
|
||||
// * 初始化
|
||||
export const useSystemInit = async () => {
|
||||
const systemStore = useSystemStore()
|
||||
|
||||
// 获取 OSS 信息
|
||||
const getOssUrl = async () => {
|
||||
const res = await ossUrlApi({}) as unknown as MyResponseType
|
||||
if (res.code === ResultEnum.SUCCESS) {
|
||||
systemStore.setItem(SystemStoreEnum.FETCH_INFO, {
|
||||
OSSUrl: res.data?.bucketURL
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 执行
|
||||
getOssUrl()
|
||||
}
|
||||
@@ -4,17 +4,21 @@ import { CreateComponentType } from '@/packages/index.d'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import dataJson from './data.json'
|
||||
|
||||
export const includes = ['legend', 'xAxis', 'yAxis']
|
||||
|
||||
export const includes = ['legend', 'xAxis', 'yAxis', 'grid']
|
||||
export const seriesItem = {
|
||||
type: 'bar',
|
||||
barWidth: null,
|
||||
barWidth: 15,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#fff',
|
||||
fontSize: 12
|
||||
},
|
||||
itemStyle: {
|
||||
color: null,
|
||||
borderRadius: 0
|
||||
borderRadius: 2
|
||||
}
|
||||
}
|
||||
|
||||
export const option = {
|
||||
tooltip: {
|
||||
show: true,
|
||||
@@ -24,10 +28,7 @@ export const option = {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: true
|
||||
},
|
||||
xAxis: {
|
||||
xAxis: {
|
||||
show: true,
|
||||
type: 'category'
|
||||
},
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
<template>
|
||||
<!-- Echarts 全局设置 -->
|
||||
<!-- Echarts 全局设置 -->
|
||||
<global-setting :optionData="optionData"></global-setting>
|
||||
<CollapseItem
|
||||
v-for="(item, index) in seriesList"
|
||||
:key="index"
|
||||
:name="`柱状图-${index + 1}`"
|
||||
:expanded="true"
|
||||
>
|
||||
<CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`柱状图-${index + 1}`" :expanded="true">
|
||||
<SettingItemBox name="图形">
|
||||
<SettingItem name="颜色">
|
||||
<n-color-picker
|
||||
size="small"
|
||||
:modes="['hex']"
|
||||
v-model:value="item.itemStyle.color"
|
||||
></n-color-picker>
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="item.itemStyle.color"></n-color-picker>
|
||||
</SettingItem>
|
||||
<SettingItem>
|
||||
<n-button size="small" @click="item.itemStyle.color = null">
|
||||
恢复默认
|
||||
</n-button>
|
||||
<n-button size="small" @click="item.itemStyle.color = null"> 恢复默认 </n-button>
|
||||
</SettingItem>
|
||||
<SettingItem name="宽度">
|
||||
<n-input-number
|
||||
@@ -30,24 +19,40 @@
|
||||
></n-input-number>
|
||||
</SettingItem>
|
||||
<SettingItem name="圆角">
|
||||
<n-input-number
|
||||
v-model:value="item.itemStyle.borderRadius"
|
||||
:min="0"
|
||||
size="small"
|
||||
></n-input-number>
|
||||
<n-input-number v-model:value="item.itemStyle.borderRadius" :min="0" size="small"></n-input-number>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
<setting-item-box name="标签">
|
||||
<setting-item>
|
||||
<n-space>
|
||||
<n-switch v-model:value="item.label.show" size="small" />
|
||||
<n-text>展示标签</n-text>
|
||||
</n-space>
|
||||
</setting-item>
|
||||
<setting-item name="大小">
|
||||
<n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number>
|
||||
</setting-item>
|
||||
<setting-item name="颜色">
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker>
|
||||
</setting-item>
|
||||
<setting-item name="位置">
|
||||
<n-select
|
||||
v-model:value="item.label.position"
|
||||
:options="[
|
||||
{ label: 'top', value: 'top' },
|
||||
{ label: 'left', value: 'left' },
|
||||
{ label: 'right', value: 'right' },
|
||||
{ label: 'bottom', value: 'bottom' }
|
||||
]"
|
||||
/>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
</CollapseItem>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType, computed } from 'vue'
|
||||
import {
|
||||
GlobalSetting,
|
||||
CollapseItem,
|
||||
SettingItemBox,
|
||||
SettingItem
|
||||
} from '@/components/Pages/ChartItemSetting'
|
||||
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
|
||||
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
@@ -53,17 +53,21 @@ const option = computed(() => {
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
(newData: { dimensions: any }, oldData) => {
|
||||
if (!isObject(newData) || !('dimensions' in newData)) return
|
||||
if (newData?.dimensions.length !== oldData?.dimensions.length) {
|
||||
const seriesArr = []
|
||||
for (let i = 0; i < newData.dimensions.length - 1; i++) {
|
||||
seriesArr.push(seriesItem)
|
||||
try {
|
||||
if (!isObject(newData) || !('dimensions' in newData)) return
|
||||
if (Array.isArray(newData?.dimensions)) {
|
||||
const seriesArr = []
|
||||
for (let i = 0; i < newData.dimensions.length - 1; i++) {
|
||||
seriesArr.push(seriesItem)
|
||||
}
|
||||
replaceMergeArr.value = ['series']
|
||||
props.chartConfig.option.series = seriesArr
|
||||
nextTick(() => {
|
||||
replaceMergeArr.value = []
|
||||
})
|
||||
}
|
||||
replaceMergeArr.value = ['series']
|
||||
props.chartConfig.option.series = seriesArr
|
||||
nextTick(() => {
|
||||
replaceMergeArr.value = []
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,17 +4,21 @@ import { CreateComponentType } from '@/packages/index.d'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import dataJson from './data.json'
|
||||
|
||||
export const includes = ['legend', 'xAxis', 'yAxis']
|
||||
|
||||
export const includes = ['legend', 'xAxis', 'yAxis', 'grid']
|
||||
export const seriesItem = {
|
||||
type: 'bar',
|
||||
barWidth: null,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
color: '#fff',
|
||||
fontSize: 12
|
||||
},
|
||||
itemStyle: {
|
||||
color: null,
|
||||
borderRadius: 0
|
||||
}
|
||||
}
|
||||
|
||||
export const option = {
|
||||
tooltip: {
|
||||
show: true,
|
||||
@@ -24,12 +28,9 @@ export const option = {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: true
|
||||
},
|
||||
xAxis: {
|
||||
show: true,
|
||||
type: 'value',
|
||||
type: 'value'
|
||||
},
|
||||
yAxis: {
|
||||
show: true,
|
||||
|
||||
@@ -26,6 +26,39 @@
|
||||
></n-input-number>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
<setting-item-box name="标签">
|
||||
<setting-item>
|
||||
<n-space>
|
||||
<n-switch v-model:value="item.label.show" size="small" />
|
||||
<n-text>展示标签</n-text>
|
||||
</n-space>
|
||||
</setting-item>
|
||||
<setting-item name="大小">
|
||||
<n-input-number
|
||||
v-model:value="item.label.fontSize"
|
||||
size="small"
|
||||
:min="1"
|
||||
></n-input-number>
|
||||
</setting-item>
|
||||
<setting-item name="颜色">
|
||||
<n-color-picker
|
||||
size="small"
|
||||
:modes="['hex']"
|
||||
v-model:value="item.label.color"
|
||||
></n-color-picker>
|
||||
</setting-item>
|
||||
<setting-item name="位置">
|
||||
<n-select
|
||||
v-model:value="item.label.position"
|
||||
:options="[
|
||||
{ label: 'top', value: 'top' },
|
||||
{ label: 'left', value: 'left' },
|
||||
{ label: 'right', value: 'right' },
|
||||
{ label: 'bottom', value: 'bottom' },
|
||||
]"
|
||||
/>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
</CollapseItem>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -52,17 +52,21 @@ const option = computed(() => {
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
(newData: { dimensions: any }, oldData) => {
|
||||
if (!isObject(newData) || !('dimensions' in newData)) return
|
||||
if (newData?.dimensions.length !== oldData?.dimensions.length) {
|
||||
const seriesArr = []
|
||||
for (let i = 0; i < newData.dimensions.length - 1; i++) {
|
||||
seriesArr.push(seriesItem)
|
||||
try {
|
||||
if (!isObject(newData) || !('dimensions' in newData)) return
|
||||
if (Array.isArray(newData?.dimensions)) {
|
||||
const seriesArr = []
|
||||
for (let i = 0; i < newData.dimensions.length - 1; i++) {
|
||||
seriesArr.push(seriesItem)
|
||||
}
|
||||
replaceMergeArr.value = ['series']
|
||||
props.chartConfig.option.series = seriesArr
|
||||
nextTick(() => {
|
||||
replaceMergeArr.value = []
|
||||
})
|
||||
}
|
||||
replaceMergeArr.value = ['series']
|
||||
props.chartConfig.option.series = seriesArr
|
||||
nextTick(() => {
|
||||
replaceMergeArr.value = []
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,17 +4,24 @@ import { CreateComponentType } from '@/packages/index.d'
|
||||
import { defaultTheme, chartColorsSearch } from '@/settings/chartThemes/index'
|
||||
import dataJson from './data.json'
|
||||
|
||||
export const includes = ['legend', 'xAxis', 'yAxis']
|
||||
|
||||
export const includes = ['legend', 'xAxis', 'yAxis', 'grid']
|
||||
export const seriesItem = {
|
||||
type: 'line',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#fff',
|
||||
fontSize: 12
|
||||
},
|
||||
symbolSize: 5, //设定实心点的大小
|
||||
itemStyle: {
|
||||
color: null,
|
||||
borderRadius: 0
|
||||
},
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
width: 3,
|
||||
itemStyle: {
|
||||
color: null,
|
||||
borderRadius: 0
|
||||
}
|
||||
color: null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +33,6 @@ export const option = {
|
||||
type: 'line'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: true
|
||||
},
|
||||
xAxis: {
|
||||
show: true,
|
||||
type: 'category'
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
<template>
|
||||
<!-- Echarts 全局设置 -->
|
||||
<global-setting :optionData="optionData"></global-setting>
|
||||
<CollapseItem
|
||||
v-for="(item, index) in seriesList"
|
||||
:key="index"
|
||||
:name="`折线图-${index + 1}`"
|
||||
:expanded="true"
|
||||
>
|
||||
<CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`折线图-${index + 1}`" :expanded="true">
|
||||
<SettingItemBox name="线条">
|
||||
<setting-item name="颜色">
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="item.lineStyle.color"></n-color-picker>
|
||||
</setting-item>
|
||||
<SettingItem name="宽度">
|
||||
<n-input-number
|
||||
v-model:value="item.lineStyle.width"
|
||||
@@ -15,16 +13,48 @@
|
||||
:max="100"
|
||||
size="small"
|
||||
placeholder="自动计算"
|
||||
></n-input-number>
|
||||
></n-input-number>
|
||||
</SettingItem>
|
||||
<SettingItem name="类型">
|
||||
<n-select
|
||||
v-model:value="item.lineStyle.type"
|
||||
size="small"
|
||||
:options="lineConf.lineStyle.type"
|
||||
></n-select>
|
||||
<n-select v-model:value="item.lineStyle.type" size="small" :options="lineConf.lineStyle.type"></n-select>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
<SettingItemBox name="实心点">
|
||||
<SettingItem name="大小">
|
||||
<n-input-number
|
||||
v-model:value="item.symbolSize"
|
||||
:min="1"
|
||||
:max="100"
|
||||
size="small"
|
||||
placeholder="自动计算"
|
||||
></n-input-number>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
<setting-item-box name="标签">
|
||||
<setting-item>
|
||||
<n-space>
|
||||
<n-switch v-model:value="item.label.show" size="small" />
|
||||
<n-text>展示标签</n-text>
|
||||
</n-space>
|
||||
</setting-item>
|
||||
<setting-item name="大小">
|
||||
<n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number>
|
||||
</setting-item>
|
||||
<setting-item name="颜色">
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker>
|
||||
</setting-item>
|
||||
<setting-item name="位置">
|
||||
<n-select
|
||||
v-model:value="item.label.position"
|
||||
:options="[
|
||||
{ label: 'top', value: 'top' },
|
||||
{ label: 'left', value: 'left' },
|
||||
{ label: 'right', value: 'right' },
|
||||
{ label: 'bottom', value: 'bottom' }
|
||||
]"
|
||||
/>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
</CollapseItem>
|
||||
</template>
|
||||
|
||||
@@ -32,12 +62,7 @@
|
||||
import { PropType, computed } from 'vue'
|
||||
import { lineConf } from '@/packages/chartConfiguration/echarts/index'
|
||||
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
|
||||
import {
|
||||
GlobalSetting,
|
||||
CollapseItem,
|
||||
SettingItemBox,
|
||||
SettingItem
|
||||
} from '@/components/Pages/ChartItemSetting'
|
||||
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
|
||||
|
||||
const props = defineProps({
|
||||
optionData: {
|
||||
|
||||
@@ -53,17 +53,21 @@ const option = computed(() => {
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
(newData: { dimensions: any }, oldData) => {
|
||||
if (!isObject(newData) || !('dimensions' in newData)) return
|
||||
if (newData?.dimensions.length !== oldData?.dimensions.length) {
|
||||
const seriesArr = []
|
||||
for (let i = 0; i < newData.dimensions.length - 1; i++) {
|
||||
seriesArr.push(seriesItem)
|
||||
try {
|
||||
if (!isObject(newData) || !('dimensions' in newData)) return
|
||||
if (Array.isArray(newData?.dimensions)) {
|
||||
const seriesArr = []
|
||||
for (let i = 0; i < newData.dimensions.length - 1; i++) {
|
||||
seriesArr.push(seriesItem)
|
||||
}
|
||||
replaceMergeArr.value = ['series']
|
||||
props.chartConfig.option.series = seriesArr
|
||||
nextTick(() => {
|
||||
replaceMergeArr.value = []
|
||||
})
|
||||
}
|
||||
replaceMergeArr.value = ['series']
|
||||
props.chartConfig.option.series = seriesArr
|
||||
nextTick(() => {
|
||||
replaceMergeArr.value = []
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ import { graphic } from 'echarts/core'
|
||||
import { defaultTheme, chartColorsSearch } from '@/settings/chartThemes/index'
|
||||
import dataJson from './data.json'
|
||||
|
||||
export const includes = ['legend', 'xAxis', 'yAxis']
|
||||
export const includes = ['legend', 'xAxis', 'yAxis', 'grid']
|
||||
|
||||
const options = {
|
||||
tooltip: {
|
||||
@@ -15,9 +15,6 @@ const options = {
|
||||
type: 'line'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: true
|
||||
},
|
||||
xAxis: {
|
||||
show: true,
|
||||
type: 'category'
|
||||
@@ -31,6 +28,13 @@ const options = {
|
||||
{
|
||||
type: 'line',
|
||||
smooth: false,
|
||||
symbolSize: 5, //设定实心点的大小
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#fff',
|
||||
fontSize: 12
|
||||
},
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
width: 3
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
<template>
|
||||
<!-- Echarts 全局设置 -->
|
||||
<global-setting :optionData="optionData"></global-setting>
|
||||
<CollapseItem
|
||||
v-for="(item, index) in seriesList"
|
||||
:key="index"
|
||||
name="单折线面积图"
|
||||
:expanded="true"
|
||||
>
|
||||
<CollapseItem v-for="(item, index) in seriesList" :key="index" name="单折线面积图" :expanded="true">
|
||||
<SettingItemBox name="线条">
|
||||
<SettingItem name="宽度">
|
||||
<n-input-number
|
||||
@@ -18,13 +13,45 @@
|
||||
></n-input-number>
|
||||
</SettingItem>
|
||||
<SettingItem name="类型">
|
||||
<n-select
|
||||
v-model:value="item.lineStyle.type"
|
||||
size="small"
|
||||
:options="lineConf.lineStyle.type"
|
||||
></n-select>
|
||||
<n-select v-model:value="item.lineStyle.type" size="small" :options="lineConf.lineStyle.type"></n-select>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
<SettingItemBox name="实心点">
|
||||
<SettingItem name="大小">
|
||||
<n-input-number
|
||||
v-model:value="item.symbolSize"
|
||||
:min="1"
|
||||
:max="100"
|
||||
size="small"
|
||||
placeholder="自动计算"
|
||||
></n-input-number>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
<setting-item-box name="标签">
|
||||
<setting-item>
|
||||
<n-space>
|
||||
<n-switch v-model:value="item.label.show" size="small" />
|
||||
<n-text>展示标签</n-text>
|
||||
</n-space>
|
||||
</setting-item>
|
||||
<setting-item name="大小">
|
||||
<n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number>
|
||||
</setting-item>
|
||||
<setting-item name="颜色">
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker>
|
||||
</setting-item>
|
||||
<setting-item name="位置">
|
||||
<n-select
|
||||
v-model:value="item.label.position"
|
||||
:options="[
|
||||
{ label: 'top', value: 'top' },
|
||||
{ label: 'left', value: 'left' },
|
||||
{ label: 'right', value: 'right' },
|
||||
{ label: 'bottom', value: 'bottom' }
|
||||
]"
|
||||
/>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
</CollapseItem>
|
||||
</template>
|
||||
|
||||
@@ -32,18 +59,13 @@
|
||||
import { PropType, computed } from 'vue'
|
||||
import { lineConf } from '@/packages/chartConfiguration/echarts/index'
|
||||
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
|
||||
import {
|
||||
GlobalSetting,
|
||||
CollapseItem,
|
||||
SettingItemBox,
|
||||
SettingItem
|
||||
} from '@/components/Pages/ChartItemSetting'
|
||||
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
|
||||
|
||||
const props = defineProps({
|
||||
optionData: {
|
||||
type: Object as PropType<GlobalThemeJsonType>,
|
||||
required: true
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const seriesList = computed(() => {
|
||||
|
||||
@@ -43,23 +43,27 @@ const option = reactive({
|
||||
watch(
|
||||
() => chartEditStore.getEditCanvasConfig.chartThemeColor,
|
||||
(newColor: keyof typeof chartColorsSearch) => {
|
||||
if (!isPreview()) {
|
||||
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
|
||||
props.chartConfig.option.series.forEach((value: any, index: number) => {
|
||||
value.areaStyle.color = new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: themeColor[3]
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(0,0,0, 0)'
|
||||
}
|
||||
])
|
||||
})
|
||||
try {
|
||||
if (!isPreview()) {
|
||||
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
|
||||
props.chartConfig.option.series.forEach((value: any, index: number) => {
|
||||
value.areaStyle.color = new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: themeColor[3]
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(0,0,0, 0)'
|
||||
}
|
||||
])
|
||||
})
|
||||
}
|
||||
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
|
||||
props.chartConfig.option = option.value
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
|
||||
props.chartConfig.option = option.value
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
@@ -70,9 +74,6 @@ watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
() => {
|
||||
option.value = props.chartConfig.option
|
||||
},
|
||||
{
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -5,12 +5,9 @@ import { graphic } from 'echarts/core'
|
||||
import { defaultTheme, chartColorsSearch } from '@/settings/chartThemes/index'
|
||||
import dataJson from './data.json'
|
||||
|
||||
export const includes = ['legend', 'xAxis', 'yAxis']
|
||||
export const includes = ['legend', 'xAxis', 'yAxis', 'grid']
|
||||
|
||||
const option = {
|
||||
legend: {
|
||||
show: true
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
@@ -31,6 +28,13 @@ const option = {
|
||||
{
|
||||
type: 'line',
|
||||
smooth: false,
|
||||
symbolSize: 5, //设定实心点的大小
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#fff',
|
||||
fontSize: 12
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
type: 'solid'
|
||||
@@ -52,6 +56,12 @@ const option = {
|
||||
{
|
||||
type: 'line',
|
||||
smooth: false,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#fff',
|
||||
fontSize: 12
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
type: 'solid'
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
<n-input-number
|
||||
v-model:value="item.lineStyle.width"
|
||||
:min="1"
|
||||
:max="100"
|
||||
size="small"
|
||||
placeholder="自动计算"
|
||||
></n-input-number>
|
||||
@@ -25,6 +24,50 @@
|
||||
></n-select>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
<SettingItemBox name="实心点">
|
||||
<SettingItem name="大小">
|
||||
<n-input-number
|
||||
v-model:value="item.symbolSize"
|
||||
:min="1"
|
||||
:max="100"
|
||||
size="small"
|
||||
placeholder="自动计算"
|
||||
></n-input-number>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
<setting-item-box name="标签">
|
||||
<setting-item>
|
||||
<n-space>
|
||||
<n-switch v-model:value="item.label.show" size="small" />
|
||||
<n-text>展示标签</n-text>
|
||||
</n-space>
|
||||
</setting-item>
|
||||
<setting-item name="大小">
|
||||
<n-input-number
|
||||
v-model:value="item.label.fontSize"
|
||||
size="small"
|
||||
:min="1"
|
||||
></n-input-number>
|
||||
</setting-item>
|
||||
<setting-item name="颜色">
|
||||
<n-color-picker
|
||||
size="small"
|
||||
:modes="['hex']"
|
||||
v-model:value="item.label.color"
|
||||
></n-color-picker>
|
||||
</setting-item>
|
||||
<setting-item name="位置">
|
||||
<n-select
|
||||
v-model:value="item.label.position"
|
||||
:options="[
|
||||
{ label: 'top', value: 'top' },
|
||||
{ label: 'left', value: 'left' },
|
||||
{ label: 'right', value: 'right' },
|
||||
{ label: 'bottom', value: 'bottom' },
|
||||
]"
|
||||
/>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
</CollapseItem>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -42,23 +42,27 @@ const option = reactive({
|
||||
watch(
|
||||
() => chartEditStore.getEditCanvasConfig.chartThemeColor,
|
||||
(newColor: keyof typeof chartColorsSearch) => {
|
||||
if (!isPreview()) {
|
||||
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
|
||||
props.chartConfig.option.series.forEach((value: any, index: number) => {
|
||||
value.areaStyle.color = new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: themeColor[3 + index]
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(0,0,0, 0)'
|
||||
}
|
||||
])
|
||||
})
|
||||
try {
|
||||
if (!isPreview()) {
|
||||
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
|
||||
props.chartConfig.option.series.forEach((value: any, index: number) => {
|
||||
value.areaStyle.color = new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: themeColor[3 + index]
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(0,0,0, 0)'
|
||||
}
|
||||
])
|
||||
})
|
||||
}
|
||||
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
|
||||
props.chartConfig.option = option.value
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
|
||||
props.chartConfig.option = option.value
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
@@ -71,6 +75,5 @@ watch(
|
||||
option.value = props.chartConfig.option
|
||||
}
|
||||
)
|
||||
|
||||
const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore)
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CreateComponentType } from '@/packages/index.d'
|
||||
import { defaultTheme, chartColorsSearch } from '@/settings/chartThemes/index'
|
||||
import dataJson from './data.json'
|
||||
|
||||
export const includes = ['legend', 'xAxis', 'yAxis']
|
||||
export const includes = ['legend', 'xAxis', 'yAxis', 'grid']
|
||||
|
||||
export const option = {
|
||||
tooltip: {
|
||||
@@ -14,9 +14,6 @@ export const option = {
|
||||
type: 'line'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: true
|
||||
},
|
||||
xAxis: {
|
||||
show: true,
|
||||
type: 'category'
|
||||
@@ -29,6 +26,7 @@ export const option = {
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
symbolSize: 5, //设定实心点的大小
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
width: 3,
|
||||
|
||||
@@ -39,6 +39,17 @@
|
||||
></n-select>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
<SettingItemBox name="实心点">
|
||||
<SettingItem name="大小">
|
||||
<n-input-number
|
||||
v-model:value="item.symbolSize"
|
||||
:min="1"
|
||||
:max="100"
|
||||
size="small"
|
||||
placeholder="自动计算"
|
||||
></n-input-number>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
<SettingItemBox name="阴影" :alone="true">
|
||||
<SettingItem name="颜色">
|
||||
<n-color-picker
|
||||
|
||||
@@ -43,17 +43,21 @@ const option = reactive({
|
||||
watch(
|
||||
() => chartEditStore.getEditCanvasConfig.chartThemeColor,
|
||||
(newColor: keyof typeof chartColorsSearch) => {
|
||||
if (!isPreview()) {
|
||||
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
|
||||
props.chartConfig.option.series.forEach((value: any) => {
|
||||
value.lineStyle.shadowColor = themeColor[2]
|
||||
value.lineStyle.color.colorStops.forEach((v: { color: string }, i: number) => {
|
||||
v.color = themeColor[i]
|
||||
try {
|
||||
if (!isPreview()) {
|
||||
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
|
||||
props.chartConfig.option.series.forEach((value: any) => {
|
||||
value.lineStyle.shadowColor = themeColor[2]
|
||||
value.lineStyle.color.colorStops.forEach((v: { color: string }, i: number) => {
|
||||
v.color = themeColor[i]
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
|
||||
props.chartConfig.option = option.value
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
|
||||
props.chartConfig.option = option.value
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
|
||||
@@ -64,9 +64,9 @@ registerMap(props.chartConfig.option.mapRegion.adcode, { geoJSON: {} as any, spe
|
||||
// 进行更换初始化地图 如果为china 单独处理
|
||||
const registerMapInitAsync = async () => {
|
||||
await nextTick()
|
||||
if (props.chartConfig.option.mapRegion.adcode!="china") {
|
||||
if (props.chartConfig.option.mapRegion.adcode != 'china') {
|
||||
await getGeojson(props.chartConfig.option.mapRegion.adcode)
|
||||
}else{
|
||||
} else {
|
||||
await hainanLandsHandle(props.chartConfig.option.mapRegion.showHainanIsLands)
|
||||
}
|
||||
vEchartsSetOption()
|
||||
@@ -90,12 +90,12 @@ const dataSetHandle = async (dataset: any) => {
|
||||
isPreview() && vEchartsSetOption()
|
||||
}
|
||||
// 处理海南群岛
|
||||
const hainanLandsHandle=async(newData:boolean)=>{
|
||||
const hainanLandsHandle = async (newData: boolean) => {
|
||||
if (newData) {
|
||||
await getGeojson('china')
|
||||
} else {
|
||||
registerMap('china', { geoJSON: mapJsonWithoutHainanIsLands as any, specialAreas: {} })
|
||||
}
|
||||
await getGeojson('china')
|
||||
} else {
|
||||
registerMap('china', { geoJSON: mapJsonWithoutHainanIsLands as any, specialAreas: {} })
|
||||
}
|
||||
}
|
||||
//监听 dataset 数据发生变化
|
||||
watch(
|
||||
@@ -113,8 +113,12 @@ watch(
|
||||
watch(
|
||||
() => props.chartConfig.option.mapRegion.showHainanIsLands,
|
||||
async newData => {
|
||||
await hainanLandsHandle(newData)
|
||||
vEchartsSetOption()
|
||||
try {
|
||||
await hainanLandsHandle(newData)
|
||||
vEchartsSetOption()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: false
|
||||
@@ -125,12 +129,16 @@ watch(
|
||||
watch(
|
||||
() => props.chartConfig.option.mapRegion.adcode,
|
||||
async newData => {
|
||||
await getGeojson(newData)
|
||||
props.chartConfig.option.geo.map = newData
|
||||
props.chartConfig.option.series.forEach((item: any) => {
|
||||
if (item.type === 'map') item.map = newData
|
||||
})
|
||||
vEchartsSetOption()
|
||||
try {
|
||||
await getGeojson(newData)
|
||||
props.chartConfig.option.geo.map = newData
|
||||
props.chartConfig.option.series.forEach((item: any) => {
|
||||
if (item.type === 'map') item.map = newData
|
||||
})
|
||||
vEchartsSetOption()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: false
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CreateComponentType } from '@/packages/index.d'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import dataJson from './data.json'
|
||||
|
||||
export const includes = ['xAxis', 'yAxis']
|
||||
export const includes = ['xAxis', 'yAxis', 'grid']
|
||||
|
||||
export const option = {
|
||||
dataset: { ...dataJson },
|
||||
|
||||
@@ -75,7 +75,11 @@ const dataSetHandle = (dataset: typeof dataJson) => {
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
newData => {
|
||||
dataSetHandle(newData)
|
||||
try {
|
||||
dataSetHandle(newData)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: false
|
||||
|
||||
@@ -57,7 +57,11 @@ const option = shallowReactive({
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
(newData: any) => {
|
||||
option.dataset = toNumber(newData, 2)
|
||||
try {
|
||||
option.dataset = toNumber(newData, 2)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: false
|
||||
|
||||
@@ -57,7 +57,11 @@ const dataSetHandle = (dataset: typeof dataJson) => {
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
newData => {
|
||||
dataSetHandle(newData)
|
||||
try {
|
||||
dataSetHandle(newData)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: false
|
||||
|
||||
@@ -49,8 +49,12 @@ const dataSetHandle = (dataset: typeof dataJson) => {
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
newData => {
|
||||
if(!isArray(newData)) return
|
||||
dataSetHandle(newData)
|
||||
try {
|
||||
if (!isArray(newData)) return
|
||||
dataSetHandle(newData)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: false
|
||||
|
||||
@@ -42,23 +42,27 @@ const option = reactive({
|
||||
watch(
|
||||
() => chartEditStore.getEditCanvasConfig.chartThemeColor,
|
||||
(newColor: keyof typeof chartColorsSearch) => {
|
||||
if (!isPreview()) {
|
||||
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
|
||||
// 背景颜色
|
||||
props.chartConfig.option.series[0].backgroundStyle.color = themeColor[2]
|
||||
// 水球颜色
|
||||
props.chartConfig.option.series[0].color[0].colorStops = [
|
||||
{
|
||||
offset: 0,
|
||||
color: themeColor[0]
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: themeColor[1]
|
||||
}
|
||||
]
|
||||
try {
|
||||
if (!isPreview()) {
|
||||
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
|
||||
// 背景颜色
|
||||
props.chartConfig.option.series[0].backgroundStyle.color = themeColor[2]
|
||||
// 水球颜色
|
||||
props.chartConfig.option.series[0].color[0].colorStops = [
|
||||
{
|
||||
offset: 0,
|
||||
color: themeColor[0]
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: themeColor[1]
|
||||
}
|
||||
]
|
||||
}
|
||||
option.value = props.chartConfig.option
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
option.value = props.chartConfig.option
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
@@ -75,7 +79,7 @@ const dataHandle = (newData: number | string) => {
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
newData => {
|
||||
if(!isString(newData) && !isNumber(newData)) return
|
||||
if (!isString(newData) && !isNumber(newData)) return
|
||||
props.chartConfig.option.series[0].data = [dataHandle(newData)]
|
||||
option.value = props.chartConfig.option
|
||||
},
|
||||
|
||||
@@ -48,7 +48,13 @@ const dataHandle = (newData: any) => {
|
||||
// 配置时
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
newData => dataHandle(newData),
|
||||
newData => {
|
||||
try {
|
||||
dataHandle(newData)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
|
||||
@@ -39,15 +39,19 @@ const option = computed(() => {
|
||||
watch(
|
||||
() => props.chartConfig.option.type,
|
||||
newData => {
|
||||
if (newData === 'nomal') {
|
||||
props.chartConfig.option.series[0].radius = '70%'
|
||||
props.chartConfig.option.series[0].roseType = false
|
||||
} else if (newData === 'ring') {
|
||||
props.chartConfig.option.series[0].radius = ['40%', '65%']
|
||||
props.chartConfig.option.series[0].roseType = false
|
||||
} else {
|
||||
props.chartConfig.option.series[0].radius = '70%'
|
||||
props.chartConfig.option.series[0].roseType = true
|
||||
try {
|
||||
if (newData === 'nomal') {
|
||||
props.chartConfig.option.series[0].radius = '70%'
|
||||
props.chartConfig.option.series[0].roseType = false
|
||||
} else if (newData === 'ring') {
|
||||
props.chartConfig.option.series[0].radius = ['40%', '65%']
|
||||
props.chartConfig.option.series[0].roseType = false
|
||||
} else {
|
||||
props.chartConfig.option.series[0].radius = '70%'
|
||||
props.chartConfig.option.series[0].roseType = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{ deep: false, immediate: true }
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CreateComponentType } from '@/packages/index.d'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import dataJson from './data.json'
|
||||
|
||||
export const includes = ['legend', 'xAxis', 'yAxis']
|
||||
export const includes = ['legend', 'xAxis', 'yAxis', 'grid']
|
||||
|
||||
export const seriesItem = {
|
||||
type: 'scatter',
|
||||
|
||||
@@ -69,17 +69,21 @@ const option = computed(() => {
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
(newData, oldData) => {
|
||||
if (!isArray(newData)) return
|
||||
if (newData?.length !== oldData?.length) {
|
||||
replaceMergeArr.value = ['series']
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
props.chartConfig.option.series = newData.map((item: { dimensions: any[] }, index: number) => ({
|
||||
...seriesItem,
|
||||
datasetIndex: index
|
||||
}))
|
||||
nextTick(() => {
|
||||
replaceMergeArr.value = []
|
||||
})
|
||||
try {
|
||||
if (!isArray(newData)) return
|
||||
if (Array.isArray(newData)) {
|
||||
replaceMergeArr.value = ['series']
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
props.chartConfig.option.series = newData.map((item: { dimensions: any[] }, index: number) => ({
|
||||
...seriesItem,
|
||||
datasetIndex: index
|
||||
}))
|
||||
nextTick(() => {
|
||||
replaceMergeArr.value = []
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CreateComponentType } from '@/packages/index.d'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import dataJson from './data.json'
|
||||
|
||||
export const includes = ['legend', 'xAxis', 'yAxis']
|
||||
export const includes = ['legend', 'xAxis', 'yAxis', 'grid']
|
||||
|
||||
export const option = {
|
||||
dataset: dataJson,
|
||||
|
||||
@@ -133,10 +133,14 @@ const renderCountdown: CountdownProps['render'] = ({ hours, minutes, seconds })
|
||||
}
|
||||
|
||||
const updateTotalDuration = () => {
|
||||
countdownActive.value = false
|
||||
totalDuration.value = useEndDate.value ? endDate.value - new Date().getTime() : dataset.value * 1000
|
||||
countdownRef.value?.reset && countdownRef.value?.reset()
|
||||
countdownActive.value = true
|
||||
try {
|
||||
countdownActive.value = false
|
||||
totalDuration.value = useEndDate.value ? endDate.value - new Date().getTime() : dataset.value * 1000
|
||||
countdownRef.value?.reset && countdownRef.value?.reset()
|
||||
countdownActive.value = true
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
|
||||
@@ -60,7 +60,11 @@ const updateDatasetHandler = (newVal: string | number) => {
|
||||
watch(
|
||||
() => props.chartConfig.option,
|
||||
newVal => {
|
||||
updateDatasetHandler((newVal as OptionType).dataset)
|
||||
try {
|
||||
updateDatasetHandler((newVal as OptionType).dataset)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
|
||||
@@ -11,23 +11,23 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType, toRefs, ref, reactive, watch, onMounted, onUnmounted } from "vue";
|
||||
import { CreateComponentType } from "@/packages/index.d";
|
||||
import { useChartEditStore } from "@/store/modules/chartEditStore/chartEditStore";
|
||||
import { useChartDataFetch } from "@/hooks";
|
||||
import { PropType, toRefs, ref, reactive, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
|
||||
import { useChartDataFetch } from '@/hooks'
|
||||
|
||||
const props = defineProps({
|
||||
chartConfig: {
|
||||
type: Object as PropType<CreateComponentType>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
let yearMonthDay = ref("2021-2-3");
|
||||
let nowData = ref("08:00:00");
|
||||
let newData = ref("2021-2-3 08:00:00");
|
||||
let boxShadow = ref("none");
|
||||
required: true
|
||||
}
|
||||
})
|
||||
let yearMonthDay = ref('2021-2-3')
|
||||
let nowData = ref('08:00:00')
|
||||
let newData = ref('2021-2-3 08:00:00')
|
||||
let boxShadow = ref('none')
|
||||
|
||||
const { w, h } = toRefs(props.chartConfig.attr);
|
||||
const { w, h } = toRefs(props.chartConfig.attr)
|
||||
|
||||
let {
|
||||
timeColor,
|
||||
@@ -39,54 +39,55 @@ let {
|
||||
hShadow,
|
||||
vShadow,
|
||||
blurShadow,
|
||||
colorShadow,
|
||||
} = toRefs(props.chartConfig.option);
|
||||
colorShadow
|
||||
} = toRefs(props.chartConfig.option)
|
||||
|
||||
watch(
|
||||
props.chartConfig.option,
|
||||
() => {
|
||||
if (props.chartConfig.option.showShadow) {
|
||||
boxShadow.value = `${props.chartConfig.option.hShadow}px ${props.chartConfig.option.vShadow}px ${props.chartConfig.option.blurShadow}px ${props.chartConfig.option.colorShadow}`;
|
||||
} else {
|
||||
boxShadow.value = "none";
|
||||
try {
|
||||
if (props.chartConfig.option.showShadow) {
|
||||
boxShadow.value = `${props.chartConfig.option.hShadow}px ${props.chartConfig.option.vShadow}px ${props.chartConfig.option.blurShadow}px ${props.chartConfig.option.colorShadow}`
|
||||
} else {
|
||||
boxShadow.value = 'none'
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
)
|
||||
onMounted(() => {
|
||||
const timer = setInterval(() => {
|
||||
var datetime = new Date();
|
||||
var year = datetime.getFullYear();
|
||||
var month =
|
||||
datetime.getMonth() + 1 < 10
|
||||
? "0" + (datetime.getMonth() + 1)
|
||||
: datetime.getMonth() + 1;
|
||||
var date = datetime.getDate() < 10 ? "0" + datetime.getDate() : datetime.getDate();
|
||||
var hh = datetime.getHours(); // 时
|
||||
var mm = datetime.getMinutes(); // 分
|
||||
var ss = datetime.getSeconds(); // 分
|
||||
let time = "";
|
||||
if (hh < 10) time += "0";
|
||||
time += hh + ":";
|
||||
if (mm < 10) time += "0";
|
||||
time += mm + ":";
|
||||
if (ss < 10) time += "0";
|
||||
time += ss;
|
||||
yearMonthDay.value = `${year}-${month}-${date}`;
|
||||
nowData.value = time;
|
||||
newData.value = yearMonthDay.value + " " + nowData.value;
|
||||
}, 500);
|
||||
});
|
||||
var datetime = new Date()
|
||||
var year = datetime.getFullYear()
|
||||
var month = datetime.getMonth() + 1 < 10 ? '0' + (datetime.getMonth() + 1) : datetime.getMonth() + 1
|
||||
var date = datetime.getDate() < 10 ? '0' + datetime.getDate() : datetime.getDate()
|
||||
var hh = datetime.getHours() // 时
|
||||
var mm = datetime.getMinutes() // 分
|
||||
var ss = datetime.getSeconds() // 分
|
||||
let time = ''
|
||||
if (hh < 10) time += '0'
|
||||
time += hh + ':'
|
||||
if (mm < 10) time += '0'
|
||||
time += mm + ':'
|
||||
if (ss < 10) time += '0'
|
||||
time += ss
|
||||
yearMonthDay.value = `${year}-${month}-${date}`
|
||||
nowData.value = time
|
||||
newData.value = yearMonthDay.value + ' ' + nowData.value
|
||||
}, 500)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
clearInterval();
|
||||
});
|
||||
useChartDataFetch(props.chartConfig, useChartEditStore);
|
||||
clearInterval()
|
||||
})
|
||||
useChartDataFetch(props.chartConfig, useChartEditStore)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@include go("decorates-number") {
|
||||
@include go('decorates-number') {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-nocheck
|
||||
import {
|
||||
ArcCurve,
|
||||
BufferAttribute,
|
||||
BufferGeometry,
|
||||
Color,
|
||||
Line,
|
||||
LineBasicMaterial,
|
||||
Points,
|
||||
PointsMaterial,
|
||||
Quaternion,
|
||||
Vector3
|
||||
} from 'three'
|
||||
import { lon2xyz } from './common'
|
||||
|
||||
/*
|
||||
* 绘制一条圆弧飞线
|
||||
* 5个参数含义:( 飞线圆弧轨迹半径, 开始角度, 结束角度)
|
||||
*/
|
||||
function createFlyLine(radius, startAngle, endAngle, color) {
|
||||
const geometry = new BufferGeometry() //声明一个几何体对象BufferGeometry
|
||||
// ArcCurve创建圆弧曲线
|
||||
const arc = new ArcCurve(0, 0, radius, startAngle, endAngle, false)
|
||||
//getSpacedPoints是基类Curve的方法,返回一个vector2对象作为元素组成的数组
|
||||
const pointsArr = arc.getSpacedPoints(100) //分段数80,返回81个顶点
|
||||
geometry.setFromPoints(pointsArr) // setFromPoints方法从pointsArr中提取数据改变几何体的顶点属性vertices
|
||||
// 每个顶点对应一个百分比数据attributes.percent 用于控制点的渲染大小
|
||||
const percentArr = [] //attributes.percent的数据
|
||||
for (let i = 0; i < pointsArr.length; i++) {
|
||||
percentArr.push(i / pointsArr.length)
|
||||
}
|
||||
const percentAttribue = new BufferAttribute(new Float32Array(percentArr), 1)
|
||||
// 通过顶点数据percent点模型从大到小变化,产生小蝌蚪形状飞线
|
||||
geometry.attributes.percent = percentAttribue
|
||||
// 批量计算所有顶点颜色数据
|
||||
const colorArr = []
|
||||
for (let i = 0; i < pointsArr.length; i++) {
|
||||
const color1 = new Color(0xec8f43) //轨迹线颜色 青色
|
||||
const color2 = new Color(0xf3ae76) //黄色
|
||||
const color = color1.lerp(color2, i / pointsArr.length)
|
||||
colorArr.push(color.r, color.g, color.b)
|
||||
}
|
||||
// 设置几何体顶点颜色数据
|
||||
geometry.attributes.color = new BufferAttribute(new Float32Array(colorArr), 3)
|
||||
const size = 1.3
|
||||
// 点模型渲染几何体每个顶点
|
||||
const material = new PointsMaterial({
|
||||
size, //点大小
|
||||
// vertexColors: VertexColors, //使用顶点颜色渲染
|
||||
transparent: true,
|
||||
depthWrite: false
|
||||
})
|
||||
// 修改点材质的着色器源码(注意:不同版本细节可能会稍微会有区别,不过整体思路是一样的)
|
||||
material.onBeforeCompile = function (shader) {
|
||||
// 顶点着色器中声明一个attribute变量:百分比
|
||||
shader.vertexShader = shader.vertexShader.replace(
|
||||
'void main() {',
|
||||
[
|
||||
'attribute float percent;', //顶点大小百分比变量,控制点渲染大小
|
||||
'void main() {'
|
||||
].join('\n') // .join()把数组元素合成字符串
|
||||
)
|
||||
// 调整点渲染大小计算方式
|
||||
shader.vertexShader = shader.vertexShader.replace(
|
||||
'gl_PointSize = size;',
|
||||
['gl_PointSize = percent * size;'].join('\n') // .join()把数组元素合成字符串
|
||||
)
|
||||
}
|
||||
const FlyLine = new Points(geometry, material)
|
||||
material.color = new Color(color)
|
||||
FlyLine.name = '飞行线'
|
||||
|
||||
return FlyLine
|
||||
}
|
||||
|
||||
/**输入地球上任意两点的经纬度坐标,通过函数flyArc可以绘制一个飞线圆弧轨迹
|
||||
* lon1,lat1:轨迹线起点经纬度坐标
|
||||
* lon2,lat2:轨迹线结束点经纬度坐标
|
||||
*/
|
||||
function flyArc(radius, lon1, lat1, lon2, lat2, options) {
|
||||
const sphereCoord1 = lon2xyz(radius, lon1, lat1) //经纬度坐标转球面坐标
|
||||
// startSphereCoord:轨迹线起点球面坐标
|
||||
const startSphereCoord = new Vector3(sphereCoord1.x, sphereCoord1.y, sphereCoord1.z)
|
||||
const sphereCoord2 = lon2xyz(radius, lon2, lat2)
|
||||
// startSphereCoord:轨迹线结束点球面坐标
|
||||
const endSphereCoord = new Vector3(sphereCoord2.x, sphereCoord2.y, sphereCoord2.z)
|
||||
|
||||
//计算绘制圆弧需要的关于y轴对称的起点、结束点和旋转四元数
|
||||
const startEndQua = _3Dto2D(startSphereCoord, endSphereCoord)
|
||||
// 调用arcXOY函数绘制一条圆弧飞线轨迹
|
||||
const arcline = arcXOY(radius, startEndQua.startPoint, startEndQua.endPoint, options)
|
||||
arcline.quaternion.multiply(startEndQua.quaternion)
|
||||
return arcline
|
||||
}
|
||||
/*
|
||||
* 把3D球面上任意的两个飞线起点和结束点绕球心旋转到到XOY平面上,
|
||||
* 同时保持关于y轴对称,借助旋转得到的新起点和新结束点绘制
|
||||
* 一个圆弧,最后把绘制的圆弧反向旋转到原来的起点和结束点即可
|
||||
*/
|
||||
function _3Dto2D(startSphere, endSphere) {
|
||||
/*计算第一次旋转的四元数:表示从一个平面如何旋转到另一个平面*/
|
||||
const origin = new Vector3(0, 0, 0) //球心坐标
|
||||
const startDir = startSphere.clone().sub(origin) //飞线起点与球心构成方向向量
|
||||
const endDir = endSphere.clone().sub(origin) //飞线结束点与球心构成方向向量
|
||||
// dir1和dir2构成一个三角形,.cross()叉乘计算该三角形法线normal
|
||||
const normal = startDir.clone().cross(endDir).normalize()
|
||||
const xoyNormal = new Vector3(0, 0, 1) //XOY平面的法线
|
||||
//.setFromUnitVectors()计算从normal向量旋转达到xoyNormal向量所需要的四元数
|
||||
// quaternion表示把球面飞线旋转到XOY平面上需要的四元数
|
||||
const quaternion3D_XOY = new Quaternion().setFromUnitVectors(normal, xoyNormal)
|
||||
/*第一次旋转:飞线起点、结束点从3D空间第一次旋转到XOY平面*/
|
||||
const startSphereXOY = startSphere.clone().applyQuaternion(quaternion3D_XOY)
|
||||
const endSphereXOY = endSphere.clone().applyQuaternion(quaternion3D_XOY)
|
||||
|
||||
/*计算第二次旋转的四元数*/
|
||||
// middleV3:startSphereXOY和endSphereXOY的中点
|
||||
const middleV3 = startSphereXOY.clone().add(endSphereXOY).multiplyScalar(0.5)
|
||||
const midDir = middleV3.clone().sub(origin).normalize() // 旋转前向量midDir,中点middleV3和球心构成的方向向量
|
||||
const yDir = new Vector3(0, 1, 0) // 旋转后向量yDir,即y轴
|
||||
// .setFromUnitVectors()计算从midDir向量旋转达到yDir向量所需要的四元数
|
||||
// quaternion2表示让第一次旋转到XOY平面的起点和结束点关于y轴对称需要的四元数
|
||||
const quaternionXOY_Y = new Quaternion().setFromUnitVectors(midDir, yDir)
|
||||
|
||||
/*第二次旋转:使旋转到XOY平面的点再次旋转,实现关于Y轴对称*/
|
||||
const startSpherXOY_Y = startSphereXOY.clone().applyQuaternion(quaternionXOY_Y)
|
||||
const endSphereXOY_Y = endSphereXOY.clone().applyQuaternion(quaternionXOY_Y)
|
||||
|
||||
/**一个四元数表示一个旋转过程
|
||||
*.invert()方法表示四元数的逆,简单说就是把旋转过程倒过来
|
||||
* 两次旋转的四元数执行.invert()求逆,然后执行.multiply()相乘
|
||||
*新版本.invert()对应旧版本.invert()
|
||||
*/
|
||||
const quaternionInverse = quaternion3D_XOY.clone().invert().multiply(quaternionXOY_Y.clone().invert())
|
||||
return {
|
||||
// 返回两次旋转四元数的逆四元数
|
||||
quaternion: quaternionInverse,
|
||||
// 范围两次旋转后在XOY平面上关于y轴对称的圆弧起点和结束点坐标
|
||||
startPoint: startSpherXOY_Y,
|
||||
endPoint: endSphereXOY_Y
|
||||
}
|
||||
}
|
||||
/**通过函数arcXOY()可以在XOY平面上绘制一个关于y轴对称的圆弧曲线
|
||||
* startPoint, endPoint:表示圆弧曲线的起点和结束点坐标值,起点和结束点关于y轴对称
|
||||
* 同时在圆弧轨迹的基础上绘制一段飞线*/
|
||||
function arcXOY(radius, startPoint, endPoint, options) {
|
||||
// 计算两点的中点
|
||||
const middleV3 = new Vector3().addVectors(startPoint, endPoint).multiplyScalar(0.5)
|
||||
// 弦垂线的方向dir(弦的中点和圆心构成的向量)
|
||||
const dir = middleV3.clone().normalize()
|
||||
// 计算球面飞线的起点、结束点和球心构成夹角的弧度值
|
||||
const earthRadianAngle = radianAOB(startPoint, endPoint, new Vector3(0, 0, 0))
|
||||
/*设置飞线轨迹圆弧的中间点坐标
|
||||
弧度值 * radius * 0.2:表示飞线轨迹圆弧顶部距离地球球面的距离
|
||||
起点、结束点相聚越远,构成的弧线顶部距离球面越高*/
|
||||
const arcTopCoord = dir.multiplyScalar(radius + earthRadianAngle * radius * 0.2) // 黄色飞行线的高度
|
||||
//求三个点的外接圆圆心(飞线圆弧轨迹的圆心坐标)
|
||||
const flyArcCenter = threePointCenter(startPoint, endPoint, arcTopCoord)
|
||||
// 飞线圆弧轨迹半径flyArcR
|
||||
const flyArcR = Math.abs(flyArcCenter.y - arcTopCoord.y)
|
||||
/*坐标原点和飞线起点构成直线和y轴负半轴夹角弧度值
|
||||
参数分别是:飞线圆弧起点、y轴负半轴上一点、飞线圆弧圆心*/
|
||||
const flyRadianAngle = radianAOB(startPoint, new Vector3(0, -1, 0), flyArcCenter)
|
||||
const startAngle = -Math.PI / 2 + flyRadianAngle //飞线圆弧开始角度
|
||||
const endAngle = Math.PI - startAngle //飞线圆弧结束角度
|
||||
// 调用圆弧线模型的绘制函数
|
||||
const arcline = circleLine(flyArcCenter.x, flyArcCenter.y, flyArcR, startAngle, endAngle, options.color)
|
||||
// const arcline = new Group();// 不绘制轨迹线,使用 Group替换circleLine()即可
|
||||
arcline.center = flyArcCenter //飞线圆弧自定一个属性表示飞线圆弧的圆心
|
||||
arcline.topCoord = arcTopCoord //飞线圆弧自定一个属性表示飞线圆弧中间也就是顶部坐标
|
||||
|
||||
// const flyAngle = Math.PI/ 10; //飞线圆弧固定弧度
|
||||
const flyAngle = (endAngle - startAngle) / 7 //飞线圆弧的弧度和轨迹线弧度相关
|
||||
// 绘制一段飞线,圆心做坐标原点
|
||||
const flyLine = createFlyLine(flyArcR, startAngle, startAngle + flyAngle, options.flyLineColor)
|
||||
flyLine.position.y = flyArcCenter.y //平移飞线圆弧和飞线轨迹圆弧重合
|
||||
//飞线段flyLine作为飞线轨迹arcLine子对象,继承飞线轨迹平移旋转等变换
|
||||
arcline.add(flyLine)
|
||||
//飞线段运动范围startAngle~flyEndAngle
|
||||
flyLine.flyEndAngle = endAngle - startAngle - flyAngle
|
||||
flyLine.startAngle = startAngle
|
||||
// arcline.flyEndAngle:飞线段当前角度位置,这里设置了一个随机值用于演示
|
||||
flyLine.AngleZ = arcline.flyEndAngle * Math.random()
|
||||
// flyLine.rotation.z = arcline.AngleZ;
|
||||
// arcline.flyLine指向飞线段,便于设置动画是访问飞线段
|
||||
arcline.userData['flyLine'] = flyLine
|
||||
|
||||
return arcline
|
||||
}
|
||||
/*计算球面上两点和球心构成夹角的弧度值
|
||||
参数point1, point2:表示地球球面上两点坐标Vector3
|
||||
计算A、B两点和顶点O构成的AOB夹角弧度值*/
|
||||
function radianAOB(A, B, O) {
|
||||
// dir1、dir2:球面上两个点和球心构成的方向向量
|
||||
const dir1 = A.clone().sub(O).normalize()
|
||||
const dir2 = B.clone().sub(O).normalize()
|
||||
//点乘.dot()计算夹角余弦值
|
||||
const cosAngle = dir1.clone().dot(dir2)
|
||||
const radianAngle = Math.acos(cosAngle) //余弦值转夹角弧度值,通过余弦值可以计算夹角范围是0~180度
|
||||
return radianAngle
|
||||
}
|
||||
/*绘制一条圆弧曲线模型Line
|
||||
5个参数含义:(圆心横坐标, 圆心纵坐标, 飞线圆弧轨迹半径, 开始角度, 结束角度)*/
|
||||
function circleLine(x, y, r, startAngle, endAngle, color) {
|
||||
const geometry = new BufferGeometry() //声明一个几何体对象Geometry
|
||||
// ArcCurve创建圆弧曲线
|
||||
const arc = new ArcCurve(x, y, r, startAngle, endAngle, false)
|
||||
//getSpacedPoints是基类Curve的方法,返回一个vector2对象作为元素组成的数组
|
||||
const points = arc.getSpacedPoints(80) //分段数50,返回51个顶点
|
||||
geometry.setFromPoints(points) // setFromPoints方法从points中提取数据改变几何体的顶点属性vertices
|
||||
const material = new LineBasicMaterial({
|
||||
color: color || 0xd18547
|
||||
}) //线条材质
|
||||
const line = new Line(geometry, material) //线条模型对象
|
||||
return line
|
||||
}
|
||||
//求三个点的外接圆圆心,p1, p2, p3表示三个点的坐标Vector3。
|
||||
function threePointCenter(p1, p2, p3) {
|
||||
const L1 = p1.lengthSq() //p1到坐标原点距离的平方
|
||||
const L2 = p2.lengthSq()
|
||||
const L3 = p3.lengthSq()
|
||||
const x1 = p1.x,
|
||||
y1 = p1.y,
|
||||
x2 = p2.x,
|
||||
y2 = p2.y,
|
||||
x3 = p3.x,
|
||||
y3 = p3.y
|
||||
const S = x1 * y2 + x2 * y3 + x3 * y1 - x1 * y3 - x2 * y1 - x3 * y2
|
||||
const x = (L2 * y3 + L1 * y2 + L3 * y1 - L2 * y1 - L3 * y2 - L1 * y3) / S / 2
|
||||
const y = (L3 * x2 + L2 * x1 + L1 * x3 - L1 * x2 - L2 * x3 - L3 * x1) / S / 2
|
||||
// 三点外接圆圆心坐标
|
||||
const center = new Vector3(x, y, 0)
|
||||
return center
|
||||
}
|
||||
|
||||
export { arcXOY, flyArc }
|
||||
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
CatmullRomCurve3,
|
||||
DoubleSide,
|
||||
Group,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
PlaneGeometry,
|
||||
Texture,
|
||||
TubeGeometry,
|
||||
Vector3
|
||||
} from 'three'
|
||||
import { punctuation } from '../world/Earth'
|
||||
|
||||
/**
|
||||
* 经纬度坐标转球面坐标
|
||||
* @param {地球半径} R
|
||||
* @param {经度(角度值)} longitude
|
||||
* @param {维度(角度值)} latitude
|
||||
*/
|
||||
export const lon2xyz = (R: number, longitude: number, latitude: number): Vector3 => {
|
||||
let lon = (longitude * Math.PI) / 180 // 转弧度值
|
||||
const lat = (latitude * Math.PI) / 180 // 转弧度值
|
||||
lon = -lon // js坐标系z坐标轴对应经度-90度,而不是90度
|
||||
|
||||
// 经纬度坐标转球面坐标计算公式
|
||||
const x = R * Math.cos(lat) * Math.cos(lon)
|
||||
const y = R * Math.sin(lat)
|
||||
const z = R * Math.cos(lat) * Math.sin(lon)
|
||||
// 返回球面坐标
|
||||
return new Vector3(x, y, z)
|
||||
}
|
||||
|
||||
// 创建波动光圈
|
||||
export const createWaveMesh = (options: { radius: number; lon: number; lat: number; textures: any }) => {
|
||||
const geometry = new PlaneGeometry(1, 1) //默认在XOY平面上
|
||||
const texture = options.textures.aperture
|
||||
|
||||
const material = new MeshBasicMaterial({
|
||||
color: 0xe99f68,
|
||||
map: texture,
|
||||
transparent: true, //使用背景透明的png贴图,注意开启透明计算
|
||||
opacity: 1.0,
|
||||
depthWrite: false //禁止写入深度缓冲区数据
|
||||
})
|
||||
const mesh = new Mesh(geometry, material)
|
||||
// 经纬度转球面坐标
|
||||
const coord = lon2xyz(options.radius * 1.001, options.lon, options.lat)
|
||||
const size = options.radius * 0.12 //矩形平面Mesh的尺寸
|
||||
mesh.scale.set(size, size, size) //设置mesh大小
|
||||
mesh.userData['size'] = size //自顶一个属性,表示mesh静态大小
|
||||
mesh.userData['scale'] = Math.random() * 1.0 //自定义属性._s表示mesh在原始大小基础上放大倍数 光圈在原来mesh.size基础上1~2倍之间变化
|
||||
mesh.position.set(coord.x, coord.y, coord.z)
|
||||
const coordVec3 = new Vector3(coord.x, coord.y, coord.z).normalize()
|
||||
const meshNormal = new Vector3(0, 0, 1)
|
||||
mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3)
|
||||
return mesh
|
||||
}
|
||||
|
||||
// 创建柱状
|
||||
export const createLightPillar = (options: {
|
||||
radius: number
|
||||
lon: number
|
||||
lat: number
|
||||
index: number
|
||||
textures: Record<string, Texture>
|
||||
punctuation: punctuation
|
||||
}) => {
|
||||
const height = options.radius * 0.3
|
||||
const geometry = new PlaneGeometry(options.radius * 0.05, height)
|
||||
geometry.rotateX(Math.PI / 2)
|
||||
geometry.translate(0, 0, height / 2)
|
||||
const material = new MeshBasicMaterial({
|
||||
map: options.textures.light_column,
|
||||
color: options.index == 0 ? options.punctuation.lightColumn.startColor : options.punctuation.lightColumn.endColor,
|
||||
transparent: true,
|
||||
side: DoubleSide,
|
||||
depthWrite: false //是否对深度缓冲区有任何的影响
|
||||
})
|
||||
const mesh = new Mesh(geometry, material)
|
||||
const group = new Group()
|
||||
// 两个光柱交叉叠加
|
||||
group.add(mesh, mesh.clone().rotateZ(Math.PI / 2)) //几何体绕x轴旋转了,所以mesh旋转轴变为z
|
||||
// 经纬度转球面坐标
|
||||
const SphereCoord = lon2xyz(options.radius, options.lon, options.lat) //SphereCoord球面坐标
|
||||
group.position.set(SphereCoord.x, SphereCoord.y, SphereCoord.z) //设置mesh位置
|
||||
const coordVec3 = new Vector3(SphereCoord.x, SphereCoord.y, SphereCoord.z).normalize()
|
||||
const meshNormal = new Vector3(0, 0, 1)
|
||||
group.quaternion.setFromUnitVectors(meshNormal, coordVec3)
|
||||
return group
|
||||
}
|
||||
|
||||
// 光柱底座矩形平面
|
||||
export const createPointMesh = (options: { radius: number; lon: number; lat: number; material: MeshBasicMaterial }) => {
|
||||
const geometry = new PlaneGeometry(1, 1) //默认在XOY平面上
|
||||
const mesh = new Mesh(geometry, options.material)
|
||||
// 经纬度转球面坐标
|
||||
const coord = lon2xyz(options.radius * 1.001, options.lon, options.lat)
|
||||
const size = options.radius * 0.05 // 矩形平面Mesh的尺寸
|
||||
mesh.scale.set(size, size, size) // 设置mesh大小
|
||||
|
||||
// 设置mesh位置
|
||||
mesh.position.set(coord.x, coord.y, coord.z)
|
||||
const coordVec3 = new Vector3(coord.x, coord.y, coord.z).normalize()
|
||||
const meshNormal = new Vector3(0, 0, 1)
|
||||
mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3)
|
||||
return mesh
|
||||
}
|
||||
|
||||
// 获取点
|
||||
export const getCirclePoints = (option: any) => {
|
||||
const list = []
|
||||
for (let j = 0; j < 2 * Math.PI - 0.1; j += (2 * Math.PI) / (option.number || 100)) {
|
||||
list.push([
|
||||
parseFloat((Math.cos(j) * (option.radius || 10)).toFixed(2)),
|
||||
0,
|
||||
parseFloat((Math.sin(j) * (option.radius || 10)).toFixed(2))
|
||||
])
|
||||
}
|
||||
if (option.closed) list.push(list[0])
|
||||
return list
|
||||
}
|
||||
|
||||
// 创建线
|
||||
|
||||
/**
|
||||
* 创建动态的线
|
||||
*/
|
||||
export const createAnimateLine = (option: any) => {
|
||||
// 由多个点数组构成的曲线 通常用于道路
|
||||
const l: Array<any> = []
|
||||
option.pointList.forEach((e: Array<any>) => l.push(new Vector3(e[0], e[1], e[2])))
|
||||
const curve = new CatmullRomCurve3(l) // 曲线路径
|
||||
|
||||
// 管道体
|
||||
const tubeGeometry = new TubeGeometry(curve, option.number || 50, option.radius || 1, option.radialSegments)
|
||||
return new Mesh(tubeGeometry, option.material)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface IEvents {
|
||||
resize: () => void
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface IWord {
|
||||
dom: HTMLElement
|
||||
data: any
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
uniform vec3 glowColor;
|
||||
uniform float bias;
|
||||
uniform float power;
|
||||
uniform float time;
|
||||
varying vec3 vp;
|
||||
varying vec3 vNormal;
|
||||
varying vec3 vPositionNormal;
|
||||
uniform float scale;
|
||||
// 获取纹理
|
||||
uniform sampler2D map;
|
||||
// 纹理坐标
|
||||
varying vec2 vUv;
|
||||
|
||||
void main(void){
|
||||
float a = pow( bias + scale * abs(dot(vNormal, vPositionNormal)), power );
|
||||
if(vp.y > time && vp.y < time + 20.0) {
|
||||
float t = smoothstep(0.0, 0.8, (1.0 - abs(0.5 - (vp.y - time) / 20.0)) / 3.0 );
|
||||
gl_FragColor = mix(gl_FragColor, vec4(glowColor, 1.0), t * t );
|
||||
}
|
||||
gl_FragColor = mix(gl_FragColor, vec4( glowColor, 1.0 ), a);
|
||||
float b = 0.8;
|
||||
gl_FragColor = gl_FragColor + texture2D( map, vUv );
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
varying vec2 vUv;
|
||||
varying vec3 vNormal;
|
||||
varying vec3 vp;
|
||||
varying vec3 vPositionNormal;
|
||||
void main(void){
|
||||
vUv = uv;
|
||||
vNormal = normalize( normalMatrix * normal ); // 转换到视图空间
|
||||
vp = position;
|
||||
vPositionNormal = normalize(( modelViewMatrix * vec4(position, 1.0) ).xyz);
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 资源文件
|
||||
* 把模型和图片分开进行加载
|
||||
*/
|
||||
|
||||
interface ITextures {
|
||||
name: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface IResources {
|
||||
textures?: ITextures[]
|
||||
}
|
||||
|
||||
const fileSuffix = ['earth', 'gradient', 'redCircle', 'label', 'aperture', 'glow', 'light_column', 'aircraft']
|
||||
const textures: ITextures[] = []
|
||||
|
||||
const modules = import.meta.globEager("../../images/earth/*");
|
||||
|
||||
for(let item in modules) {
|
||||
const n = item.split('/').pop()
|
||||
if(n) {
|
||||
textures.push({
|
||||
name: n.split('.')[0],
|
||||
url: modules[item].default
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const resources: IResources = {
|
||||
textures
|
||||
}
|
||||
|
||||
export { resources }
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 创建 threejs 四大天王
|
||||
* 场景、相机、渲染器、控制器
|
||||
*/
|
||||
|
||||
import * as THREE from 'three'
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||
|
||||
export class Basic {
|
||||
public scene!: THREE.Scene
|
||||
public camera!: THREE.PerspectiveCamera
|
||||
public renderer!: THREE.WebGLRenderer
|
||||
public controls!: OrbitControls
|
||||
public dom: HTMLElement
|
||||
|
||||
constructor(dom: HTMLElement) {
|
||||
this.dom = dom
|
||||
this.initScenes()
|
||||
this.setControls()
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化场景
|
||||
*/
|
||||
initScenes() {
|
||||
this.scene = new THREE.Scene()
|
||||
|
||||
this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100000)
|
||||
this.camera.position.set(0, 30, -250)
|
||||
|
||||
this.renderer = new THREE.WebGLRenderer({
|
||||
// canvas: this.dom,
|
||||
alpha: true, // 透明
|
||||
antialias: true // 抗锯齿
|
||||
})
|
||||
this.renderer.setPixelRatio(window.devicePixelRatio) // 设置屏幕像素比
|
||||
this.renderer.setSize(window.innerWidth, window.innerHeight) // 设置渲染器宽高
|
||||
this.dom.appendChild(this.renderer.domElement) // 添加到dom中
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置控制器
|
||||
*/
|
||||
setControls() {
|
||||
// 鼠标控制 相机,渲染dom
|
||||
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
|
||||
|
||||
this.controls.autoRotateSpeed = 3
|
||||
// 使动画循环使用时阻尼或自转 意思是否有惯性
|
||||
this.controls.enableDamping = true
|
||||
// 动态阻尼系数 就是鼠标拖拽旋转灵敏度
|
||||
this.controls.dampingFactor = 0.05
|
||||
// 是否可以缩放
|
||||
this.controls.enableZoom = true
|
||||
// 设置相机距离原点的最远距离
|
||||
this.controls.minDistance = 100
|
||||
// 设置相机距离原点的最远距离
|
||||
this.controls.maxDistance = 300
|
||||
// 是否开启右键拖拽
|
||||
this.controls.enablePan = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,496 @@
|
||||
import {
|
||||
BufferAttribute,
|
||||
BufferGeometry,
|
||||
Color,
|
||||
DoubleSide,
|
||||
Group,
|
||||
Material,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
NormalBlending,
|
||||
Object3D,
|
||||
Points,
|
||||
PointsMaterial,
|
||||
ShaderMaterial,
|
||||
SphereGeometry,
|
||||
Sprite,
|
||||
SpriteMaterial,
|
||||
Texture,
|
||||
TextureLoader,
|
||||
Vector3
|
||||
} from 'three'
|
||||
|
||||
import {
|
||||
createAnimateLine,
|
||||
createLightPillar,
|
||||
createPointMesh,
|
||||
createWaveMesh,
|
||||
getCirclePoints,
|
||||
lon2xyz
|
||||
} from '../Utils/common'
|
||||
import gsap from 'gsap'
|
||||
import { flyArc } from '../Utils/arc'
|
||||
import earthVertex from '../shaders/earth/vertex.vs?raw'
|
||||
import earthFragment from '../shaders/earth/fragment.fs?raw'
|
||||
|
||||
export type punctuation = {
|
||||
circleColor: number
|
||||
lightColumn: {
|
||||
startColor: number // 起点颜色
|
||||
endColor: number // 终点颜色
|
||||
}
|
||||
}
|
||||
|
||||
type options = {
|
||||
data: {
|
||||
startArray: {
|
||||
name: string
|
||||
E: number // 经度
|
||||
N: number // 维度
|
||||
}
|
||||
endArray: {
|
||||
name: string
|
||||
E: number // 经度
|
||||
N: number // 维度
|
||||
}[]
|
||||
}[]
|
||||
dom: HTMLElement
|
||||
textures: Record<string, Texture> // 贴图
|
||||
earth: {
|
||||
radius: number // 地球半径
|
||||
rotateSpeed: number // 地球旋转速度
|
||||
isRotation: boolean // 地球组是否自转
|
||||
}
|
||||
satellite: {
|
||||
show: boolean // 是否显示卫星
|
||||
rotateSpeed: number // 旋转速度
|
||||
size: number // 卫星大小
|
||||
number: number // 一个圆环几个球
|
||||
}
|
||||
punctuation: punctuation
|
||||
flyLine: {
|
||||
color: number // 飞线的颜色
|
||||
speed: number // 飞机拖尾线速度
|
||||
flyLineColor: number // 飞行线的颜色
|
||||
}
|
||||
}
|
||||
type uniforms = {
|
||||
glowColor: { value: Color }
|
||||
scale: { type: string; value: number }
|
||||
bias: { type: string; value: number }
|
||||
power: { type: string; value: number }
|
||||
time: { type: string; value: any }
|
||||
isHover: { value: boolean }
|
||||
map: { value?: Texture }
|
||||
}
|
||||
|
||||
export default class earth {
|
||||
public group: Group
|
||||
public earthGroup: Group
|
||||
|
||||
public around!: BufferGeometry
|
||||
public aroundPoints!: Points<BufferGeometry, PointsMaterial>
|
||||
|
||||
public options: options
|
||||
public uniforms: uniforms
|
||||
public timeValue: number
|
||||
|
||||
public earth!: Mesh<SphereGeometry, ShaderMaterial>
|
||||
public punctuationMaterial!: MeshBasicMaterial
|
||||
public markupPoint: Group
|
||||
public waveMeshArr: Object3D[]
|
||||
|
||||
public circleLineList: any[]
|
||||
public circleList: any[]
|
||||
public x: number
|
||||
public n: number
|
||||
public isRotation: boolean
|
||||
public flyLineArcGroup!: Group
|
||||
|
||||
constructor(options: options) {
|
||||
this.options = options
|
||||
|
||||
this.group = new Group()
|
||||
this.group.name = 'group'
|
||||
this.group.scale.set(0, 0, 0)
|
||||
this.earthGroup = new Group()
|
||||
this.group.add(this.earthGroup)
|
||||
this.earthGroup.name = 'EarthGroup'
|
||||
|
||||
// 标注点效果
|
||||
this.markupPoint = new Group()
|
||||
this.markupPoint.name = 'markupPoint'
|
||||
this.waveMeshArr = []
|
||||
|
||||
// 卫星和标签
|
||||
this.circleLineList = []
|
||||
this.circleList = []
|
||||
this.x = 0
|
||||
this.n = 0
|
||||
|
||||
// 地球自转
|
||||
this.isRotation = this.options.earth.isRotation
|
||||
|
||||
// 扫光动画 shader
|
||||
this.timeValue = 200
|
||||
|
||||
this.uniforms = {
|
||||
glowColor: {
|
||||
value: new Color(0x0cd1eb)
|
||||
},
|
||||
scale: {
|
||||
type: 'f',
|
||||
value: -1.0
|
||||
},
|
||||
bias: {
|
||||
type: 'f',
|
||||
value: 1.0
|
||||
},
|
||||
power: {
|
||||
type: 'f',
|
||||
value: 3.3
|
||||
},
|
||||
time: {
|
||||
type: 'f',
|
||||
value: this.timeValue
|
||||
},
|
||||
isHover: {
|
||||
value: false
|
||||
},
|
||||
map: {
|
||||
value: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const init = async () => {
|
||||
this.createEarth() // 创建地球
|
||||
this.createEarthGlow() // 创建地球辉光
|
||||
this.createEarthAperture() // 创建地球的大气层
|
||||
await this.createMarkupPoint() // 创建柱状点位
|
||||
this.createAnimateCircle() // 创建环绕卫星
|
||||
this.createFlyLine() // 创建飞线
|
||||
this.show()
|
||||
resolve()
|
||||
}
|
||||
init()
|
||||
})
|
||||
}
|
||||
|
||||
createEarth() {
|
||||
const earth_geometry = new SphereGeometry(this.options.earth.radius, 50, 50)
|
||||
const earth_border = new SphereGeometry(this.options.earth.radius + 10, 60, 60)
|
||||
|
||||
const pointMaterial = new PointsMaterial({
|
||||
color: 0x81ffff, //设置颜色,默认 0xFFFFFF
|
||||
transparent: true,
|
||||
sizeAttenuation: true,
|
||||
opacity: 0.1,
|
||||
vertexColors: false, //定义材料是否使用顶点颜色,默认false ---如果该选项设置为true,则color属性失效
|
||||
size: 0.2 //定义粒子的大小。默认为1.0
|
||||
})
|
||||
const points = new Points(earth_border, pointMaterial) //将模型添加到场景
|
||||
|
||||
this.earthGroup.add(points)
|
||||
|
||||
this.uniforms.map.value = this.options.textures.earth
|
||||
|
||||
const earth_material = new ShaderMaterial({
|
||||
// wireframe:true, // 显示模型线条
|
||||
uniforms: this.uniforms as any,
|
||||
vertexShader: earthVertex,
|
||||
fragmentShader: earthFragment
|
||||
})
|
||||
|
||||
earth_material.needsUpdate = true
|
||||
this.earth = new Mesh(earth_geometry, earth_material)
|
||||
this.earth.name = 'earth'
|
||||
this.earthGroup.add(this.earth)
|
||||
}
|
||||
|
||||
createEarthGlow() {
|
||||
const R = this.options.earth.radius //地球半径
|
||||
|
||||
// TextureLoader创建一个纹理加载器对象,可以加载图片作为纹理贴图
|
||||
const texture = this.options.textures.glow // 加载纹理贴图
|
||||
|
||||
// 创建精灵材质对象SpriteMaterial
|
||||
const spriteMaterial = new SpriteMaterial({
|
||||
map: texture, // 设置精灵纹理贴图
|
||||
color: 0x4390d1,
|
||||
transparent: true, //开启透明
|
||||
opacity: 0.7, // 可以通过透明度整体调节光圈
|
||||
depthWrite: false //禁止写入深度缓冲区数据
|
||||
})
|
||||
|
||||
// 创建表示地球光圈的精灵模型
|
||||
const sprite = new Sprite(spriteMaterial)
|
||||
sprite.scale.set(R * 3.0, R * 3.0, 1) //适当缩放精灵
|
||||
this.earthGroup.add(sprite)
|
||||
}
|
||||
|
||||
createEarthAperture() {
|
||||
const vertexShader = [
|
||||
'varying vec3 vVertexWorldPosition;',
|
||||
'varying vec3 vVertexNormal;',
|
||||
'varying vec4 vFragColor;',
|
||||
'void main(){',
|
||||
' vVertexNormal = normalize(normalMatrix * normal);', //将法线转换到视图坐标系中
|
||||
' vVertexWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;', //将顶点转换到世界坐标系中
|
||||
' // set gl_Position',
|
||||
' gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);',
|
||||
'}'
|
||||
].join('\n')
|
||||
|
||||
//大气层效果
|
||||
const AeroSphere = {
|
||||
uniforms: {
|
||||
coeficient: {
|
||||
type: 'f',
|
||||
value: 1.0
|
||||
},
|
||||
power: {
|
||||
type: 'f',
|
||||
value: 3
|
||||
},
|
||||
glowColor: {
|
||||
type: 'c',
|
||||
value: new Color(0x4390d1)
|
||||
}
|
||||
},
|
||||
vertexShader: vertexShader,
|
||||
fragmentShader: [
|
||||
'uniform vec3 glowColor;',
|
||||
'uniform float coeficient;',
|
||||
'uniform float power;',
|
||||
|
||||
'varying vec3 vVertexNormal;',
|
||||
'varying vec3 vVertexWorldPosition;',
|
||||
|
||||
'varying vec4 vFragColor;',
|
||||
|
||||
'void main(){',
|
||||
' vec3 worldCameraToVertex = vVertexWorldPosition - cameraPosition;', //世界坐标系中从相机位置到顶点位置的距离
|
||||
' vec3 viewCameraToVertex = (viewMatrix * vec4(worldCameraToVertex, 0.0)).xyz;', //视图坐标系中从相机位置到顶点位置的距离
|
||||
' viewCameraToVertex= normalize(viewCameraToVertex);', //规一化
|
||||
' float intensity = pow(coeficient + dot(vVertexNormal, viewCameraToVertex), power);',
|
||||
' gl_FragColor = vec4(glowColor, intensity);',
|
||||
'}'
|
||||
].join('\n')
|
||||
}
|
||||
//球体 辉光 大气层
|
||||
const material1 = new ShaderMaterial({
|
||||
uniforms: AeroSphere.uniforms,
|
||||
vertexShader: AeroSphere.vertexShader,
|
||||
fragmentShader: AeroSphere.fragmentShader,
|
||||
blending: NormalBlending,
|
||||
transparent: true,
|
||||
depthWrite: false
|
||||
})
|
||||
const sphere = new SphereGeometry(this.options.earth.radius + 0, 50, 50)
|
||||
const mesh = new Mesh(sphere, material1)
|
||||
this.earthGroup.add(mesh)
|
||||
}
|
||||
|
||||
async createMarkupPoint() {
|
||||
await Promise.all(
|
||||
this.options.data.map(async item => {
|
||||
const radius = this.options.earth.radius
|
||||
const lon = item.startArray.E //经度
|
||||
const lat = item.startArray.N //纬度
|
||||
|
||||
this.punctuationMaterial = new MeshBasicMaterial({
|
||||
color: this.options.punctuation.circleColor,
|
||||
map: this.options.textures.label,
|
||||
transparent: true, //使用背景透明的png贴图,注意开启透明计算
|
||||
depthWrite: false //禁止写入深度缓冲区数据
|
||||
})
|
||||
|
||||
const mesh = createPointMesh({ radius, lon, lat, material: this.punctuationMaterial }) //光柱底座矩形平面
|
||||
this.markupPoint.add(mesh)
|
||||
const LightPillar = createLightPillar({
|
||||
radius: this.options.earth.radius,
|
||||
lon,
|
||||
lat,
|
||||
index: 0,
|
||||
textures: this.options.textures,
|
||||
punctuation: this.options.punctuation
|
||||
}) //光柱
|
||||
this.markupPoint.add(LightPillar)
|
||||
const WaveMesh = createWaveMesh({ radius, lon, lat, textures: this.options.textures }) //波动光圈
|
||||
this.markupPoint.add(WaveMesh)
|
||||
this.waveMeshArr.push(WaveMesh)
|
||||
|
||||
await Promise.all(
|
||||
item.endArray.map(obj => {
|
||||
const lon = obj.E //经度
|
||||
const lat = obj.N //纬度
|
||||
const mesh = createPointMesh({ radius, lon, lat, material: this.punctuationMaterial }) //光柱底座矩形平面
|
||||
this.markupPoint.add(mesh)
|
||||
const LightPillar = createLightPillar({
|
||||
radius: this.options.earth.radius,
|
||||
lon,
|
||||
lat,
|
||||
index: 1,
|
||||
textures: this.options.textures,
|
||||
punctuation: this.options.punctuation
|
||||
}) //光柱
|
||||
this.markupPoint.add(LightPillar)
|
||||
const WaveMesh = createWaveMesh({ radius, lon, lat, textures: this.options.textures }) //波动光圈
|
||||
this.markupPoint.add(WaveMesh)
|
||||
this.waveMeshArr.push(WaveMesh)
|
||||
})
|
||||
)
|
||||
this.earthGroup.add(this.markupPoint)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
createAnimateCircle() {
|
||||
// 创建 圆环 点
|
||||
const list = getCirclePoints({
|
||||
radius: this.options.earth.radius + 15,
|
||||
number: 150, //切割数
|
||||
closed: true // 闭合
|
||||
})
|
||||
const mat = new MeshBasicMaterial({
|
||||
color: '#0c3172',
|
||||
transparent: true,
|
||||
opacity: 0.4,
|
||||
side: DoubleSide
|
||||
})
|
||||
const line = createAnimateLine({
|
||||
pointList: list,
|
||||
material: mat,
|
||||
number: 100,
|
||||
radius: 0.1
|
||||
})
|
||||
this.earthGroup.add(line)
|
||||
|
||||
// 在clone两条线出来
|
||||
const l2 = line.clone()
|
||||
l2.scale.set(1.2, 1.2, 1.2)
|
||||
l2.rotateZ(Math.PI / 6)
|
||||
this.earthGroup.add(l2)
|
||||
|
||||
const l3 = line.clone()
|
||||
l3.scale.set(0.8, 0.8, 0.8)
|
||||
l3.rotateZ(-Math.PI / 6)
|
||||
this.earthGroup.add(l3)
|
||||
|
||||
/**
|
||||
* 旋转的球
|
||||
*/
|
||||
const ball = new Mesh(
|
||||
new SphereGeometry(this.options.satellite.size, 32, 32),
|
||||
new MeshBasicMaterial({
|
||||
color: '#e0b187' // 745F4D
|
||||
})
|
||||
)
|
||||
|
||||
const ball2 = new Mesh(
|
||||
new SphereGeometry(this.options.satellite.size, 32, 32),
|
||||
new MeshBasicMaterial({
|
||||
color: '#628fbb' // 324A62
|
||||
})
|
||||
)
|
||||
|
||||
const ball3 = new Mesh(
|
||||
new SphereGeometry(this.options.satellite.size, 32, 32),
|
||||
new MeshBasicMaterial({
|
||||
color: '#806bdf' //6D5AC4
|
||||
})
|
||||
)
|
||||
|
||||
this.circleLineList.push(line, l2, l3)
|
||||
ball.name = ball2.name = ball3.name = '卫星'
|
||||
|
||||
for (let i = 0; i < this.options.satellite.number; i++) {
|
||||
const ball01 = ball.clone()
|
||||
// 一根线上总共有几个球,根据数量平均分布一下
|
||||
const num = Math.floor(list.length / this.options.satellite.number)
|
||||
ball01.position.set(list[num * (i + 1)][0] * 1, list[num * (i + 1)][1] * 1, list[num * (i + 1)][2] * 1)
|
||||
line.add(ball01)
|
||||
|
||||
const ball02 = ball2.clone()
|
||||
const num02 = Math.floor(list.length / this.options.satellite.number)
|
||||
ball02.position.set(list[num02 * (i + 1)][0] * 1, list[num02 * (i + 1)][1] * 1, list[num02 * (i + 1)][2] * 1)
|
||||
l2.add(ball02)
|
||||
|
||||
const ball03 = ball2.clone()
|
||||
const num03 = Math.floor(list.length / this.options.satellite.number)
|
||||
ball03.position.set(list[num03 * (i + 1)][0] * 1, list[num03 * (i + 1)][1] * 1, list[num03 * (i + 1)][2] * 1)
|
||||
l3.add(ball03)
|
||||
}
|
||||
}
|
||||
|
||||
createFlyLine() {
|
||||
this.flyLineArcGroup = new Group()
|
||||
this.flyLineArcGroup.userData['flyLineArray'] = []
|
||||
this.earthGroup.add(this.flyLineArcGroup)
|
||||
this.options.data.forEach(cities => {
|
||||
cities.endArray.forEach(item => {
|
||||
// 调用函数flyArc绘制球面上任意两点之间飞线圆弧轨迹
|
||||
const arcline = flyArc(
|
||||
this.options.earth.radius,
|
||||
cities.startArray.E,
|
||||
cities.startArray.N,
|
||||
item.E,
|
||||
item.N,
|
||||
this.options.flyLine
|
||||
)
|
||||
|
||||
this.flyLineArcGroup.add(arcline) // 飞线插入flyArcGroup中
|
||||
this.flyLineArcGroup.userData['flyLineArray'].push(arcline.userData['flyLine'])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
show() {
|
||||
gsap.to(this.group.scale, {
|
||||
x: 1,
|
||||
y: 1,
|
||||
z: 1,
|
||||
duration: 2,
|
||||
ease: 'Quadratic'
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
this.flyLineArcGroup?.userData['flyLineArray']?.forEach((fly: any) => {
|
||||
fly.rotation.z += this.options.flyLine.speed // 调节飞线速度
|
||||
if (fly.rotation.z >= fly.flyEndAngle) fly.rotation.z = 0
|
||||
})
|
||||
|
||||
if (this.isRotation) {
|
||||
this.earthGroup.rotation.y += this.options.earth.rotateSpeed
|
||||
}
|
||||
|
||||
this.circleLineList.forEach(e => {
|
||||
e.rotateY(this.options.satellite.rotateSpeed)
|
||||
})
|
||||
|
||||
this.uniforms.time.value =
|
||||
this.uniforms.time.value < -this.timeValue ? this.timeValue : this.uniforms.time.value - 1
|
||||
|
||||
if (this.waveMeshArr.length) {
|
||||
this.waveMeshArr.forEach((mesh: any) => {
|
||||
mesh.userData['scale'] += 0.007
|
||||
mesh.scale.set(
|
||||
mesh.userData['size'] * mesh.userData['scale'],
|
||||
mesh.userData['size'] * mesh.userData['scale'],
|
||||
mesh.userData['size'] * mesh.userData['scale']
|
||||
)
|
||||
if (mesh.userData['scale'] <= 1.5) {
|
||||
(mesh.material as Material).opacity = (mesh.userData['scale'] - 1) * 2 //2等于1/(1.5-1.0),保证透明度在0~1之间变化
|
||||
} else if (mesh.userData['scale'] > 1.5 && mesh.userData['scale'] <= 2) {
|
||||
(mesh.material as Material).opacity = 1 - (mesh.userData['scale'] - 1.5) * 2 //2等于1/(2.0-1.5) mesh缩放2倍对应0 缩放1.5被对应1
|
||||
} else {
|
||||
mesh.userData['scale'] = 1
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 资源管理和加载
|
||||
*/
|
||||
import { LoadingManager, Texture, TextureLoader } from 'three'
|
||||
import { loadingStart, loadingFinish, loadingError } from '@/utils'
|
||||
import { resources } from './Assets'
|
||||
export class Resources {
|
||||
private manager!: LoadingManager
|
||||
private callback: () => void
|
||||
private textureLoader!: InstanceType<typeof TextureLoader>
|
||||
public textures: Record<string, Texture>
|
||||
constructor(callback: () => void) {
|
||||
this.callback = callback // 资源加载完成的回调
|
||||
this.textures = {} // 贴图对象
|
||||
this.setLoadingManager()
|
||||
this.loadResources()
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理加载状态
|
||||
*/
|
||||
private setLoadingManager() {
|
||||
this.manager = new LoadingManager()
|
||||
// 开始加载
|
||||
this.manager.onStart = () => {
|
||||
loadingStart()
|
||||
}
|
||||
// 加载完成
|
||||
this.manager.onLoad = () => {
|
||||
this.callback()
|
||||
}
|
||||
// 正在进行中
|
||||
this.manager.onProgress = url => {
|
||||
loadingFinish()
|
||||
}
|
||||
|
||||
this.manager.onError = url => {
|
||||
loadingError()
|
||||
window['$message'].error('数据加载失败,请刷新重试!')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载资源
|
||||
*/
|
||||
private loadResources(): void {
|
||||
this.textureLoader = new TextureLoader(this.manager)
|
||||
resources.textures?.forEach(item => {
|
||||
this.textureLoader.load(item.url, t => {
|
||||
this.textures[item.name] = t
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { MeshBasicMaterial, PerspectiveCamera, Scene, ShaderMaterial, WebGLRenderer } from 'three'
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||
// interfaces
|
||||
import { IWord } from '../interfaces/IWord'
|
||||
import { Basic } from './Basic'
|
||||
import { Resources } from './Resources'
|
||||
// earth
|
||||
import Earth from './Earth'
|
||||
|
||||
export default class World {
|
||||
public basic: Basic
|
||||
public scene: Scene
|
||||
public camera: PerspectiveCamera
|
||||
public renderer: WebGLRenderer
|
||||
public controls: OrbitControls
|
||||
public material!: ShaderMaterial | MeshBasicMaterial
|
||||
public resources: Resources
|
||||
public option: IWord
|
||||
public earth!: Earth
|
||||
|
||||
constructor(option: IWord) {
|
||||
/**
|
||||
* 加载资源
|
||||
*/
|
||||
this.option = option
|
||||
this.basic = new Basic(option.dom)
|
||||
this.scene = this.basic.scene
|
||||
this.renderer = this.basic.renderer
|
||||
this.controls = this.basic.controls
|
||||
this.camera = this.basic.camera
|
||||
this.updateSize()
|
||||
this.resources = new Resources(async () => {
|
||||
await this.createEarth()
|
||||
// 开始渲染
|
||||
this.render()
|
||||
})
|
||||
}
|
||||
|
||||
async createEarth(data?: any) {
|
||||
// 资源加载完成,开始制作地球,注释在new Earth()类型里面
|
||||
this.earth = new Earth({
|
||||
data: data || this.option.data,
|
||||
dom: this.option.dom,
|
||||
textures: this.resources.textures,
|
||||
earth: {
|
||||
radius: 50,
|
||||
rotateSpeed: 0.002,
|
||||
isRotation: true
|
||||
},
|
||||
satellite: {
|
||||
show: true,
|
||||
rotateSpeed: -0.01,
|
||||
size: 1,
|
||||
number: 2
|
||||
},
|
||||
punctuation: {
|
||||
circleColor: 0x3892ff,
|
||||
lightColumn: {
|
||||
startColor: 0xe4007f, // 起点颜色
|
||||
endColor: 0xffffff // 终点颜色
|
||||
}
|
||||
},
|
||||
flyLine: {
|
||||
color: 0xf3ae76, // 飞线的颜色
|
||||
flyLineColor: 0xff7714, // 飞行线的颜色
|
||||
speed: 0.004 // 拖尾飞线的速度
|
||||
}
|
||||
})
|
||||
|
||||
this.scene.add(this.earth.group)
|
||||
await this.earth.init()
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染函数
|
||||
*/
|
||||
public render() {
|
||||
requestAnimationFrame(this.render.bind(this))
|
||||
this.renderer.render(this.scene, this.camera)
|
||||
this.controls && this.controls.update()
|
||||
this.earth && this.earth.render()
|
||||
}
|
||||
|
||||
// 更新
|
||||
public updateSize(width?: number, height?: number) {
|
||||
let w = width || this.option.width
|
||||
let h = height || this.option.height
|
||||
// 取小值
|
||||
if (w < h) h = w
|
||||
else w = h
|
||||
|
||||
this.renderer.setSize(w, h)
|
||||
this.camera.aspect = w / h
|
||||
this.camera.updateProjectionMatrix()
|
||||
}
|
||||
|
||||
// 数据更新重新渲染
|
||||
public updateData(data?: any) {
|
||||
if (!this.earth.group) return
|
||||
// 先删除旧的
|
||||
this.scene.remove(this.earth.group)
|
||||
// 递归遍历组对象group释放所有后代网格模型绑定几何体占用内存
|
||||
this.earth.group.traverse((obj: any) => {
|
||||
if (obj.type === 'Mesh') {
|
||||
obj.geometry.dispose()
|
||||
obj.material.dispose()
|
||||
}
|
||||
})
|
||||
// 重新创建
|
||||
this.createEarth(data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { PublicConfigClass } from '@/packages/public'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import { chartInitConfig } from '@/settings/designSetting'
|
||||
import { ThreeEarth01Config } from './index'
|
||||
import dataJson from './data.json'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
export const option = {
|
||||
dataset: dataJson
|
||||
}
|
||||
|
||||
export default class Config extends PublicConfigClass implements CreateComponentType {
|
||||
public key = ThreeEarth01Config.key
|
||||
public attr = { ...chartInitConfig, w: 800, h: 800, zIndex: -1 }
|
||||
public chartConfig = cloneDeep(ThreeEarth01Config)
|
||||
public option = cloneDeep(option)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<template></template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
|
||||
import { option } from './config'
|
||||
|
||||
const props = defineProps({
|
||||
optionData: {
|
||||
type: Object as PropType<typeof option>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,84 @@
|
||||
[
|
||||
{
|
||||
"startArray": {
|
||||
"name": "杭州",
|
||||
"N": 30.246026,
|
||||
"E": 120.210792
|
||||
},
|
||||
"endArray": [
|
||||
{
|
||||
"name": "曼谷",
|
||||
"N": 22,
|
||||
"E": 100.49074172973633
|
||||
},
|
||||
{
|
||||
"name": "澳大利亚",
|
||||
"N": -23.68477416688374,
|
||||
"E": 133.857421875
|
||||
},
|
||||
|
||||
{
|
||||
"name": "新疆维吾尔自治区",
|
||||
"N": 41.748,
|
||||
"E": 84.9023
|
||||
},
|
||||
|
||||
{
|
||||
"name": "德黑兰",
|
||||
"N": 35,
|
||||
"E": 51
|
||||
},
|
||||
{
|
||||
"name": "德黑兰",
|
||||
"N": 35,
|
||||
"E": 51
|
||||
},
|
||||
{
|
||||
"name": "美国",
|
||||
"N": 34.125447565116126,
|
||||
"E": 241.7431640625
|
||||
},
|
||||
{
|
||||
"name": "英国",
|
||||
"N": 51.508742458803326,
|
||||
"E": 359.82421875
|
||||
},
|
||||
{
|
||||
"name": "巴西",
|
||||
"N": -9.96885060854611,
|
||||
"E": 668.1445312499999
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"startArray": {
|
||||
"name": "北京",
|
||||
"N": 39.89491,
|
||||
"E": 116.322056
|
||||
},
|
||||
"endArray": [
|
||||
{
|
||||
"name": "西藏",
|
||||
"N": 29.660361,
|
||||
"E": 91.132212
|
||||
},
|
||||
{
|
||||
"name": "广西",
|
||||
"N": 22.830824,
|
||||
"E": 108.30616
|
||||
},
|
||||
|
||||
{
|
||||
"name": "江西",
|
||||
"N": 28.676493,
|
||||
"E": 115.892151
|
||||
},
|
||||
|
||||
{
|
||||
"name": "贵阳",
|
||||
"N": 26.647661,
|
||||
"E": 106.630153
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 566 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 504 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,15 @@
|
||||
import image from '@/assets/images/chart/decorates/threeEarth01.png'
|
||||
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
|
||||
import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
|
||||
|
||||
export const ThreeEarth01Config: ConfigType = {
|
||||
key: 'ThreeEarth01',
|
||||
chartKey: 'VThreeEarth01',
|
||||
conKey: 'VCThreeEarth01',
|
||||
title: '三维地球',
|
||||
category: ChatCategoryEnum.THREE,
|
||||
categoryName: ChatCategoryEnumName.THREE,
|
||||
package: PackagesCategoryEnum.DECORATES,
|
||||
chartFrame: ChartFrameEnum.STATIC,
|
||||
image
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div ref="chartRef"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, PropType, ref, toRefs, watch } from 'vue'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import { useChartDataFetch } from '@/hooks'
|
||||
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
|
||||
import { option } from './config'
|
||||
import World from './code/world/Word'
|
||||
import throttle from 'lodash/throttle'
|
||||
|
||||
const props = defineProps({
|
||||
chartConfig: {
|
||||
type: Object as PropType<CreateComponentType & typeof option>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const chartEditStore = useChartEditStore()
|
||||
|
||||
const chartRef = ref<HTMLElement>()
|
||||
const { w, h } = toRefs(props.chartConfig.attr)
|
||||
let threeClassInstance: World
|
||||
|
||||
// 初始化
|
||||
const init = () => {
|
||||
const dom: HTMLElement | undefined = chartRef.value
|
||||
if (dom) {
|
||||
threeClassInstance = new World({
|
||||
dom: dom,
|
||||
data: props.chartConfig.option.dataset,
|
||||
width: w.value,
|
||||
height: h.value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const updateData = (data: any) => {
|
||||
try {
|
||||
threeClassInstance.updateData(data)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 改变大小
|
||||
watch(
|
||||
() => [w.value, h.value],
|
||||
throttle(([newWidth], [newHeight]) => {
|
||||
threeClassInstance.updateSize(newWidth, newHeight)
|
||||
}, 100)
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
(newData: any) => {
|
||||
updateData(newData)
|
||||
},
|
||||
{
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
// DOM 渲染之后进行初始化
|
||||
onMounted(() => {
|
||||
try {
|
||||
if (navigator.userAgent.indexOf('Chrome') < -1 || navigator.userAgent.indexOf('Edg') > -1) {
|
||||
window['$message'].error('此组件仅在【谷歌】浏览器上能正常展示!')
|
||||
chartEditStore.removeComponentList(undefined, false)
|
||||
return
|
||||
}
|
||||
init()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
})
|
||||
|
||||
useChartDataFetch(props.chartConfig, useChartEditStore, updateData)
|
||||
</script>
|
||||
3
src/packages/components/Decorates/Three/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { ThreeEarth01Config } from './ThreeEarth01/index'
|
||||
|
||||
export default [ThreeEarth01Config]
|
||||
2
src/packages/components/Decorates/index.d.ts
vendored
@@ -1,11 +1,13 @@
|
||||
export enum ChatCategoryEnum {
|
||||
BORDER = 'Borders',
|
||||
DECORATE = 'Decorates',
|
||||
THREE = 'Three',
|
||||
MORE = 'Mores'
|
||||
}
|
||||
|
||||
export enum ChatCategoryEnumName {
|
||||
BORDER = '边框',
|
||||
DECORATE = '装饰',
|
||||
THREE = '三维',
|
||||
MORE = '更多'
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import Borders from './Borders'
|
||||
import Decorates from './Decorates'
|
||||
import Three from './Three'
|
||||
import Mores from './Mores'
|
||||
|
||||
export const DecorateList = [...Borders, ...Decorates, ...Mores]
|
||||
export const DecorateList = [...Borders, ...Decorates, ...Three, ...Mores]
|
||||
|
||||
@@ -47,9 +47,12 @@ const option = computed(() => {
|
||||
})
|
||||
|
||||
const dataSetHandle = (dataset: typeof dataJson) => {
|
||||
dataset && (props.chartConfig.option.series[0].data = dataset)
|
||||
|
||||
vChartRef.value && isPreview() && vChartRef.value.setOption(props.chartConfig.option)
|
||||
try {
|
||||
dataset && (props.chartConfig.option.series[0].data = dataset)
|
||||
vChartRef.value && isPreview() && vChartRef.value.setOption(props.chartConfig.option)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// dataset 无法变更条数的补丁
|
||||
|
||||
@@ -10,19 +10,12 @@
|
||||
<div class="rank" :style="`color: ${color};font-size: ${indexFontSize}px`">No.{{ item.ranking }}</div>
|
||||
<div class="info-name" :style="`font-size: ${leftFontSize}px`" v-html="item.name" />
|
||||
<div class="ranking-value" :style="`color: ${textColor};font-size: ${rightFontSize}px`">
|
||||
{{
|
||||
status.mergedConfig.valueFormatter
|
||||
? status.mergedConfig.valueFormatter(item)
|
||||
: item.value
|
||||
}}
|
||||
{{ status.mergedConfig.valueFormatter ? status.mergedConfig.valueFormatter(item) : item.value }}
|
||||
{{ unit }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ranking-column" :style="`border-color: ${borderColor}`">
|
||||
<div
|
||||
class="inside-column"
|
||||
:style="`width: ${item.percent}%;background-color: ${color}`"
|
||||
>
|
||||
<div class="inside-column" :style="`width: ${item.percent}%;background-color: ${color}`">
|
||||
<div class="shine" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,8 +32,8 @@ import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore
|
||||
const props = defineProps({
|
||||
chartConfig: {
|
||||
type: Object as PropType<CreateComponentType>,
|
||||
required: true,
|
||||
},
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const { w, h } = toRefs(props.chartConfig.attr)
|
||||
const { rowNum, unit, color, textColor, borderColor, indexFontSize, leftFontSize, rightFontSize } = toRefs(
|
||||
@@ -50,13 +43,15 @@ const { rowNum, unit, color, textColor, borderColor, indexFontSize, leftFontSize
|
||||
const status = reactive({
|
||||
mergedConfig: props.chartConfig.option,
|
||||
rowsData: [],
|
||||
rows: [{
|
||||
scroll: 0,
|
||||
ranking: 1,
|
||||
name: '',
|
||||
value: '',
|
||||
percent: 0
|
||||
}],
|
||||
rows: [
|
||||
{
|
||||
scroll: 0,
|
||||
ranking: 1,
|
||||
name: '',
|
||||
value: '',
|
||||
percent: 0
|
||||
}
|
||||
],
|
||||
heights: [0],
|
||||
animationIndex: 0,
|
||||
animationHandler: 0,
|
||||
@@ -81,16 +76,16 @@ const calcRowsData = () => {
|
||||
// abs of max
|
||||
const maxAbs = Math.abs(max)
|
||||
const total = max + minAbs
|
||||
dataset = dataset.map((row: any, i:number) => ({
|
||||
dataset = dataset.map((row: any, i: number) => ({
|
||||
...row,
|
||||
ranking: i + 1,
|
||||
percent: ((row.value + minAbs) / total) * 100,
|
||||
percent: ((row.value + minAbs) / total) * 100
|
||||
}))
|
||||
const rowLength = dataset.length
|
||||
if (rowLength > rowNum && rowLength < 2 * rowNum) {
|
||||
dataset = [...dataset, ...dataset]
|
||||
}
|
||||
dataset = dataset.map((d:any, i:number) => ({ ...d, scroll: i }))
|
||||
dataset = dataset.map((d: any, i: number) => ({ ...d, scroll: i }))
|
||||
status.rowsData = dataset
|
||||
status.rows = dataset
|
||||
}
|
||||
@@ -134,11 +129,15 @@ const stopAnimation = () => {
|
||||
}
|
||||
|
||||
const onRestart = async () => {
|
||||
if (!status.mergedConfig) return
|
||||
stopAnimation()
|
||||
calcRowsData()
|
||||
calcHeights(true)
|
||||
animation(true)
|
||||
try {
|
||||
if (!status.mergedConfig) return
|
||||
stopAnimation()
|
||||
calcRowsData()
|
||||
calcHeights(true)
|
||||
animation(true)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
onRestart()
|
||||
|
||||
@@ -1,24 +1,47 @@
|
||||
<template>
|
||||
<div class="dv-scroll-board">
|
||||
<div class="header" v-if="status.header.length && status.mergedConfig"
|
||||
:style="`background-color: ${status.mergedConfig.headerBGC};`">
|
||||
<div class="header-item" v-for="(headerItem, i) in status.header" :key="`${headerItem}${i}`" :style="`
|
||||
<div
|
||||
class="header"
|
||||
v-if="status.header.length && status.mergedConfig"
|
||||
:style="`background-color: ${status.mergedConfig.headerBGC};`"
|
||||
>
|
||||
<div
|
||||
class="header-item"
|
||||
v-for="(headerItem, i) in status.header"
|
||||
:key="`${headerItem}${i}`"
|
||||
:style="`
|
||||
height: ${status.mergedConfig.headerHeight}px;
|
||||
line-height: ${status.mergedConfig.headerHeight}px;
|
||||
width: ${status.widths[i]}px;
|
||||
`" :align="status.aligns[i]" v-html="headerItem" />
|
||||
`"
|
||||
:align="status.aligns[i]"
|
||||
v-html="headerItem"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="status.mergedConfig" class="rows"
|
||||
:style="`height: ${h - (status.header.length ? status.mergedConfig.headerHeight : 0)}px;`">
|
||||
<div class="row-item" v-for="(row, ri) in status.rows" :key="`${row.toString()}${row.scroll}`" :style="`
|
||||
<div
|
||||
v-if="status.mergedConfig"
|
||||
class="rows"
|
||||
:style="`height: ${h - (status.header.length ? status.mergedConfig.headerHeight : 0)}px;`"
|
||||
>
|
||||
<div
|
||||
class="row-item"
|
||||
v-for="(row, ri) in status.rows"
|
||||
:key="`${row.toString()}${row.scroll}`"
|
||||
:style="`
|
||||
height: ${status.heights[ri]}px;
|
||||
line-height: ${status.heights[ri]}px;
|
||||
background-color: ${status.mergedConfig[row.rowIndex % 2 === 0 ? 'evenRowBGC' : 'oddRowBGC']};
|
||||
`">
|
||||
<div class="ceil" v-for="(ceil, ci) in row.ceils" :key="`${ceil}${ri}${ci}`"
|
||||
:style="`width: ${status.widths[ci]}px;`" :align="status.aligns[ci]" v-html="ceil" />
|
||||
|
||||
`"
|
||||
>
|
||||
<div
|
||||
class="ceil"
|
||||
v-for="(ceil, ci) in row.ceils"
|
||||
:key="`${ceil}${ri}${ci}`"
|
||||
:style="`width: ${status.widths[ci]}px;`"
|
||||
:align="status.aligns[ci]"
|
||||
v-html="ceil"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -35,8 +58,8 @@ import cloneDeep from 'lodash/cloneDeep'
|
||||
const props = defineProps({
|
||||
chartConfig: {
|
||||
type: Object as PropType<CreateComponentType>,
|
||||
required: true,
|
||||
},
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
// 这里能拿到图表宽高等
|
||||
@@ -138,11 +161,13 @@ const status = reactive({
|
||||
mergedConfig: props.chartConfig.option,
|
||||
header: [],
|
||||
rowsData: [],
|
||||
rows: [{
|
||||
ceils: [],
|
||||
rowIndex: 0,
|
||||
scroll: 0
|
||||
}],
|
||||
rows: [
|
||||
{
|
||||
ceils: [],
|
||||
rowIndex: 0,
|
||||
scroll: 0
|
||||
}
|
||||
],
|
||||
widths: [],
|
||||
heights: [0],
|
||||
avgHeight: 0,
|
||||
@@ -163,7 +188,7 @@ const calcData = () => {
|
||||
animation(true)
|
||||
}
|
||||
|
||||
onMounted(()=> {
|
||||
onMounted(() => {
|
||||
calcData()
|
||||
})
|
||||
|
||||
@@ -185,19 +210,21 @@ const calcHeaderData = () => {
|
||||
const calcRowsData = () => {
|
||||
let { dataset, index, headerBGC, rowNum } = status.mergedConfig
|
||||
if (index) {
|
||||
dataset = dataset.map((row:any, i:number) => {
|
||||
dataset = dataset.map((row: any, i: number) => {
|
||||
row = [...row]
|
||||
const indexTag = `<span class="index" style="background-color: ${headerBGC};border-radius: 3px;padding: 0px 3px;">${i + 1}</span>`
|
||||
const indexTag = `<span class="index" style="background-color: ${headerBGC};border-radius: 3px;padding: 0px 3px;">${
|
||||
i + 1
|
||||
}</span>`
|
||||
row.unshift(indexTag)
|
||||
return row
|
||||
})
|
||||
}
|
||||
dataset = dataset.map((ceils:any, i:number) => ({ ceils, rowIndex: i }))
|
||||
dataset = dataset.map((ceils: any, i: number) => ({ ceils, rowIndex: i }))
|
||||
const rowLength = dataset.length
|
||||
if (rowLength > rowNum && rowLength < 2 * rowNum) {
|
||||
dataset = [...dataset, ...dataset]
|
||||
}
|
||||
dataset = dataset.map((d:any, i:number) => ({ ...d, scroll: i }))
|
||||
dataset = dataset.map((d: any, i: number) => ({ ...d, scroll: i }))
|
||||
|
||||
status.rowsData = dataset
|
||||
status.rows = dataset
|
||||
@@ -206,7 +233,7 @@ const calcRowsData = () => {
|
||||
const calcWidths = () => {
|
||||
const { mergedConfig, rowsData } = status
|
||||
const { columnWidth, header } = mergedConfig
|
||||
const usedWidth = columnWidth.reduce((all:any, ws:number) => all + ws, 0)
|
||||
const usedWidth = columnWidth.reduce((all: any, ws: number) => all + ws, 0)
|
||||
let columnNum = 0
|
||||
if (rowsData[0]) {
|
||||
columnNum = (rowsData[0] as any).ceils.length
|
||||
@@ -254,7 +281,7 @@ const animation = async (start = false) => {
|
||||
const rowLength = rowsData.length
|
||||
if (rowNum >= rowLength) return
|
||||
if (start) {
|
||||
await new Promise(resolve => setTimeout(resolve, waitTime*1000))
|
||||
await new Promise(resolve => setTimeout(resolve, waitTime * 1000))
|
||||
if (updater !== status.updater) return
|
||||
}
|
||||
const animationNum = carousel === 'single' ? 1 : rowNum
|
||||
@@ -269,7 +296,7 @@ const animation = async (start = false) => {
|
||||
const back = animationIndex - rowLength
|
||||
if (back >= 0) animationIndex = back
|
||||
status.animationIndex = animationIndex
|
||||
status.animationHandler = setTimeout(animation, waitTime*1000 - 300) as any
|
||||
status.animationHandler = setTimeout(animation, waitTime * 1000 - 300) as any
|
||||
}
|
||||
|
||||
const stopAnimation = () => {
|
||||
@@ -279,9 +306,13 @@ const stopAnimation = () => {
|
||||
}
|
||||
|
||||
const onRestart = async () => {
|
||||
if (!status.mergedConfig) return
|
||||
stopAnimation()
|
||||
calcData()
|
||||
try {
|
||||
if (!status.mergedConfig) return
|
||||
stopAnimation()
|
||||
calcData()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
@@ -304,7 +335,7 @@ watch(
|
||||
() => {
|
||||
onRestart()
|
||||
},
|
||||
{deep:true}
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 数据更新 (默认更新 dataset,若更新之后有其它操作,可添加回调函数)
|
||||
@@ -316,7 +347,6 @@ useChartDataFetch(props.chartConfig, useChartEditStore, (resData: any[]) => {
|
||||
onUnmounted(() => {
|
||||
stopAnimation()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
19
src/packages/index.d.ts
vendored
@@ -90,6 +90,21 @@ export const BlendModeEnumList = [
|
||||
{ label: '亮度', value: 'luminosity' }
|
||||
]
|
||||
|
||||
// vue3 生命周期事件
|
||||
export enum EventLife {
|
||||
// 渲染之后
|
||||
MOUNTED = 'vnodeMounted',
|
||||
// 渲染之前
|
||||
BEFORE_MOUNT = 'vnodeBeforeMount',
|
||||
// 鼠标事件
|
||||
MOUSE_CLICK = 'click',
|
||||
MOUSE_OVER = "mouseover",
|
||||
MOUSE_LEAVE = "mouseleave",
|
||||
// 图表事件
|
||||
ECHART_LEGEND_SELECT_CHANGED = "legendselectchanged",
|
||||
ECHART_HIGH_LIGHT = "highlight"
|
||||
}
|
||||
|
||||
// 组件实例类
|
||||
export interface PublicConfigType {
|
||||
id: string
|
||||
@@ -115,7 +130,9 @@ export interface PublicConfigType {
|
||||
}
|
||||
filter?: string
|
||||
status: StatusType
|
||||
setPosition: Function
|
||||
events?: {
|
||||
[K in EventLife]?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface CreateComponentType extends PublicConfigType, requestConfig {
|
||||
|
||||
@@ -81,12 +81,8 @@ export class PublicConfigClass implements PublicConfigType {
|
||||
public request = cloneDeep(requestConfig)
|
||||
// 数据过滤
|
||||
public filter = undefined
|
||||
|
||||
// 设置坐标
|
||||
public setPosition(x: number, y: number): void {
|
||||
this.attr.x = x
|
||||
this.attr.y = y
|
||||
}
|
||||
// 事件
|
||||
public events = undefined
|
||||
}
|
||||
|
||||
// 多选成组类
|
||||
|
||||
@@ -18,11 +18,13 @@ export function createRouterGuards(router: Router) {
|
||||
const isErrorPage = router.getRoutes().findIndex((item) => item.name === to.name);
|
||||
if (isErrorPage === -1) {
|
||||
next({ name: PageEnum.ERROR_PAGE_NAME_404 })
|
||||
return
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (!routerAllowList.includes(to.name) && !loginCheck()) {
|
||||
next({ name: PageEnum.BASE_LOGIN_NAME })
|
||||
return
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"fontSize": 12,
|
||||
"color": "#B9B8CE"
|
||||
"color": "#B9B8CE",
|
||||
"rotate": 0
|
||||
},
|
||||
"position": "bottom",
|
||||
"axisLine": {
|
||||
@@ -58,7 +59,8 @@
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"fontSize": 12,
|
||||
"color": "#B9B8CE"
|
||||
"color": "#B9B8CE",
|
||||
"rotate": 0
|
||||
},
|
||||
"position": "left",
|
||||
"axisLine": {
|
||||
@@ -88,5 +90,12 @@
|
||||
"textStyle": {
|
||||
"color": "#B9B8CE"
|
||||
}
|
||||
},
|
||||
"grid": {
|
||||
"show": false,
|
||||
"left": "10%",
|
||||
"top": "60",
|
||||
"right": "10%",
|
||||
"bottom": "60"
|
||||
}
|
||||
}
|
||||
@@ -518,34 +518,31 @@ export const useChartEditStore = defineStore({
|
||||
}
|
||||
const parseHandle = (e: CreateComponentType | CreateComponentGroupType) => {
|
||||
e = cloneDeep(e)
|
||||
// 生成新 id
|
||||
e.id = getUUID()
|
||||
e.attr.x = this.getMousePosition.x + 30
|
||||
e.attr.y = this.getMousePosition.y + 30
|
||||
// 外层生成新 id
|
||||
e.id = getUUID()
|
||||
// 分组列表生成新 id
|
||||
if (e.isGroup) {
|
||||
(e as CreateComponentGroupType).groupList.forEach((item: CreateComponentType) => {
|
||||
item.id = getUUID()
|
||||
})
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
const isCut = recordCharts.type === HistoryActionTypeEnum.CUT
|
||||
const targetList = Array.isArray(recordCharts.charts) ? recordCharts.charts : [ recordCharts.charts ]
|
||||
// 多项
|
||||
if (Array.isArray(recordCharts.charts)) {
|
||||
recordCharts.charts.forEach((e: CreateComponentType) => {
|
||||
this.addComponentList(parseHandle(e), undefined, true)
|
||||
// 剪切需删除原数据
|
||||
if (isCut) {
|
||||
this.setTargetSelectChart(e.id)
|
||||
this.removeComponentList(undefined, true)
|
||||
}
|
||||
})
|
||||
if (isCut) this.setRecordChart(undefined)
|
||||
loadingFinish()
|
||||
return
|
||||
}
|
||||
// 单项
|
||||
this.addComponentList(parseHandle(recordCharts.charts), undefined, true)
|
||||
if (isCut) {
|
||||
this.setTargetSelectChart(recordCharts.charts.id)
|
||||
this.removeComponentList()
|
||||
this.setRecordChart(undefined)
|
||||
}
|
||||
targetList.forEach((e: CreateComponentType | CreateComponentGroupType) => {
|
||||
this.addComponentList(parseHandle(e), undefined, true)
|
||||
// 剪切需删除原数据
|
||||
if (isCut) {
|
||||
this.setTargetSelectChart(e.id)
|
||||
this.removeComponentList(undefined, true)
|
||||
}
|
||||
})
|
||||
if (isCut) this.setRecordChart(undefined)
|
||||
loadingFinish()
|
||||
} catch (value) {
|
||||
loadingError()
|
||||
|
||||
@@ -4,10 +4,10 @@ import { asideCollapsedWidth } from '@/settings/designSetting'
|
||||
import { SettingStoreType, ToolsStatusEnum } from './settingStore.d'
|
||||
import { setLocalStorage, getLocalStorage } from '@/utils'
|
||||
import { StorageEnum } from '@/enums/storageEnum'
|
||||
const { GO_SETTING_STORE } = StorageEnum
|
||||
const { GO_SYSTEM_SETTING_STORE } = StorageEnum
|
||||
|
||||
const storageSetting: SettingStoreType = getLocalStorage(
|
||||
GO_SETTING_STORE
|
||||
GO_SYSTEM_SETTING_STORE
|
||||
)
|
||||
|
||||
// 全局设置
|
||||
@@ -48,7 +48,7 @@ export const useSettingStore = defineStore({
|
||||
this.$patch(state => {
|
||||
state[key] = value
|
||||
})
|
||||
setLocalStorage(GO_SETTING_STORE, this.$state)
|
||||
setLocalStorage(GO_SYSTEM_SETTING_STORE, this.$state)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -25,6 +25,23 @@ export const base64toFile = (dataurl: string, fileName: string) => {
|
||||
return ImageUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* file转 blob
|
||||
* @param { File } file 文件对象
|
||||
*/
|
||||
export const fileToBlob = (file:File) =>{
|
||||
return new Promise<Blob>(function (resolve, reject) {
|
||||
let reader = new FileReader()
|
||||
reader.readAsArrayBuffer(file)
|
||||
reader.onload = function (e: ProgressEvent<FileReader>) {
|
||||
if(e.target){
|
||||
const blob = new Blob([<ArrayBuffer>e.target.result], { type: file.type });
|
||||
resolve(blob);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* * url转file
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
/**
|
||||
* * 请求失败统一处理
|
||||
* 请求失败统一处理,allowRoute 允许跳转。
|
||||
* @param MyResponse MyResponseType,可以为空。
|
||||
* @return
|
||||
*/
|
||||
export const httpErrorHandle = () => {
|
||||
import { ResultEnum } from "@/enums/httpEnum"
|
||||
import { PageEnum, ErrorPageNameMap } from "@/enums/pageEnum"
|
||||
import { redirectErrorPage, routerTurnByName } from '@/utils'
|
||||
|
||||
export const httpErrorHandle = (MyResponse?:any, allowRoute:boolean = true) => {
|
||||
if(MyResponse){
|
||||
const {code, msg} = MyResponse
|
||||
if (MyResponse.code === ResultEnum.TOKEN_OVERDUE) {
|
||||
window['$message'].error(msg || window['$t']('http.token_overdue_message'))
|
||||
if(allowRoute) routerTurnByName(PageEnum.BASE_LOGIN_NAME)
|
||||
return
|
||||
}
|
||||
|
||||
if (MyResponse.code != ResultEnum.SUCCESS) {
|
||||
// 其他错误处理 Todo
|
||||
if (ErrorPageNameMap.get(code) && allowRoute) {
|
||||
redirectErrorPage(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
window['$message'].error(window['$t']('http.error_message'))
|
||||
}
|
||||
@@ -4,7 +4,7 @@ export * from '@/utils/router'
|
||||
export * from '@/utils/storage'
|
||||
export * from '@/utils/style'
|
||||
export * from '@/utils/plugin'
|
||||
export * from '@/utils/componets'
|
||||
export * from '@/utils/components'
|
||||
export * from '@/utils/type'
|
||||
export * from '@/utils/file'
|
||||
export * from '@/utils/http'
|
||||