Compare commits
577 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f20c655a23 | ||
|
|
5c3857433e | ||
|
|
1b524f675d | ||
|
|
76859c20c1 | ||
|
|
a172909ddf | ||
|
|
54d695f851 | ||
|
|
0147e7f1e1 | ||
|
|
86886ded16 | ||
|
|
6c44abded9 | ||
|
|
f3fb777924 | ||
|
|
1323bba420 | ||
|
|
e55a1d8713 | ||
|
|
d951a6057d | ||
|
|
9755c59687 | ||
|
|
2918c55fa9 | ||
|
|
f01f5d7837 | ||
|
|
01313b16e1 | ||
|
|
601d037201 | ||
|
|
9bc56f5d17 | ||
|
|
8237cd21ed | ||
|
|
bb3f1f2c10 | ||
|
|
f8463823d9 | ||
|
|
08c46fd66e | ||
|
|
1939d42d09 | ||
|
|
9a0a04ed76 | ||
|
|
813a49bf67 | ||
|
|
77924ff248 | ||
|
|
9dad51fa0b | ||
|
|
8c39a81644 | ||
|
|
7749fac683 | ||
|
|
91f4b2fd9d | ||
|
|
e0a3259765 | ||
|
|
38c1a768fc | ||
|
|
3c71af064c | ||
|
|
7841f54db8 | ||
|
|
936dee9da5 | ||
|
|
a10d5ee43b | ||
|
|
a0b8529606 | ||
|
|
6bf1eb5bde | ||
|
|
fb1b5969f5 | ||
|
|
8eca06fd5f | ||
|
|
3c8642f30f | ||
|
|
3796a3dbde | ||
|
|
637104643c | ||
|
|
9364cfe1cf | ||
|
|
4ae66ee3aa | ||
|
|
a6534518f8 | ||
|
|
e327311477 | ||
|
|
78c766c52e | ||
|
|
3c89ecb2c7 | ||
|
|
948972184e | ||
|
|
8874a3fec7 | ||
|
|
dde28136e1 | ||
|
|
ce03296078 | ||
|
|
457efa1c79 | ||
|
|
cfd1fc275b | ||
|
|
9d56dd122f | ||
|
|
b1e35e6824 | ||
|
|
775fdec0b8 | ||
|
|
f403014ef6 | ||
|
|
5982424864 | ||
|
|
2ad5ccabec | ||
|
|
9ffc3520e7 | ||
|
|
7ba28a9770 | ||
|
|
bec72dfd5f | ||
|
|
cf6b4f5432 | ||
|
|
fe82c44687 | ||
|
|
49e4f15bd1 | ||
|
|
7dd85b2ba6 | ||
|
|
7b64793449 | ||
|
|
5485f2013e | ||
|
|
6ed24ec310 | ||
|
|
e39335864d | ||
|
|
27ec2c276a | ||
|
|
b2f8da500b | ||
|
|
80a21dbf4a | ||
|
|
33bd39859d | ||
|
|
7367a769b1 | ||
|
|
1ca4c67f7e | ||
|
|
120a67159d | ||
|
|
aa808587df | ||
|
|
57df991dd3 | ||
|
|
665184bfa2 | ||
|
|
ceee696443 | ||
|
|
7fda376f19 | ||
|
|
bd60cb3a18 | ||
|
|
534ed6ae6c | ||
|
|
acac93bbd1 | ||
|
|
070e4b8527 | ||
|
|
a4e8761add | ||
|
|
590b76a884 | ||
|
|
98729cfa7b | ||
|
|
2bd077136f | ||
|
|
6fba94594b | ||
|
|
24e9ff4c86 | ||
|
|
379357ee8e | ||
|
|
ef7a64644b | ||
|
|
c163c20c3d | ||
|
|
3ddbeac419 | ||
|
|
ef7a2ec123 | ||
|
|
8dd4cfa6b2 | ||
|
|
7491a6faac | ||
|
|
eaf1a59e89 | ||
|
|
4cbc5040a2 | ||
|
|
b90b129d98 | ||
|
|
64c0ae2734 | ||
|
|
1c2a7a219a | ||
|
|
868b8e7206 | ||
|
|
6115828dfe | ||
|
|
af9b5ab014 | ||
|
|
5497bd97b2 | ||
|
|
c53db63cc7 | ||
|
|
1263603c7b | ||
|
|
d1036c3be7 | ||
|
|
5adbd6e8f1 | ||
|
|
b83ce02849 | ||
|
|
735a9c8a5d | ||
|
|
bf46881e3b | ||
|
|
f68226f4b4 | ||
|
|
07d864e48b | ||
|
|
0326864510 | ||
|
|
2a97060b96 | ||
|
|
6cffd81540 | ||
|
|
eb2b11ed3e | ||
|
|
7cb52bebf5 | ||
|
|
3731f5d32c | ||
|
|
7161b14631 | ||
|
|
2e7ff88be6 | ||
|
|
6368ee5c12 | ||
|
|
42106e907d | ||
|
|
3f71162e9d | ||
|
|
008d8de24e | ||
|
|
e3000dcc7c | ||
|
|
f98cf4cbfc | ||
|
|
807f885f92 | ||
|
|
15ecd8d592 | ||
|
|
588a6f1bf4 | ||
|
|
f633bc81cf | ||
|
|
3006dc6584 | ||
|
|
a7c63e22e5 | ||
|
|
4304158088 | ||
|
|
0f446a1f77 | ||
|
|
71639a91b9 | ||
|
|
c1fb67f143 | ||
|
|
b0395a6ed2 | ||
|
|
7d0e6ac50b | ||
|
|
c6ea524821 | ||
|
|
7c82769448 | ||
|
|
a4b69c3911 | ||
|
|
e301abe9e3 | ||
|
|
d38ccba618 | ||
|
|
849ac56296 | ||
|
|
67bb821a0e | ||
|
|
43c51d48d9 | ||
|
|
48b7f4924e | ||
|
|
30c149be31 | ||
|
|
42d9271ea0 | ||
|
|
00e666e423 | ||
|
|
6b29b686c7 | ||
|
|
db99a21514 | ||
|
|
6fb1c474d2 | ||
|
|
f1c7c35e48 | ||
|
|
923549bb5e | ||
|
|
0aa18ded72 | ||
|
|
ee08d8d740 | ||
|
|
0c23fa7c9d | ||
|
|
fe91765ab0 | ||
|
|
8541e180cc | ||
|
|
a3d950e2a3 | ||
|
|
e1c80636ba | ||
|
|
4fb971a935 | ||
|
|
117d0fbcef | ||
|
|
389cafc240 | ||
|
|
4591b59465 | ||
|
|
d4a082382d | ||
|
|
885437de8d | ||
|
|
39cd9f4a44 | ||
|
|
01a2244fed | ||
|
|
e0e3dd128e | ||
|
|
0f6408d7f6 | ||
|
|
b610dc4969 | ||
|
|
bc6fa7fd27 | ||
|
|
0e34cc49df | ||
|
|
629881d16b | ||
|
|
92569f5b3a | ||
|
|
4ff32511b9 | ||
|
|
5afed4b85e | ||
|
|
2aa687a40b | ||
|
|
57567ebb1b | ||
|
|
6448169caa | ||
|
|
e5901f28e3 | ||
|
|
2d3f0dd95a | ||
|
|
2e4e827887 | ||
|
|
22704b32d6 | ||
|
|
2839e595cc | ||
|
|
a9f28dfb47 | ||
|
|
c1bdd079cf | ||
|
|
467d05a358 | ||
|
|
05c8a6e664 | ||
|
|
59b2ecebda | ||
|
|
fe8cf38a7f | ||
|
|
e754ceace8 | ||
|
|
41ceb9bd82 | ||
|
|
b0b7c63561 | ||
|
|
c13f65611f | ||
|
|
b9feeabfb3 | ||
|
|
8e9ba7a2d2 | ||
|
|
b3e9b1c2be | ||
|
|
e969b5b7e4 | ||
|
|
8e0a684a9a | ||
|
|
152aa0f92b | ||
|
|
97fb0ba95a | ||
|
|
01355b9a28 | ||
|
|
772922cefb | ||
|
|
95a3aa7290 | ||
|
|
6f8a4af5eb | ||
|
|
b332c4e23d | ||
|
|
47c0c3fa3f | ||
|
|
71cb8612d8 | ||
|
|
af12aecd36 | ||
|
|
fec116f131 | ||
|
|
d1fdec0970 | ||
|
|
77b6c53a42 | ||
|
|
f2eaac50a6 | ||
|
|
ec8b0ada14 | ||
|
|
1e28be9671 | ||
|
|
8a0e813734 | ||
|
|
2975d550eb | ||
|
|
248a9c4070 | ||
|
|
327d0d20a9 | ||
|
|
1ffd2812dc | ||
|
|
29da4d209d | ||
|
|
35a369a953 | ||
|
|
4785cae8f0 | ||
|
|
9eaa575d1a | ||
|
|
996ed78a0e | ||
|
|
1759572b2e | ||
|
|
0a9f9eea90 | ||
|
|
223fb540b1 | ||
|
|
c5f1c95f7b | ||
|
|
60086ead7c | ||
|
|
3bbcbca926 | ||
|
|
63c1deb630 | ||
|
|
424e2428fe | ||
|
|
2fdef45156 | ||
|
|
63ac99e906 | ||
|
|
4cd4550a36 | ||
|
|
16af625aef | ||
|
|
8e72794c07 | ||
|
|
d9cf6d7e1b | ||
|
|
57c4f935e2 | ||
|
|
f4e4252227 | ||
|
|
84bbcf7753 | ||
|
|
df129f0d20 | ||
|
|
a76da167eb | ||
|
|
1547b3f53b | ||
|
|
68f5b30f7d | ||
|
|
d9b9c0f42c | ||
|
|
aa0597c27e | ||
|
|
4d59cd1521 | ||
|
|
6ba4170f08 | ||
|
|
827bc97e8e | ||
|
|
7107409b1b | ||
|
|
6c086fab6f | ||
|
|
54d215b8d1 | ||
|
|
4cd47a5c77 | ||
|
|
d027d67e08 | ||
|
|
a7d9e635eb | ||
|
|
e7196efaea | ||
|
|
0a2a903c74 | ||
|
|
8104c26b19 | ||
|
|
420245c630 | ||
|
|
20cf83907d | ||
|
|
0a04d2ee83 | ||
|
|
bc05781edd | ||
|
|
ce9d91e990 | ||
|
|
973d13b705 | ||
|
|
51e4427108 | ||
|
|
d09ab541ab | ||
|
|
a9e1dc3cd5 | ||
|
|
1551e6c640 | ||
|
|
03a1bdfc7f | ||
|
|
24545aa9cb | ||
|
|
c11426419c | ||
|
|
cf7e7c1a06 | ||
|
|
ab388f1ef5 | ||
|
|
101d5c7eb0 | ||
|
|
cad253b85f | ||
|
|
f17009a988 | ||
|
|
1f76278d2b | ||
|
|
e032d29c91 | ||
|
|
31d1b0c994 | ||
|
|
611c6d415c | ||
|
|
e3b7ac00fd | ||
|
|
7c952822db | ||
|
|
b9e435c0e2 | ||
|
|
356d40e640 | ||
|
|
123ffd4e66 | ||
|
|
c952659620 | ||
|
|
ff59f31e84 | ||
|
|
011fc8cc9e | ||
|
|
7428022b2f | ||
|
|
3815eb1983 | ||
|
|
3778f10edb | ||
|
|
00a4b867ea | ||
|
|
3a1024e848 | ||
|
|
ea8e1e9c57 | ||
|
|
478d63893b | ||
|
|
3066631301 | ||
|
|
b0998d5f95 | ||
|
|
a074cb9664 | ||
|
|
43a3d1e5ac | ||
|
|
24c8a4a9d1 | ||
|
|
b5ccba552f | ||
|
|
8d2ee364ba | ||
|
|
14006068c8 | ||
|
|
b4358ffc66 | ||
|
|
33135b1df1 | ||
|
|
9a66c38e01 | ||
|
|
81132ecab0 | ||
|
|
313fc09c8d | ||
|
|
5610486a89 | ||
|
|
9f0231405a | ||
|
|
e650dd0503 | ||
|
|
6fc6cb75e4 | ||
|
|
184efc6f27 | ||
|
|
1117b57101 | ||
|
|
0928ba71f2 | ||
|
|
7170eded68 | ||
|
|
32e891d6ba | ||
|
|
5a273676fb | ||
|
|
c3f6edf7c5 | ||
|
|
e0c0cc8613 | ||
|
|
fbeb3dd81e | ||
|
|
9d89af37be | ||
|
|
fbc8a36232 | ||
|
|
ea028ea1a1 | ||
|
|
35b1c12bb5 | ||
|
|
fc89d96635 | ||
|
|
27129652f2 | ||
|
|
23ef992a7f | ||
|
|
04533e17ec | ||
|
|
7f2fcba542 | ||
|
|
c115e2f985 | ||
|
|
88642c2003 | ||
|
|
2c678b5363 | ||
|
|
bc3b72fafe | ||
|
|
df3b8cf09c | ||
|
|
65393b7809 | ||
|
|
9782c849ad | ||
|
|
e7efaed08a | ||
|
|
337b3e5b5d | ||
|
|
7b1b7d1372 | ||
|
|
e5b838a2b3 | ||
|
|
f9cc2ceb11 | ||
|
|
4f107c5618 | ||
|
|
b1c5aaff43 | ||
|
|
d0a4473e2b | ||
|
|
1c79361094 | ||
|
|
f72114c223 | ||
|
|
dbd59cd958 | ||
|
|
d65f8a3c82 | ||
|
|
96587a4e45 | ||
|
|
76ab47c82e | ||
|
|
af8344c555 | ||
|
|
10ff02b8a0 | ||
|
|
cb6cf1e34b | ||
|
|
953e924aa2 | ||
|
|
2e0c262d32 | ||
|
|
cbf2e6a140 | ||
|
|
49c5a9f621 | ||
|
|
fb8f63f305 | ||
|
|
49ff61ad65 | ||
|
|
695fb60aa4 | ||
|
|
da20fafa39 | ||
|
|
d6a9ecd912 | ||
|
|
f95d721c9c | ||
|
|
69d6417985 | ||
|
|
ab2bbc28c8 | ||
|
|
b0b39429ed | ||
|
|
ff14cbc752 | ||
|
|
dd013aaaa3 | ||
|
|
119f61ef67 | ||
|
|
a99588c766 | ||
|
|
6f35fe9936 | ||
|
|
3112425e43 | ||
|
|
1ab3aefaa5 | ||
|
|
be08732e6b | ||
|
|
97b58b5f9a | ||
|
|
e4855875cf | ||
|
|
7a267cc07b | ||
|
|
f4f351cf9d | ||
|
|
970b811ab9 | ||
|
|
99604dbe35 | ||
|
|
09b1d89718 | ||
|
|
9d1a9f3134 | ||
|
|
ccb889233c | ||
|
|
939f2cbf97 | ||
|
|
580e0cb36a | ||
|
|
4bd8835f23 | ||
|
|
22f32da0c5 | ||
|
|
aa8a094383 | ||
|
|
4a72b7f089 | ||
|
|
a33e4905cf | ||
|
|
40bd2f0742 | ||
|
|
1fd73fe79e | ||
|
|
fd7d3e06f4 | ||
|
|
7a4d27da69 | ||
|
|
afbadf7d81 | ||
|
|
7b65c64431 | ||
|
|
4f2c0e94d9 | ||
|
|
7593d7a3e9 | ||
|
|
35ddc4a472 | ||
|
|
3400d1e803 | ||
|
|
f672428fde | ||
|
|
4171d993d0 | ||
|
|
d22310ea31 | ||
|
|
1521d1e883 | ||
|
|
0412615f6e | ||
|
|
2bd666efe7 | ||
|
|
541e1f760b | ||
|
|
f176bed436 | ||
|
|
403545cd9b | ||
|
|
54e4ed27ae | ||
|
|
e5ddf5616a | ||
|
|
967c4f04d9 | ||
|
|
36d4d445a6 | ||
|
|
5ed0ae2fa9 | ||
|
|
503a719609 | ||
|
|
2dcbba63cb | ||
|
|
5e86bdfda7 | ||
|
|
4f74a0440c | ||
|
|
858709f610 | ||
|
|
6689d48fad | ||
|
|
8581a7c308 | ||
|
|
9878efb198 | ||
|
|
9855c50367 | ||
|
|
04c59041e0 | ||
|
|
7fb1ecc9b0 | ||
|
|
ecd2cdd28e | ||
|
|
4b0ad22f8d | ||
|
|
789558da6c | ||
|
|
3f96117a57 | ||
|
|
282d6b5746 | ||
|
|
86d2bb9e2a | ||
|
|
ab4fbf0437 | ||
|
|
ae527a78ef | ||
|
|
8218868681 | ||
|
|
251f4ca4ac | ||
|
|
5d5c8ced24 | ||
|
|
f0ec9b7826 | ||
|
|
90b8e785a4 | ||
|
|
cb11a71ff5 | ||
|
|
54dd90fc4a | ||
|
|
4bb080bd58 | ||
|
|
5115a048a3 | ||
|
|
259351cdd2 | ||
|
|
972b8f83bc | ||
|
|
9f6b1c1e25 | ||
|
|
6229a103aa | ||
|
|
84116e531b | ||
|
|
2c1b944b7c | ||
|
|
0e66ca148d | ||
|
|
ce70c1ca3a | ||
|
|
56b91a59a2 | ||
|
|
8f1fbcb19e | ||
|
|
a4799a1bc0 | ||
|
|
cc24ff22e9 | ||
|
|
72ca335c4c | ||
|
|
0ef6476e58 | ||
|
|
6ba63d1466 | ||
|
|
f9d28e1b6b | ||
|
|
63534d3eb5 | ||
|
|
2ab2cf01db | ||
|
|
2fc039dd70 | ||
|
|
230d1a1f86 | ||
|
|
f7921bf388 | ||
|
|
feed984ba8 | ||
|
|
5324861f16 | ||
|
|
ae80939d2e | ||
|
|
c117e4b087 | ||
|
|
96580e2284 | ||
|
|
bc3932c8b8 | ||
|
|
f7cd4f34d3 | ||
|
|
3a76f51707 | ||
|
|
041ba8f2ed | ||
|
|
17e5d15b1b | ||
|
|
a6ba59eac0 | ||
|
|
e89ff02b59 | ||
|
|
b28be29dc8 | ||
|
|
9ad85e01de | ||
|
|
d49790ba78 | ||
|
|
598f01de95 | ||
|
|
992e137339 | ||
|
|
124666cca6 | ||
|
|
848359bf7b | ||
|
|
fb24af1900 | ||
|
|
3d6df3cc09 | ||
|
|
ea58ed46f2 | ||
|
|
32817a4275 | ||
|
|
95033cd5b7 | ||
|
|
9999548bc2 | ||
|
|
3d04bd4444 | ||
|
|
21e618cca2 | ||
|
|
02eb386155 | ||
|
|
35bd038802 | ||
|
|
1ff59aee56 | ||
|
|
d64fae1832 | ||
|
|
ae169810d0 | ||
|
|
3c7619098a | ||
|
|
a881bfd63b | ||
|
|
09d3131d46 | ||
|
|
53550b8975 | ||
|
|
569164ed56 | ||
|
|
f61d79d53e | ||
|
|
bba28d6b57 | ||
|
|
469f30044a | ||
|
|
0993d87799 | ||
|
|
09fa33236f | ||
|
|
4e9abd6512 | ||
|
|
2996c0b38e | ||
|
|
36e366abe0 | ||
|
|
f9c6c6c127 | ||
|
|
34772ef2bf | ||
|
|
305af935a7 | ||
|
|
d0438390cc | ||
|
|
d22266a947 | ||
|
|
e34fb25759 | ||
|
|
675955b2e6 | ||
|
|
4516bce0ee | ||
|
|
4853fbcec3 | ||
|
|
72e5f9a83e | ||
|
|
1a1ddc34a2 | ||
|
|
40ebfb676c | ||
|
|
19dd16fcf0 | ||
|
|
f596749645 | ||
|
|
b5e3cc2503 | ||
|
|
2e11fe2b58 | ||
|
|
70f1258bab | ||
|
|
d91fa33330 | ||
|
|
8f0b5dc049 | ||
|
|
778f6367f9 | ||
|
|
6115eea401 | ||
|
|
ff62ef729a | ||
|
|
fa987b4e30 | ||
|
|
b47c494240 | ||
|
|
a31134195b | ||
|
|
5580a4ee3d | ||
|
|
608660a101 | ||
|
|
25cb8015d0 | ||
|
|
e686e8f58c | ||
|
|
14b699b5b1 | ||
|
|
ce0c86c8e5 | ||
|
|
11d1f26724 | ||
|
|
a8c0978f0d | ||
|
|
f643911014 | ||
|
|
0a0227cca4 | ||
|
|
8bacc3b6ba | ||
|
|
d46eec568c | ||
|
|
7dc6d0ffb2 | ||
|
|
6529e45868 | ||
|
|
3ba6ea9c7e | ||
|
|
b6ff6f5453 | ||
|
|
cfa529b742 | ||
|
|
2eabc76c1a | ||
|
|
463e67d64c | ||
|
|
1f96af1024 | ||
|
|
563aa92958 | ||
|
|
320f183b49 | ||
|
|
c8f11578d6 | ||
|
|
f33be18be3 | ||
|
|
513c7dbd85 | ||
|
|
02a9623310 | ||
|
|
58c1d52f52 | ||
|
|
b71c881b43 | ||
|
|
ffc2c7dea3 | ||
|
|
d7652a7a32 |
@@ -1,4 +1,4 @@
|
||||
APP_NAME=Dootask
|
||||
APP_NAME=DooTask
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
|
||||
75
.github/workflows/electron-generic.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
name: Build Generic
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build-mac:
|
||||
runs-on: macos-latest
|
||||
environment: build
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Create changelog text
|
||||
id: changelog
|
||||
uses: loopwerk/tag-changelog@v1
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
exclude_types: other,chore,build
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Build for MacOS
|
||||
env:
|
||||
APPLEID: ${{ secrets.APPLEID }}
|
||||
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
PROVIDER: "generic"
|
||||
RELEASE_BODY: ${{ steps.changelog.outputs.changes }}
|
||||
run: ./cmd electron build-mac
|
||||
|
||||
build-win:
|
||||
runs-on: windows-latest
|
||||
environment: build
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Create changelog text
|
||||
id: changelog
|
||||
uses: loopwerk/tag-changelog@v1
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
exclude_types: other,chore,build
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Build for Windows
|
||||
shell: powershell
|
||||
env:
|
||||
PROVIDER: "generic"
|
||||
RELEASE_BODY: ${{ steps.changelog.outputs.changes }}
|
||||
run: |
|
||||
npm install
|
||||
cd electron
|
||||
npm install
|
||||
cd ../
|
||||
mkdir -p ./electron/public
|
||||
cp ./electron/index.html ./electron/public/index.html
|
||||
npx mix --production -- --env --electron
|
||||
node ./electron/build.js build-win
|
||||
|
||||
85
.github/workflows/electron-main.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Build Main
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Create changelog text
|
||||
id: changelog
|
||||
uses: loopwerk/tag-changelog@v1
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
exclude_types: other,chore,build
|
||||
|
||||
- name: Create release
|
||||
uses: actions/create-release@latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
body: ${{ steps.changelog.outputs.changes }}
|
||||
|
||||
build-mac:
|
||||
runs-on: macos-latest
|
||||
environment: build
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Build for MacOS
|
||||
env:
|
||||
APPLEID: ${{ secrets.APPLEID }}
|
||||
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
GH_TOKEN: ${{ secrets.GH_PAT }}
|
||||
EP_PRE_RELEASE: true
|
||||
run: ./cmd electron build-mac
|
||||
|
||||
build-win:
|
||||
runs-on: windows-latest
|
||||
environment: build
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Build for Windows
|
||||
shell: powershell
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_PAT }}
|
||||
EP_PRE_RELEASE: true
|
||||
run: |
|
||||
npm install
|
||||
cd electron
|
||||
npm install
|
||||
cd ../
|
||||
mkdir -p ./electron/public
|
||||
cp ./electron/index.html ./electron/public/index.html
|
||||
npx mix --production -- --env --electron
|
||||
node ./electron/build.js build-win
|
||||
|
||||
61
.github/workflows/electron.yml
vendored
@@ -1,61 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Create changelog text
|
||||
id: changelog
|
||||
uses: loopwerk/tag-changelog@v1
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
exclude_types: other,chore
|
||||
|
||||
- name: Create release
|
||||
uses: actions/create-release@latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
body: ${{ steps.changelog.outputs.changes }}
|
||||
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: build
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-11]
|
||||
platform: [
|
||||
build-mac,
|
||||
build-mac-arm,
|
||||
build-win
|
||||
]
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_PAT }}
|
||||
EP_PRE_RELEASE: true
|
||||
run: ./cmd electron ${{ matrix.platform }}
|
||||
|
||||
2
.gitignore
vendored
@@ -14,12 +14,12 @@
|
||||
.vscode
|
||||
.vagrant
|
||||
.phpunit.result.cache
|
||||
CHANGELOG.md
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
test.*
|
||||
composer.lock
|
||||
package-lock.json
|
||||
laravels-timer-process.pid
|
||||
.DS_Store
|
||||
|
||||
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "resources/drawio"]
|
||||
path = resources/drawio
|
||||
url = https://github.com/jgraph/drawio.git
|
||||
70
README.md
@@ -5,25 +5,57 @@ English | **[中文文档](./README_CN.md)**
|
||||
- [Screenshot Preview](README_PREVIEW.md)
|
||||
- [Demo site](http://www.dootask.com/)
|
||||
|
||||
## 🙋 Upgrade to Pro version
|
||||
|
||||
**🎉 The Pro version is now available, and you can upgrade to the Pro version for free with the following command, [Functional compare](https://www.dootask.com/pro)。**
|
||||
|
||||
```bash
|
||||
# Upgrade from the regular version to the pro version
|
||||
|
||||
git fetch origin pro:pro
|
||||
git stash save
|
||||
git checkout pro
|
||||
|
||||
./cmd up -d
|
||||
./cmd update
|
||||
```
|
||||
|
||||
**QQ Group**
|
||||
|
||||
Group No.: `546574618`
|
||||
|
||||
## Setup
|
||||
|
||||
- `Docker` & `Docker Compose v2.0+` must be installed
|
||||
- `Docker v20.10+` & `Docker Compose v2.0+` must be installed
|
||||
- System: `Centos/Debian/Ubuntu/macOS`
|
||||
- Hardware suggestion: 2 cores and above 2G memory
|
||||
- Hardware suggestion: 2 cores and above 4G memory
|
||||
|
||||
### Deployment project
|
||||
### Deployment(Pro Edition)
|
||||
|
||||
```bash
|
||||
# 1、Clone the repository
|
||||
|
||||
# Clone projects on github
|
||||
git clone https://github.com/kuaifan/dootask.git
|
||||
git clone -b pro --depth=1 https://github.com/kuaifan/dootask.git
|
||||
# Or you can use gitee
|
||||
git clone https://gitee.com/aipaw/dootask.git
|
||||
git clone -b pro --depth=1 https://gitee.com/aipaw/dootask.git
|
||||
|
||||
# 2、Enter directory
|
||||
cd dootask
|
||||
|
||||
# 3、Installation(Custom port installation: ./cmd install --port 2222)
|
||||
./cmd install
|
||||
```
|
||||
|
||||
### Deployment (Normal Edition)
|
||||
|
||||
```bash
|
||||
# 1、Clone the repository
|
||||
|
||||
# Clone projects on github
|
||||
git clone -b v0.13.0 --depth=1 https://github.com/kuaifan/dootask.git
|
||||
# Or you can use gitee
|
||||
git clone -b v0.13.0 --depth=1 https://gitee.com/aipaw/dootask.git
|
||||
|
||||
# 2、Enter directory
|
||||
cd dootask
|
||||
@@ -86,7 +118,7 @@ proxy_set_header X-Forwarded-Host $http_host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# 2、Enter directory and run command
|
||||
# 2、Running commands in a project
|
||||
./cmd https
|
||||
```
|
||||
|
||||
@@ -95,7 +127,7 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
**Note: Please back up your data before upgrading!**
|
||||
|
||||
```bash
|
||||
# Method 1: enter directory and run command
|
||||
# Method 1: Running commands in a project
|
||||
./cmd update
|
||||
|
||||
# Or method 2: use this method if method 1 fails
|
||||
@@ -104,12 +136,32 @@ git pull
|
||||
./cmd uninstall
|
||||
./cmd install
|
||||
./cmd mysql recovery
|
||||
./cmd artisan migrate
|
||||
```
|
||||
|
||||
If 502 after the upgrade please run `./cmd restart` restart the service.
|
||||
|
||||
## Transfer
|
||||
|
||||
Follow these steps to complete the project migration after the new project is installed:
|
||||
|
||||
1. Backup original database
|
||||
|
||||
```bash
|
||||
# Run command under old project
|
||||
./cmd mysql backup
|
||||
```
|
||||
|
||||
2. Copy `database backup file` and `public/uploads` directory to the new project.
|
||||
|
||||
3. Restore database to new project
|
||||
```bash
|
||||
# Run command under new project
|
||||
./cmd mysql recovery
|
||||
```
|
||||
|
||||
## Uninstall
|
||||
|
||||
```bash
|
||||
# Enter directory and run command
|
||||
# Running commands in a project
|
||||
./cmd uninstall
|
||||
```
|
||||
|
||||
70
README_CN.md
@@ -5,25 +5,57 @@
|
||||
- [截图预览](README_PREVIEW.md)
|
||||
- [演示站点](http://www.dootask.com/)
|
||||
|
||||
## 🙋 升级到Pro版本
|
||||
|
||||
**🎉 Pro版本现已发布,可通过一下命令免费升级至Pro版本,[版本功能比较](https://www.dootask.com/pro)。**
|
||||
|
||||
```bash
|
||||
# 普通版升级至Pro版
|
||||
|
||||
git fetch origin pro:pro
|
||||
git stash save
|
||||
git checkout pro
|
||||
|
||||
./cmd up -d
|
||||
./cmd update
|
||||
```
|
||||
|
||||
**QQ交流群**
|
||||
|
||||
- QQ群号: `546574618`
|
||||
|
||||
## 安装程序
|
||||
|
||||
- 必须安装:`Docker` 和 `Docker Compose v2.0+`
|
||||
- 必须安装:`Docker v20.10+` 和 `Docker Compose v2.0+`
|
||||
- 支持环境:`Centos/Debian/Ubuntu/macOS`
|
||||
- 硬件建议:2核2G以上
|
||||
- 硬件建议:2核4G以上
|
||||
|
||||
### 部署项目
|
||||
### 部署项目(Pro版)
|
||||
|
||||
```bash
|
||||
# 1、克隆项目到您的本地或服务器
|
||||
|
||||
# 通过github克隆项目
|
||||
git clone https://github.com/kuaifan/dootask.git
|
||||
git clone -b pro --depth=1 https://github.com/kuaifan/dootask.git
|
||||
# 或者你也可以使用gitee
|
||||
git clone https://gitee.com/aipaw/dootask.git
|
||||
git clone -b pro --depth=1 https://gitee.com/aipaw/dootask.git
|
||||
|
||||
# 2、进入目录
|
||||
cd dootask
|
||||
|
||||
# 3、一键安装项目(自定义端口安装 ./cmd install --port 2222)
|
||||
./cmd install
|
||||
```
|
||||
|
||||
### 部署项目(普通版)
|
||||
|
||||
```bash
|
||||
# 1、克隆项目到您的本地或服务器
|
||||
|
||||
# 通过github克隆项目
|
||||
git clone -b v0.13.0 --depth=1 https://github.com/kuaifan/dootask.git
|
||||
# 或者你也可以使用gitee
|
||||
git clone -b v0.13.0 --depth=1 https://gitee.com/aipaw/dootask.git
|
||||
|
||||
# 2、进入目录
|
||||
cd dootask
|
||||
@@ -87,7 +119,7 @@ proxy_set_header X-Forwarded-Host $http_host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# 2、进入项目所在目录,运行以下命令
|
||||
# 2、在项目下运行命令
|
||||
./cmd https
|
||||
```
|
||||
|
||||
@@ -96,7 +128,7 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
**注意:在升级之前请备份好你的数据!**
|
||||
|
||||
```bash
|
||||
# 方法1:进入项目所在目录,运行以下命令
|
||||
# 方法1:在项目下运行命令
|
||||
./cmd update
|
||||
|
||||
# (或者)方法2:如果方法1失败请使用此方法
|
||||
@@ -105,12 +137,32 @@ git pull
|
||||
./cmd uninstall
|
||||
./cmd install
|
||||
./cmd mysql recovery
|
||||
./cmd artisan migrate
|
||||
```
|
||||
|
||||
如果升级后出现502请运行 `./cmd restart` 重启服务即可。
|
||||
|
||||
## 迁移项目
|
||||
|
||||
在新项目安装好之后按照以下步骤完成项目迁移:
|
||||
|
||||
1、备份原数据库
|
||||
|
||||
```bash
|
||||
# 在旧的项目下运行命令
|
||||
./cmd mysql backup
|
||||
```
|
||||
|
||||
2、将`数据库备份文件`及`public/uploads`目录拷贝至新项目
|
||||
|
||||
3、还原数据库至新项目
|
||||
```bash
|
||||
# 在新的项目下运行命令
|
||||
./cmd mysql recovery
|
||||
```
|
||||
|
||||
## 卸载项目
|
||||
|
||||
```bash
|
||||
# 进入项目所在目录,运行以下命令
|
||||
# 在项目下运行命令
|
||||
./cmd uninstall
|
||||
```
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\File;
|
||||
use App\Models\ProjectTask;
|
||||
use App\Models\ProjectTaskFile;
|
||||
use App\Models\User;
|
||||
@@ -10,6 +11,7 @@ use App\Models\WebSocketDialogMsg;
|
||||
use App\Models\WebSocketDialogMsgRead;
|
||||
use App\Models\WebSocketDialogUser;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Request;
|
||||
use Response;
|
||||
|
||||
@@ -39,9 +41,10 @@ class DialogController extends AbstractController
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$list = WebSocketDialog::select(['web_socket_dialogs.*'])
|
||||
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
->where('u.userid', $user->userid)
|
||||
->orderByDesc('u.top_at')
|
||||
->orderByDesc('web_socket_dialogs.last_at')
|
||||
->paginate(Base::getPaginate(200, 100));
|
||||
$list->transform(function (WebSocketDialog $item) use ($user) {
|
||||
@@ -71,7 +74,7 @@ class DialogController extends AbstractController
|
||||
//
|
||||
$dialog_id = intval(Request::input('dialog_id'));
|
||||
//
|
||||
$item = WebSocketDialog::select(['web_socket_dialogs.*'])
|
||||
$item = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
->where('web_socket_dialogs.id', $dialog_id)
|
||||
->where('u.userid', $user->userid)
|
||||
@@ -152,6 +155,12 @@ class DialogController extends AbstractController
|
||||
$user->task_dialog_id = $dialog->id;
|
||||
$user->save();
|
||||
}
|
||||
//去掉标记未读
|
||||
$isMarkDialogUser = WebSocketDialogUser::whereDialogId($dialog->id)->whereUserid($user->userid)->whereMarkUnread(1)->first();
|
||||
if ($isMarkDialogUser) {
|
||||
$isMarkDialogUser->mark_unread = 0;
|
||||
$isMarkDialogUser->save();
|
||||
}
|
||||
//
|
||||
$data = $list->toArray();
|
||||
if ($list->currentPage() === 1) {
|
||||
@@ -161,23 +170,35 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/sendtext 05. 未读消息
|
||||
* @api {get} api/dialog/msg/unread 05. 获取未读消息数量
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup dialog
|
||||
* @apiName msg__sendtext
|
||||
* @apiName msg__unread
|
||||
*
|
||||
* @apiParam {Number} [dialog_id] 对话ID,留空获取总未读消息数量
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function msg__unread()
|
||||
{
|
||||
$unread = WebSocketDialogMsgRead::whereUserid(User::userid())->whereReadAt(null)->count();
|
||||
$dialog_id = intval(Request::input('dialog_id'));
|
||||
//
|
||||
$builder = WebSocketDialogMsgRead::whereUserid(User::userid())->whereReadAt(null);
|
||||
if ($dialog_id > 0) {
|
||||
$builder->whereDialogId($dialog_id);
|
||||
}
|
||||
$unread = $builder->count();
|
||||
return Base::retSuccess('success', [
|
||||
'unread' => $unread,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/sendtext 06. 发送消息
|
||||
* @api {post} api/dialog/msg/sendtext 06. 发送消息
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -193,6 +214,7 @@ class DialogController extends AbstractController
|
||||
*/
|
||||
public function msg__sendtext()
|
||||
{
|
||||
Base::checkClientVersion('0.8.1');
|
||||
$user = User::auth();
|
||||
//
|
||||
$chat_nickname = Base::settingFind('system', 'chat_nickname');
|
||||
@@ -203,8 +225,8 @@ class DialogController extends AbstractController
|
||||
}
|
||||
}
|
||||
//
|
||||
$dialog_id = intval(Request::input('dialog_id'));
|
||||
$text = trim(Request::input('text'));
|
||||
$dialog_id = Base::getPostInt('dialog_id');
|
||||
$text = trim(Base::getPostValue('text'));
|
||||
//
|
||||
if (mb_strlen($text) < 1) {
|
||||
return Base::retError('消息内容不能为空');
|
||||
@@ -214,11 +236,21 @@ class DialogController extends AbstractController
|
||||
//
|
||||
WebSocketDialog::checkDialog($dialog_id);
|
||||
//
|
||||
$msg = [
|
||||
'text' => $text
|
||||
];
|
||||
if (mb_strlen($text) > 2000) {
|
||||
$array = mb_str_split($text, 2000);
|
||||
} else {
|
||||
$array = [$text];
|
||||
}
|
||||
//
|
||||
return WebSocketDialogMsg::sendMsg($dialog_id, 'text', $msg, $user->userid);
|
||||
$list = [];
|
||||
foreach ($array as $item) {
|
||||
$res = WebSocketDialogMsg::sendMsg($dialog_id, 'text', ['text' => $item], $user->userid);
|
||||
if (Base::isSuccess($res)) {
|
||||
$list[] = $res['data'];
|
||||
}
|
||||
}
|
||||
//
|
||||
return Base::retSuccess('发送成功', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,7 +278,7 @@ class DialogController extends AbstractController
|
||||
//
|
||||
$dialog = WebSocketDialog::checkDialog($dialog_id);
|
||||
//
|
||||
$path = "uploads/chat/" . $user->userid . "/";
|
||||
$path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
|
||||
$image64 = Base::getPostValue('image64');
|
||||
$fileName = Base::getPostValue('filename');
|
||||
if ($image64) {
|
||||
@@ -258,7 +290,7 @@ class DialogController extends AbstractController
|
||||
} else {
|
||||
$data = Base::upload([
|
||||
"file" => Request::file('files'),
|
||||
"type" => 'file',
|
||||
"type" => 'more',
|
||||
"path" => $path,
|
||||
"fileName" => $fileName,
|
||||
]);
|
||||
@@ -271,8 +303,8 @@ class DialogController extends AbstractController
|
||||
$fileData['thumb'] = Base::unFillUrl($fileData['thumb']);
|
||||
$fileData['size'] *= 1024;
|
||||
//
|
||||
if ($dialog->type === 'group') {
|
||||
if ($dialog->group_type === 'task') {
|
||||
if ($dialog->type === 'group' && $dialog->group_type === 'task') { // 任务群聊保存文件
|
||||
if (!in_array($fileData['ext'], File::localExt)) { // 如果是图片不保存
|
||||
$task = ProjectTask::whereDialogId($dialog->id)->first();
|
||||
if ($task) {
|
||||
$file = ProjectTaskFile::createInstance([
|
||||
@@ -338,6 +370,9 @@ class DialogController extends AbstractController
|
||||
* @apiName msg__detail
|
||||
*
|
||||
* @apiParam {Number} msg_id 消息ID
|
||||
* @apiParam {String} only_update_at 仅获取update_at字段
|
||||
* - no (默认)
|
||||
* - yes
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -348,39 +383,30 @@ class DialogController extends AbstractController
|
||||
User::auth();
|
||||
//
|
||||
$msg_id = intval(Request::input('msg_id'));
|
||||
$only_update_at = Request::input('only_update_at', 'no');
|
||||
//
|
||||
$dialogMsg = WebSocketDialogMsg::whereId($msg_id)->first();
|
||||
if (empty($dialogMsg)) {
|
||||
return Base::retError("文件不存在");
|
||||
}
|
||||
//
|
||||
if ($only_update_at == 'yes') {
|
||||
return Base::retSuccess('success', [
|
||||
'id' => $dialogMsg->id,
|
||||
'update_at' => Carbon::parse($dialogMsg->updated_at)->toDateTimeString()
|
||||
]);
|
||||
}
|
||||
//
|
||||
$data = $dialogMsg->toArray();
|
||||
//
|
||||
if ($data['type'] == 'file') {
|
||||
$codeExt = ['txt'];
|
||||
$officeExt = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
|
||||
$localExt = ['jpg', 'jpeg', 'png', 'gif'];
|
||||
$msg = Base::json2array($dialogMsg->getRawOriginal('msg'));
|
||||
$filePath = public_path($msg['path']);
|
||||
if (in_array($msg['ext'], $codeExt) && $msg['size'] < 2 * 1024 * 1024) {
|
||||
// 文本预览,限制2M内的文件
|
||||
$data['content'] = file_get_contents($filePath);
|
||||
$data['file_mode'] = 1;
|
||||
} elseif (in_array($msg['ext'], $officeExt)) {
|
||||
// office预览
|
||||
$data['file_mode'] = 2;
|
||||
} else {
|
||||
// 其他预览
|
||||
if (in_array($msg['ext'], $localExt)) {
|
||||
$url = Base::fillUrl($msg['path']);
|
||||
} else {
|
||||
$url = 'http://' . env('APP_IPPR') . '.3/' . $msg['path'];
|
||||
}
|
||||
$data['url'] = base64_encode($url);
|
||||
$data['file_mode'] = 3;
|
||||
}
|
||||
$msg = File::formatFileData($msg);
|
||||
$data['content'] = $msg['content'];
|
||||
$data['file_mode'] = $msg['file_mode'];
|
||||
}
|
||||
//
|
||||
return Base::retSuccess("success", $data);
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -414,4 +440,112 @@ class DialogController extends AbstractController
|
||||
//
|
||||
return Response::download(public_path($array['path']), $array['name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/withdraw 11. 聊天消息撤回
|
||||
*
|
||||
* @apiDescription 消息撤回限制24小时内,需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup dialog
|
||||
* @apiName msg__withdraw
|
||||
*
|
||||
* @apiParam {Number} msg_id 消息ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function msg__withdraw()
|
||||
{
|
||||
$user = User::auth();
|
||||
$msg_id = intval(Request::input("msg_id"));
|
||||
$msg = WebSocketDialogMsg::whereId($msg_id)->whereUserid($user->userid)->first();
|
||||
if (empty($msg)) {
|
||||
return Base::retError("消息不存在或已被删除");
|
||||
}
|
||||
$msg->deleteMsg();
|
||||
return Base::retSuccess("success");
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/top 12. 会话置顶
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup dialog
|
||||
* @apiName top
|
||||
*
|
||||
* @apiParam {Number} dialog_id 会话ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function top()
|
||||
{
|
||||
$user = User::auth();
|
||||
$dialogId = intval(Request::input('dialog_id'));
|
||||
$dialogUser = WebSocketDialogUser::whereUserid($user->userid)->whereDialogId($dialogId)->first();
|
||||
if (!$dialogUser) {
|
||||
return Base::retError("会话不存在");
|
||||
}
|
||||
$dialogUser->top_at = $dialogUser->top_at ? null : Carbon::now();
|
||||
$dialogUser->save();
|
||||
return Base::retSuccess("success", [
|
||||
'id' => $dialogUser->dialog_id,
|
||||
'top_at' => $dialogUser->top_at?->toDateTimeString(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/mark 13. 消息标记操作
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup dialog
|
||||
* @apiName msg__mark
|
||||
*
|
||||
* @apiParam {Number} dialog_id 消息ID
|
||||
* @apiParam {String} type 类型
|
||||
* - read
|
||||
* - unread
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function msg__mark()
|
||||
{
|
||||
$user = User::auth();
|
||||
$dialogId = intval(Request::input('dialog_id'));
|
||||
$type = Request::input('type');
|
||||
$dialogUser = WebSocketDialogUser::whereUserid($user->userid)->whereDialogId($dialogId)->first();
|
||||
if (!$dialogUser) {
|
||||
return Base::retError("会话不存在");
|
||||
}
|
||||
switch ($type) {
|
||||
case 'read':
|
||||
WebSocketDialogMsgRead::whereUserid($user->userid)
|
||||
->whereReadAt(null)
|
||||
->whereDialogId($dialogId)
|
||||
->chunkById(100, function ($list) {
|
||||
WebSocketDialogMsgRead::onlyMarkRead($list);
|
||||
});
|
||||
$dialogUser->mark_unread = 0;
|
||||
$dialogUser->save();
|
||||
break;
|
||||
|
||||
case 'unread':
|
||||
$dialogUser->mark_unread = 1;
|
||||
$dialogUser->save();
|
||||
break;
|
||||
|
||||
default:
|
||||
return Base::retError("参数错误");
|
||||
}
|
||||
return Base::retSuccess("success", [
|
||||
'id' => $dialogId,
|
||||
'mark_unread' => $dialogUser->mark_unread,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,11 @@ use App\Models\FileContent;
|
||||
use App\Models\FileLink;
|
||||
use App\Models\FileUser;
|
||||
use App\Models\User;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Module\Base;
|
||||
use App\Module\Ihttp;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Request;
|
||||
use Response;
|
||||
|
||||
/**
|
||||
* @apiDefine file
|
||||
@@ -91,6 +90,10 @@ class FileController extends AbstractController
|
||||
}
|
||||
}
|
||||
}
|
||||
// 图片直接返回预览地址
|
||||
foreach ($array as &$item) {
|
||||
File::handleImageUrl($item);
|
||||
}
|
||||
return Base::retSuccess('success', $array);
|
||||
}
|
||||
|
||||
@@ -207,21 +210,30 @@ class FileController extends AbstractController
|
||||
'folder',
|
||||
'document',
|
||||
'mind',
|
||||
'flow',
|
||||
'drawio',
|
||||
'word',
|
||||
'excel',
|
||||
'ppt',
|
||||
])) {
|
||||
return Base::retError('类型错误');
|
||||
}
|
||||
$ext = '';
|
||||
if (in_array($type, [
|
||||
$ext = str_replace([
|
||||
'folder',
|
||||
'document',
|
||||
'mind',
|
||||
'drawio',
|
||||
'word',
|
||||
'excel',
|
||||
'ppt',
|
||||
])) {
|
||||
$ext = str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $type);
|
||||
}
|
||||
], [
|
||||
'',
|
||||
'md',
|
||||
'mind',
|
||||
'drawio',
|
||||
'docx',
|
||||
'xlsx',
|
||||
'pptx',
|
||||
], $type);
|
||||
//
|
||||
$userid = $user->userid;
|
||||
if ($pid > 0) {
|
||||
@@ -244,7 +256,7 @@ class FileController extends AbstractController
|
||||
'userid' => $userid,
|
||||
'created_id' => $user->userid,
|
||||
]);
|
||||
$file->save();
|
||||
$file->saveBeforePids();
|
||||
//
|
||||
$data = File::find($file->id);
|
||||
$data->pushMsg('add', $data);
|
||||
@@ -294,9 +306,19 @@ class FileController extends AbstractController
|
||||
'userid' => $userid,
|
||||
'created_id' => $user->userid,
|
||||
]);
|
||||
$file->save();
|
||||
$data = AbstractModel::transaction(function() use ($file) {
|
||||
$content = FileContent::select(['content', 'text', 'size'])->whereFid($file->cid)->orderByDesc('id')->first();
|
||||
$file->size = $content?->size ?: 0;
|
||||
$file->saveBeforePids();
|
||||
if ($content) {
|
||||
$content = $content->toArray();
|
||||
$content['fid'] = $file->id;
|
||||
$content['userid'] = $file->userid;
|
||||
FileContent::createInstance($content)->save();
|
||||
}
|
||||
return File::find($file->id);
|
||||
});
|
||||
//
|
||||
$data = File::find($file->id);
|
||||
$data->pushMsg('add', $data);
|
||||
return Base::retSuccess('复制成功', $data);
|
||||
}
|
||||
@@ -309,7 +331,7 @@ class FileController extends AbstractController
|
||||
* @apiGroup file
|
||||
* @apiName move
|
||||
*
|
||||
* @apiParam {Number} id 文件ID
|
||||
* @apiParam {Numbers} ids 文件ID(格式:[id1, id2])
|
||||
* @apiParam {Number} pid 移动到的文件夹ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
@@ -318,32 +340,47 @@ class FileController extends AbstractController
|
||||
*/
|
||||
public function move()
|
||||
{
|
||||
$user = User::auth();
|
||||
User::auth();
|
||||
//
|
||||
$id = intval(Request::input('id'));
|
||||
$ids = Request::input('ids');
|
||||
$pid = intval(Request::input('pid'));
|
||||
//
|
||||
$file = File::permissionFind($id, 1000);
|
||||
//
|
||||
if (!is_array($ids) || empty($ids)) {
|
||||
return Base::retError('请选择移动的文件或文件夹');
|
||||
}
|
||||
if (count($ids) > 100) {
|
||||
return Base::retError('一次最多只能移动100个文件或文件夹');
|
||||
}
|
||||
if ($pid > 0) {
|
||||
if (!File::whereUserid($user->userid)->whereId($pid)->exists()) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
$arr = [];
|
||||
$tid = $pid;
|
||||
while ($tid > 0) {
|
||||
$arr[] = $tid;
|
||||
$tid = intval(File::whereId($tid)->value('pid'));
|
||||
}
|
||||
if (in_array($id, $arr)) {
|
||||
return Base::retError('位置错误');
|
||||
}
|
||||
File::permissionFind($pid, 1);
|
||||
}
|
||||
//
|
||||
$file->pid = $pid;
|
||||
$file->save();
|
||||
$file->pushMsg('update', $file);
|
||||
return Base::retSuccess('操作成功', $file);
|
||||
$files = [];
|
||||
AbstractModel::transaction(function() use ($pid, $ids, &$files) {
|
||||
foreach ($ids as $id) {
|
||||
$file = File::permissionFind($id, 1000);
|
||||
//
|
||||
if ($pid > 0) {
|
||||
$arr = [];
|
||||
$tid = $pid;
|
||||
while ($tid > 0) {
|
||||
$arr[] = $tid;
|
||||
$tid = intval(File::whereId($tid)->value('pid'));
|
||||
}
|
||||
if (in_array($id, $arr)) {
|
||||
throw new ApiException('移动位置错误');
|
||||
}
|
||||
}
|
||||
//
|
||||
$file->pid = $pid;
|
||||
$file->saveBeforePids();
|
||||
$files[] = $file;
|
||||
}
|
||||
});
|
||||
foreach ($files as $file) {
|
||||
$file->pushMsg('update', $file);
|
||||
}
|
||||
return Base::retSuccess('操作成功', $files);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,7 +391,7 @@ class FileController extends AbstractController
|
||||
* @apiGroup file
|
||||
* @apiName remove
|
||||
*
|
||||
* @apiParam {Number} id 文件ID
|
||||
* @apiParam {Numbers} ids 文件ID(格式:[id1, id2])
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -364,12 +401,25 @@ class FileController extends AbstractController
|
||||
{
|
||||
User::auth();
|
||||
//
|
||||
$id = intval(Request::input('id'));
|
||||
$ids = Request::input('ids');
|
||||
//
|
||||
$file = File::permissionFind($id, 1000);
|
||||
if (!is_array($ids) || empty($ids)) {
|
||||
return Base::retError('请选择删除的文件或文件夹');
|
||||
}
|
||||
if (count($ids) > 100) {
|
||||
return Base::retError('一次最多只能删除100个文件或文件夹');
|
||||
}
|
||||
//
|
||||
$file->deleteFile();
|
||||
return Base::retSuccess('删除成功', $file);
|
||||
$files = [];
|
||||
AbstractModel::transaction(function() use ($ids, &$files) {
|
||||
foreach ($ids as $id) {
|
||||
$file = File::permissionFind($id, 1000);
|
||||
$file->deleteFile();
|
||||
$files[] = $file;
|
||||
}
|
||||
});
|
||||
//
|
||||
return Base::retSuccess('删除成功', $files);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -383,7 +433,10 @@ class FileController extends AbstractController
|
||||
* @apiParam {Number|String} id
|
||||
* - Number: 文件ID(需要登录)
|
||||
* - String: 链接码(不需要登录,用于预览)
|
||||
* @apiParam {String} down 直接下载
|
||||
* @apiParam {String} only_update_at 仅获取update_at字段
|
||||
* - no (默认)
|
||||
* - yes
|
||||
* @apiParam {String} down 直接下载
|
||||
* - no: 浏览(默认)
|
||||
* - yes: 下载(office文件直接下载)
|
||||
*
|
||||
@@ -395,6 +448,7 @@ class FileController extends AbstractController
|
||||
{
|
||||
$id = Request::input('id');
|
||||
$down = Request::input('down', 'no');
|
||||
$only_update_at = Request::input('only_update_at', 'no');
|
||||
//
|
||||
if (Base::isNumber($id)) {
|
||||
User::auth();
|
||||
@@ -409,6 +463,13 @@ class FileController extends AbstractController
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
//
|
||||
if ($only_update_at == 'yes') {
|
||||
return Base::retSuccess('success', [
|
||||
'id' => $file->id,
|
||||
'update_at' => Carbon::parse($file->updated_at)->toDateTimeString()
|
||||
]);
|
||||
}
|
||||
//
|
||||
$content = FileContent::whereFid($file->id)->orderByDesc('id')->first();
|
||||
return FileContent::formatContent($file, $content?->content, $down == 'yes');
|
||||
}
|
||||
@@ -421,7 +482,7 @@ class FileController extends AbstractController
|
||||
* @apiGroup file
|
||||
* @apiName content__save
|
||||
*
|
||||
* @apiParam {Number} id 文件ID
|
||||
* @apiParam {Number} id 文件ID
|
||||
* @apiParam {Object} [D] Request Payload 提交
|
||||
* - content: 内容
|
||||
*
|
||||
@@ -431,6 +492,7 @@ class FileController extends AbstractController
|
||||
*/
|
||||
public function content__save()
|
||||
{
|
||||
Base::checkClientVersion('0.9.13');
|
||||
$user = User::auth();
|
||||
//
|
||||
$id = Base::getPostInt('id');
|
||||
@@ -444,12 +506,11 @@ class FileController extends AbstractController
|
||||
$isRep = false;
|
||||
preg_match_all("/<img\s*src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"/s", $data['content'], $matchs);
|
||||
foreach ($matchs[2] as $key => $text) {
|
||||
$p = "uploads/files/document/" . $id . "/";
|
||||
Base::makeDir(public_path($p));
|
||||
$p.= md5($text) . "." . $matchs[1][$key];
|
||||
$r = file_put_contents(public_path($p), base64_decode($text));
|
||||
if ($r) {
|
||||
$data['content'] = str_replace($matchs[0][$key], '<img src="' . Base::fillUrl($p) . '"', $data['content']);
|
||||
$tmpPath = "uploads/file/document/" . date("Ym") . "/" . $id . "/attached/";
|
||||
Base::makeDir(public_path($tmpPath));
|
||||
$tmpPath .= md5($text) . "." . $matchs[1][$key];
|
||||
if (file_put_contents(public_path($tmpPath), base64_decode($text))) {
|
||||
$data['content'] = str_replace($matchs[0][$key], '<img src="' . Base::fillUrl($tmpPath) . '"', $data['content']);
|
||||
$isRep = true;
|
||||
}
|
||||
}
|
||||
@@ -459,11 +520,41 @@ class FileController extends AbstractController
|
||||
}
|
||||
}
|
||||
//
|
||||
switch ($file->type) {
|
||||
case 'document':
|
||||
$contentArray = Base::json2array($content);
|
||||
$contentString = $contentArray['content'];
|
||||
$file->ext = $contentArray['type'] == 'md' ? 'md' : 'text';
|
||||
break;
|
||||
case 'drawio':
|
||||
$contentArray = Base::json2array($content);
|
||||
$contentString = $contentArray['xml'];
|
||||
$file->ext = 'drawio';
|
||||
break;
|
||||
case 'mind':
|
||||
$contentString = $content;
|
||||
$file->ext = 'mind';
|
||||
break;
|
||||
case 'code':
|
||||
case 'txt':
|
||||
$contentString = $content;
|
||||
break;
|
||||
default:
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
$path = "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $id . "/" . md5($contentString);
|
||||
$save = public_path($path);
|
||||
Base::makeDir(dirname($save));
|
||||
file_put_contents($save, $contentString);
|
||||
//
|
||||
$content = FileContent::createInstance([
|
||||
'fid' => $file->id,
|
||||
'content' => $content,
|
||||
'content' => [
|
||||
'type' => $file->ext,
|
||||
'url' => $path
|
||||
],
|
||||
'text' => $text,
|
||||
'size' => strlen($content),
|
||||
'size' => filesize($save),
|
||||
'userid' => $user->userid,
|
||||
]);
|
||||
$content->save();
|
||||
@@ -503,7 +594,7 @@ class FileController extends AbstractController
|
||||
if ($status === 2) {
|
||||
$parse = parse_url($url);
|
||||
$from = 'http://' . env('APP_IPPR') . '.3' . $parse['path'] . '?' . $parse['query'];
|
||||
$path = 'uploads/office/' . date("Ym") . '/' . $file->id . '/' . $user->userid . '-' . $key;
|
||||
$path = 'uploads/file/' . $file->type . '/' . date("Ym") . '/' . $file->id . '/' . $key;
|
||||
$save = public_path($path);
|
||||
Base::makeDir(dirname($save));
|
||||
$res = Ihttp::download($from, $save);
|
||||
@@ -521,6 +612,7 @@ class FileController extends AbstractController
|
||||
$content->save();
|
||||
//
|
||||
$file->size = $content->size;
|
||||
$file->updated_at = Carbon::now();
|
||||
$file->save();
|
||||
$file->pushMsg('update', $file);
|
||||
}
|
||||
@@ -564,10 +656,11 @@ class FileController extends AbstractController
|
||||
}
|
||||
//
|
||||
$dirs = explode("/", $webkitRelativePath);
|
||||
AbstractModel::transaction(function() use ($user, $userid, $dirs, &$pid) {
|
||||
while (count($dirs) > 1) {
|
||||
$dirName = array_shift($dirs);
|
||||
if ($dirName) {
|
||||
while (count($dirs) > 1) {
|
||||
$dirName = array_shift($dirs);
|
||||
if ($dirName) {
|
||||
$pushMsg = [];
|
||||
AbstractModel::transaction(function () use ($dirName, $user, $userid, &$pid, &$pushMsg) {
|
||||
$dirRow = File::wherePid($pid)->whereType('folder')->whereName($dirName)->lockForUpdate()->first();
|
||||
if (empty($dirRow)) {
|
||||
$dirRow = File::createInstance([
|
||||
@@ -577,20 +670,22 @@ class FileController extends AbstractController
|
||||
'userid' => $userid,
|
||||
'created_id' => $user->userid,
|
||||
]);
|
||||
if ($dirRow->save()) {
|
||||
$tmpRow = File::find($dirRow->id);
|
||||
$tmpRow->pushMsg('add', $tmpRow);
|
||||
if ($dirRow->saveBeforePids()) {
|
||||
$pushMsg[] = File::find($dirRow->id);
|
||||
}
|
||||
}
|
||||
if (empty($dirRow)) {
|
||||
throw new ApiException('创建文件夹失败');
|
||||
}
|
||||
$pid = $dirRow->id;
|
||||
});
|
||||
foreach ($pushMsg as $tmpRow) {
|
||||
$tmpRow->pushMsg('add', $tmpRow);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
//
|
||||
$path = 'uploads/file/' . date("Ym") . '/u' . $user->userid . '/';
|
||||
$path = 'uploads/tmp/' . date("Ym") . '/';
|
||||
$data = Base::upload([
|
||||
"file" => Request::file('files'),
|
||||
"type" => 'more',
|
||||
@@ -603,28 +698,36 @@ class FileController extends AbstractController
|
||||
$data = $data['data'];
|
||||
//
|
||||
$type = match ($data['ext']) {
|
||||
'text', 'md', 'markdown' => 'document',
|
||||
'drawio' => 'drawio',
|
||||
'mind' => 'mind',
|
||||
'doc', 'docx' => "word",
|
||||
'xls', 'xlsx' => "excel",
|
||||
'ppt', 'pptx' => "ppt",
|
||||
'wps' => "wps",
|
||||
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw' => "picture",
|
||||
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z' => "archive",
|
||||
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw', 'svg' => "picture",
|
||||
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z', 'gz', 'apk', 'dmg' => "archive",
|
||||
'tif', 'tiff' => "tif",
|
||||
'dwg', 'dxf' => "cad",
|
||||
'ofd' => "ofd",
|
||||
'pdf' => "pdf",
|
||||
'txt' => "txt",
|
||||
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css',
|
||||
'dockerfile', 'go', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
|
||||
'ocamlmakefile', 'make', 'md', 'markdown', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php',
|
||||
'dockerfile', 'go', 'golang', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
|
||||
'ocamlmakefile', 'make', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php',
|
||||
'inc', 'phtml', 'shtml', 'php3', 'php4', 'php5', 'phps', 'phpt', 'aw', 'ctp', 'module', 'ps1', 'py', 'r', 'rb', 'ru', 'gemspec', 'rake', 'guardfile', 'rakefile',
|
||||
'gemfile', 'rs', 'sass', 'scss', 'sh', 'bash', 'bashrc', 'sql', 'sqlserver', 'swift', 'ts', 'typescript', 'str', 'vbs', 'vb', 'v', 'vh', 'sv', 'svh', 'xml',
|
||||
'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
|
||||
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx' => "code",
|
||||
'mp3', 'wav', 'mp4', 'flv',
|
||||
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm' => "media",
|
||||
'xmind' => "xmind",
|
||||
'rp' => "axure",
|
||||
default => "",
|
||||
};
|
||||
if ($data['ext'] == 'markdown') {
|
||||
$data['ext'] = 'md';
|
||||
}
|
||||
$file = File::createInstance([
|
||||
'pid' => $pid,
|
||||
'name' => Base::rightDelete($data['name'], '.' . $data['ext']),
|
||||
@@ -636,8 +739,9 @@ class FileController extends AbstractController
|
||||
// 开始创建
|
||||
return AbstractModel::transaction(function () use ($webkitRelativePath, $type, $user, $data, $file) {
|
||||
$file->size = $data['size'] * 1024;
|
||||
$file->save();
|
||||
$file->saveBeforePids();
|
||||
//
|
||||
$data = Base::uploadMove($data, "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $file->id . "/");
|
||||
$content = FileContent::createInstance([
|
||||
'fid' => $file->id,
|
||||
'content' => [
|
||||
@@ -657,6 +761,7 @@ class FileController extends AbstractController
|
||||
//
|
||||
$data = $tmpRow->toArray();
|
||||
$data['full_name'] = $webkitRelativePath ?: $data['name'];
|
||||
File::handleImageUrl($data);
|
||||
return Base::retSuccess($data['name'] . ' 上传成功', $data);
|
||||
});
|
||||
}
|
||||
@@ -711,6 +816,9 @@ class FileController extends AbstractController
|
||||
* - 0:只读
|
||||
* - 1:读写
|
||||
* - -1: 删除
|
||||
* @apiParam {Number} [force] 设置共享时是否忽略提醒
|
||||
* - 0:如果子文件夹已存在共享则ret返回-3001(默认)
|
||||
* - 1:忽略提醒
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -723,6 +831,7 @@ class FileController extends AbstractController
|
||||
$id = intval(Request::input('id'));
|
||||
$userids = Request::input('userids');
|
||||
$permission = intval(Request::input('permission'));
|
||||
$force = intval(Request::input('force'));
|
||||
//
|
||||
if (!in_array($permission, [-1, 0, 1])) {
|
||||
return Base::retError('参数错误');
|
||||
@@ -749,16 +858,18 @@ class FileController extends AbstractController
|
||||
// 取消共享
|
||||
$action = "delete";
|
||||
foreach ($userids as $userid) {
|
||||
if (FileUser::where([
|
||||
'file_id' => $file->id,
|
||||
'userid' => $userid,
|
||||
])->delete()) {
|
||||
if (FileUser::deleteFileUser($file->id, $userid)) {
|
||||
$array[] = $userid;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 设置共享
|
||||
$action = "update";
|
||||
if ($force === 0) {
|
||||
if (File::where("pids", "like", "%,{$file->id},%")->whereShare(1)->exists()) {
|
||||
return Base::retError('此文件夹内已有共享文件夹', [], -3001);
|
||||
}
|
||||
}
|
||||
if (FileUser::whereFileId($file->id)->count() + count($userids) > 100) {
|
||||
return Base::retError('共享人数上限100个成员');
|
||||
}
|
||||
@@ -774,7 +885,7 @@ class FileController extends AbstractController
|
||||
}
|
||||
}
|
||||
//
|
||||
$file->setShare();
|
||||
$file->updataShare();
|
||||
$file->pushMsg($action, $action == "delete" ? null : $file, $array);
|
||||
return Base::retSuccess($action == "delete" ? "删除成功" : "设置成功", $file);
|
||||
}
|
||||
@@ -804,18 +915,12 @@ class FileController extends AbstractController
|
||||
if ($file->userid == $user->userid) {
|
||||
return Base::retError('不能退出自己共享的文件');
|
||||
}
|
||||
if (FileUser::where([
|
||||
'file_id' => $file->id,
|
||||
'userid' => 0,
|
||||
])->exists()) {
|
||||
if (FileUser::whereFileId($file->id)->whereUserid(0)->exists()) {
|
||||
return Base::retError('无法退出共享所有人的文件或文件夹');
|
||||
}
|
||||
FileUser::where([
|
||||
'file_id' => $file->id,
|
||||
'userid' => $user->userid,
|
||||
])->delete();
|
||||
FileUser::deleteFileUser($file->id, $user->userid);
|
||||
//
|
||||
$file->setShare();
|
||||
$file->updataShare();
|
||||
return Base::retSuccess("退出成功");
|
||||
}
|
||||
|
||||
@@ -838,20 +943,21 @@ class FileController extends AbstractController
|
||||
*/
|
||||
public function link()
|
||||
{
|
||||
User::auth();
|
||||
$user = User::auth();
|
||||
//
|
||||
$id = intval(Request::input('id'));
|
||||
$refresh = Request::input('refresh', 'no');
|
||||
//
|
||||
$file = File::permissionFind($id, 1000);
|
||||
$file = File::permissionFind($id);
|
||||
if ($file->type == 'folder') {
|
||||
return Base::retError('文件夹暂不支持此功能');
|
||||
}
|
||||
//
|
||||
$fileLink = FileLink::whereFileId($file->id)->first();
|
||||
$fileLink = FileLink::whereFileId($file->id)->whereUserid($user->userid)->first();
|
||||
if (empty($fileLink)) {
|
||||
$fileLink = FileLink::createInstance([
|
||||
'file_id' => $file->id,
|
||||
'userid' => $user->userid,
|
||||
'code' => Base::generatePassword(64),
|
||||
]);
|
||||
$fileLink->save();
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Models\AbstractModel;
|
||||
use App\Models\File;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectColumn;
|
||||
use App\Models\ProjectFlow;
|
||||
@@ -12,14 +13,18 @@ use App\Models\ProjectInvite;
|
||||
use App\Models\ProjectLog;
|
||||
use App\Models\ProjectTask;
|
||||
use App\Models\ProjectTaskFile;
|
||||
use App\Models\ProjectTaskFlowChange;
|
||||
use App\Models\ProjectUser;
|
||||
use App\Models\User;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Module\Base;
|
||||
use App\Module\BillExport;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Arr;
|
||||
use Madzipper;
|
||||
use Request;
|
||||
use Response;
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* @apiDefine project
|
||||
@@ -204,6 +209,9 @@ class ProjectController extends AbstractController
|
||||
* @apiParam {String} name 项目名称
|
||||
* @apiParam {String} [desc] 项目介绍
|
||||
* @apiParam {String} [columns] 列表,格式:列表名称1,列表名称2
|
||||
* @apiParam {String} [flow] 开启流程
|
||||
* - open: 开启
|
||||
* - close: 关闭(默认)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -215,6 +223,7 @@ class ProjectController extends AbstractController
|
||||
// 项目名称
|
||||
$name = trim(Request::input('name', ''));
|
||||
$desc = trim(Request::input('desc', ''));
|
||||
$flow = trim(Request::input('flow', 'close'));
|
||||
if (mb_strlen($name) < 2) {
|
||||
return Base::retError('项目名称不可以少于2个字');
|
||||
} elseif (mb_strlen($name) > 32) {
|
||||
@@ -251,7 +260,7 @@ class ProjectController extends AbstractController
|
||||
'desc' => $desc,
|
||||
'userid' => $user->userid,
|
||||
]);
|
||||
AbstractModel::transaction(function() use ($insertColumns, $project) {
|
||||
AbstractModel::transaction(function() use ($flow, $insertColumns, $project) {
|
||||
$project->save();
|
||||
ProjectUser::createInstance([
|
||||
'project_id' => $project->id,
|
||||
@@ -268,6 +277,10 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
$project->dialog_id = $dialog->id;
|
||||
$project->save();
|
||||
//
|
||||
if ($flow == 'open') {
|
||||
$project->addFlow(Base::json2array('[{"id":-10,"name":"待处理","status":"start","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-11,"name":"进行中","status":"progress","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-12,"name":"待测试","status":"test","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-13,"name":"已完成","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-14,"name":"已取消","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0}]'));
|
||||
}
|
||||
});
|
||||
//
|
||||
$data = Project::find($project->id);
|
||||
@@ -595,13 +608,14 @@ class ProjectController extends AbstractController
|
||||
if (!is_array($item['task'])) continue;
|
||||
$index = 0;
|
||||
foreach ($item['task'] as $task_id) {
|
||||
ProjectTask::whereId($task_id)->whereProjectId($project->id)->update([
|
||||
if (ProjectTask::whereId($task_id)->whereProjectId($project->id)->whereCompleteAt(null)->update([
|
||||
'column_id' => $item['id'],
|
||||
'sort' => $index
|
||||
]);
|
||||
ProjectTask::whereParentId($task_id)->whereProjectId($project->id)->update([
|
||||
'column_id' => $item['id'],
|
||||
]);
|
||||
])) {
|
||||
ProjectTask::whereParentId($task_id)->whereProjectId($project->id)->update([
|
||||
'column_id' => $item['id'],
|
||||
]);
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
@@ -880,6 +894,10 @@ class ProjectController extends AbstractController
|
||||
* - all:所有
|
||||
* - yes:已归档
|
||||
* - no:未归档(默认)
|
||||
* @apiParam {String} [deleted] 是否读取已删除
|
||||
* - all:所有
|
||||
* - yes:已删除
|
||||
* - no:未删除(默认)
|
||||
* @apiParam {Object} sorts 排序方式
|
||||
* - sorts.complete_at 完成时间:asc|desc
|
||||
* - sorts.archived_at 归档时间:asc|desc
|
||||
@@ -902,6 +920,7 @@ class ProjectController extends AbstractController
|
||||
$time_before = Request::input('time_before');
|
||||
$complete = Request::input('complete', 'all');
|
||||
$archived = Request::input('archived', 'no');
|
||||
$deleted = Request::input('deleted', 'no');
|
||||
$keys = Request::input('keys');
|
||||
$sorts = Request::input('sorts');
|
||||
$keys = is_array($keys) ? $keys : [];
|
||||
@@ -913,7 +932,9 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$scopeAll = false;
|
||||
if ($parent_id > 0) {
|
||||
ProjectTask::userTask($parent_id, str_replace(['all', 'yes', 'no'], [null, false, true], $archived));
|
||||
$isArchived = str_replace(['all', 'yes', 'no'], [null, false, true], $archived);
|
||||
$isDeleted = str_replace(['all', 'yes', 'no'], [null, false, true], $deleted);
|
||||
ProjectTask::userTask($parent_id, $isArchived, $isDeleted);
|
||||
$scopeAll = true;
|
||||
$builder->where('project_tasks.parent_id', $parent_id);
|
||||
} elseif ($parent_id === -1) {
|
||||
@@ -956,8 +977,14 @@ class ProjectController extends AbstractController
|
||||
$builder->whereNull('project_tasks.archived_at');
|
||||
}
|
||||
//
|
||||
if ($deleted == 'all') {
|
||||
$builder->withTrashed();
|
||||
} elseif ($deleted == 'yes') {
|
||||
$builder->onlyTrashed();
|
||||
}
|
||||
//
|
||||
foreach ($sorts as $column => $direction) {
|
||||
if (!in_array($column, ['complete_at', 'archived_at', 'end_at'])) continue;
|
||||
if (!in_array($column, ['complete_at', 'archived_at', 'end_at', 'deleted_at'])) continue;
|
||||
if (!in_array($direction, ['asc', 'desc'])) continue;
|
||||
$builder->orderBy('project_tasks.' . $column, $direction);
|
||||
}
|
||||
@@ -968,7 +995,213 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/one 19. 获取单个任务信息
|
||||
* @api {get} api/project/task/export 19. 导出任务(限管理员)
|
||||
*
|
||||
* @apiDescription 导出指定范围任务(已完成、未完成、已归档),返回下载地址,需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName task__export
|
||||
*
|
||||
* @apiParam {Array} [userid] 指定会员,如:[1, 2]
|
||||
* @apiParam {Array} [time] 指定时间范围,如:['2020-12-12', '2020-12-30']
|
||||
* @apiParam {String} [type]
|
||||
* - createdTime 任务创建时间
|
||||
* - taskTime 任务计划时间(默认)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function task__export()
|
||||
{
|
||||
$user = User::auth('admin');
|
||||
//
|
||||
$userid = Base::arrayRetainInt(Request::input('userid'), true);
|
||||
$time = Request::input('time');
|
||||
$type = Request::input('type','taskTime');
|
||||
if (empty($userid) || empty($time)) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
if (count($userid) > 20) {
|
||||
return Base::retError('导出会员限制最多20个');
|
||||
}
|
||||
if (!(is_array($time) && Base::isDateOrTime($time[0]) && Base::isDateOrTime($time[1]))) {
|
||||
return Base::retError('时间选择错误');
|
||||
}
|
||||
if (Carbon::parse($time[1])->timestamp - Carbon::parse($time[0])->timestamp > 90 * 86400) {
|
||||
return Base::retError('时间范围限制最大90天');
|
||||
}
|
||||
$headings = [];
|
||||
$headings[] = '任务ID';
|
||||
$headings[] = '父级任务ID';
|
||||
$headings[] = '所属项目';
|
||||
$headings[] = '任务标题';
|
||||
$headings[] = '任务开始时间';
|
||||
$headings[] = '任务结束时间';
|
||||
$headings[] = '完成时间';
|
||||
$headings[] = '归档时间';
|
||||
$headings[] = '任务计划用时';
|
||||
$headings[] = '实际完成用时';
|
||||
$headings[] = '超时时间';
|
||||
$headings[] = '开发用时';
|
||||
$headings[] = '验收/测试用时';
|
||||
$headings[] = '负责人';
|
||||
$headings[] = '创建人';
|
||||
$datas = [];
|
||||
//
|
||||
$builder = ProjectTask::select(['project_tasks.*', 'project_task_users.userid as ownerid'])
|
||||
->join('project_task_users', 'project_tasks.id', '=', 'project_task_users.task_id')
|
||||
->where('project_task_users.owner', 1)
|
||||
->whereIn('project_task_users.userid', $userid)
|
||||
->betweenTime(Carbon::parse($time[0])->startOfDay(), Carbon::parse($time[1])->endOfDay(), $type);
|
||||
$builder->orderByDesc('project_tasks.id')->chunk(100, function($tasks) use (&$datas) {
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
$flowChanges = ProjectTaskFlowChange::whereTaskId($task->id)->get();
|
||||
$developTime = 0;//开发时间
|
||||
$testTime = 0;//验收/测试时间
|
||||
foreach ($flowChanges as $change) {
|
||||
if (!str_contains($change->before_flow_item_name, 'end')) {
|
||||
$upOne = ProjectTaskFlowChange::where('id', '<', $change->id)->whereTaskId($task->id)->orderByDesc('id')->first();
|
||||
if ($upOne) {
|
||||
if (str_contains($change->before_flow_item_name, 'progress') && str_contains($change->before_flow_item_name, '进行')) {
|
||||
$devCtime = Carbon::parse($change->created_at)->timestamp;
|
||||
$oCtime = Carbon::parse($upOne->created_at)->timestamp;
|
||||
$minusNum = $devCtime - $oCtime;
|
||||
$developTime += $minusNum;
|
||||
}
|
||||
if (str_contains($change->before_flow_item_name, 'test') || str_contains($change->before_flow_item_name, '测试') || strpos($change->before_flow_item_name, '验收') !== false) {
|
||||
$testCtime = Carbon::parse($change->created_at)->timestamp;
|
||||
$tTime = Carbon::parse($upOne->created_at)->timestamp;
|
||||
$tMinusNum = $testCtime - $tTime;
|
||||
$testTime += $tMinusNum;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$task->complete_at) {
|
||||
$lastChange = ProjectTaskFlowChange::whereTaskId($task->id)->orderByDesc('id')->first();
|
||||
$nowTime = time();
|
||||
$unFinishTime = $nowTime - Carbon::parse($lastChange->created_at)->timestamp;
|
||||
if (str_contains($lastChange->after_flow_item_name, 'progress') || str_contains($lastChange->after_flow_item_name, '进行')) {
|
||||
$developTime += $unFinishTime;
|
||||
} elseif (str_contains($lastChange->after_flow_item_name, 'test') || str_contains($lastChange->after_flow_item_name, '测试') || strpos($lastChange->after_flow_item_name, '验收') !== false) {
|
||||
$testTime += $unFinishTime;
|
||||
}
|
||||
}
|
||||
$firstChange = ProjectTaskFlowChange::whereTaskId($task->id)->orderBy('id')->first();
|
||||
if (str_contains($firstChange->after_flow_item_name, 'end')) {
|
||||
$firstDevTime = Carbon::parse($firstChange->created_at)->timestamp - Carbon::parse($task->created_at)->timestamp;
|
||||
$developTime += $firstDevTime;
|
||||
}
|
||||
if (count($flowChanges) === 0 && $task->start_at) {
|
||||
$lastTime = $task->complete_at ? Carbon::parse($task->complete_at)->timestamp : time();
|
||||
$developTime = $lastTime - Carbon::parse($task->start_at)->timestamp;
|
||||
}
|
||||
$totalTime = $developTime + $testTime; //任务总用时
|
||||
if ($task->complete_at) {
|
||||
$a = Carbon::parse($task->complete_at)->timestamp;
|
||||
if ($task->start_at) {
|
||||
$b = Carbon::parse($task->start_at)->timestamp;
|
||||
$totalTime = $a - $b;
|
||||
}
|
||||
}
|
||||
$planTime = '-';//任务计划用时
|
||||
$overTime = '-';//超时时间
|
||||
if ($task->end_at) {
|
||||
$startTime = Carbon::parse($task->start_at)->timestamp;
|
||||
$endTime = Carbon::parse($task->end_at)->timestamp;
|
||||
$planTotalTime = $endTime - $startTime;
|
||||
$residueTime = $planTotalTime - $totalTime;
|
||||
if ($residueTime < 0) {
|
||||
$overTime = Base::timeFormat(abs($residueTime));
|
||||
}
|
||||
$planTime = Base::timeDiff($startTime, $endTime);
|
||||
}
|
||||
$actualTime = $task->complete_at ? $totalTime : 0;//实际完成用时
|
||||
$datas[] = [
|
||||
$task->id,
|
||||
$task->parent_id ?: '-',
|
||||
Base::filterEmoji($task->project?->name) ?: '-',
|
||||
Base::filterEmoji($task->name),
|
||||
$task->start_at ?: '-',
|
||||
$task->end_at ?: '-',
|
||||
$task->complete_at ?: '-',
|
||||
$task->archived_at ?: '-',
|
||||
$planTime ?: '-',
|
||||
$actualTime ? Base::timeFormat($actualTime) : '-',
|
||||
$overTime,
|
||||
$developTime > 0? Base::timeFormat($developTime) : '-',
|
||||
$testTime > 0 ? Base::timeFormat($testTime) : '-',
|
||||
Base::filterEmoji(User::userid2nickname($task->ownerid)) . " (ID: {$task->ownerid})",
|
||||
Base::filterEmoji(User::userid2nickname($task->userid)) . " (ID: {$task->userid})",
|
||||
];
|
||||
}
|
||||
});
|
||||
//
|
||||
$fileName = User::userid2nickname($userid[0]) ?: $userid[0];
|
||||
if (count($userid) > 1) {
|
||||
$fileName .= "等" . count($userid) . "位成员";
|
||||
}
|
||||
$fileName .= '任务统计_' . Base::time() . '.xls';
|
||||
$filePath = "temp/task/export/" . date("Ym", Base::time());
|
||||
$res = BillExport::create()->setHeadings($headings)->setData($datas)->store($filePath . "/" . $fileName);
|
||||
if ($res != 1) {
|
||||
return Base::retError('导出失败,' . $fileName . '!');
|
||||
}
|
||||
$xlsPath = storage_path("app/" . $filePath . "/" . $fileName);
|
||||
$zipFile = "app/" . $filePath . "/" . Base::rightDelete($fileName, '.xls'). ".zip";
|
||||
$zipPath = storage_path($zipFile);
|
||||
if (file_exists($zipPath)) {
|
||||
Base::deleteDirAndFile($zipPath, true);
|
||||
}
|
||||
try {
|
||||
Madzipper::make($zipPath)->add($xlsPath)->close();
|
||||
} catch (\Exception) { }
|
||||
//
|
||||
if (file_exists($zipPath)) {
|
||||
$base64 = base64_encode(Base::array2string([
|
||||
'file' => $zipFile,
|
||||
]));
|
||||
Session::put('task::export:userid', $user->userid);
|
||||
return Base::retSuccess('success', [
|
||||
'size' => Base::twoFloat(filesize($zipPath) / 1024, true),
|
||||
'url' => Base::fillUrl('api/project/task/down?key=' . urlencode($base64)),
|
||||
]);
|
||||
} else {
|
||||
return Base::retError('打包失败,请稍后再试...');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/down 20. 导出任务(限管理员)
|
||||
*
|
||||
* @apiDescription 导出指定范围任务(已完成、未完成、已归档),返回下载地址,需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName task__down
|
||||
*
|
||||
* @apiParam {String} key 通过export接口得到的下载钥匙
|
||||
*
|
||||
* @apiSuccess {File} data 返回数据(直接下载文件)
|
||||
*/
|
||||
public function task__down()
|
||||
{
|
||||
$userid = Session::get('task::export:userid');
|
||||
if (empty($userid)) {
|
||||
return Base::ajaxError("请求已过期,请重新导出!", [], 0, 502);
|
||||
}
|
||||
//
|
||||
$array = Base::string2array(base64_decode(urldecode(Request::input('key'))));
|
||||
$file = $array['file'];
|
||||
if (empty($file) || !file_exists(storage_path($file))) {
|
||||
return Base::ajaxError("文件不存在!", [], 0, 502);
|
||||
}
|
||||
return response()->download(storage_path($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/one 21. 获取单个任务信息
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -992,7 +1225,8 @@ class ProjectController extends AbstractController
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
$archived = Request::input('archived', 'no');
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id, str_replace(['all', 'yes', 'no'], [null, false, true], $archived), false, ['taskUser', 'taskTag']);
|
||||
$isArchived = str_replace(['all', 'yes', 'no'], [null, false, true], $archived);
|
||||
$task = ProjectTask::userTask($task_id, $isArchived, true, false, ['taskUser', 'taskTag']);
|
||||
//
|
||||
$data = $task->toArray();
|
||||
$data['project_name'] = $task->project?->name;
|
||||
@@ -1001,7 +1235,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/content 20. 获取任务详细描述
|
||||
* @api {get} api/project/task/content 22. 获取任务详细描述
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1022,11 +1256,14 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id, null);
|
||||
//
|
||||
return Base::retSuccess('success', $task->content ?: json_decode('{}'));
|
||||
if (empty($task->content)) {
|
||||
return Base::retSuccess('success', json_decode('{}'));
|
||||
}
|
||||
return Base::retSuccess('success', $task->content->getContentInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/files 21. 获取任务文件列表
|
||||
* @api {get} api/project/task/files 23. 获取任务文件列表
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1051,7 +1288,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/filedelete 22. 删除任务文件
|
||||
* @api {get} api/project/task/filedelete 24. 删除任务文件
|
||||
*
|
||||
* @apiDescription 需要token身份(限:项目、任务负责人)
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1075,7 +1312,7 @@ class ProjectController extends AbstractController
|
||||
return Base::retError('文件不存在或已被删除');
|
||||
}
|
||||
//
|
||||
$task = ProjectTask::userTask($file->task_id, true, true);
|
||||
$task = ProjectTask::userTask($file->task_id, true, true, true);
|
||||
//
|
||||
$task->pushMsg('filedelete', $file);
|
||||
$file->delete();
|
||||
@@ -1084,14 +1321,17 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/filedetail 23. 获取任务文件详情
|
||||
* @api {get} api/project/task/filedetail 25. 获取任务文件详情
|
||||
*
|
||||
* @apiDescription 需要token身份(限:项目、任务负责人)
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName task__filedetail
|
||||
*
|
||||
* @apiParam {Number} file_id 文件ID
|
||||
* @apiParam {Number} file_id 文件ID
|
||||
* @apiParam {String} only_update_at 仅获取update_at字段
|
||||
* - no (默认)
|
||||
* - yes
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -1102,45 +1342,32 @@ class ProjectController extends AbstractController
|
||||
User::auth();
|
||||
//
|
||||
$file_id = intval(Request::input('file_id'));
|
||||
$only_update_at = Request::input('only_update_at', 'no');
|
||||
//
|
||||
$file = ProjectTaskFile::find($file_id);
|
||||
if (empty($file)) {
|
||||
return Base::retError("文件不存在");
|
||||
}
|
||||
//
|
||||
if ($only_update_at == 'yes') {
|
||||
return Base::retSuccess('success', [
|
||||
'id' => $file->id,
|
||||
'update_at' => Carbon::parse($file->updated_at)->toDateTimeString()
|
||||
]);
|
||||
}
|
||||
//
|
||||
$data = $file->toArray();
|
||||
$data['path'] = $file->getRawOriginal('path');
|
||||
//
|
||||
ProjectTask::userTask($file->task_id, true, true);
|
||||
ProjectTask::userTask($file->task_id, null);
|
||||
//
|
||||
$codeExt = ['txt'];
|
||||
$officeExt = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
|
||||
$localExt = ['jpg', 'jpeg', 'png', 'gif'];
|
||||
$filePath = public_path($data['path']);
|
||||
if (in_array($data['ext'], $codeExt) && $data['size'] < 2 * 1024 * 1024) {
|
||||
// 文本预览,限制2M内的文件
|
||||
$data['content'] = file_get_contents($filePath);
|
||||
$data['file_mode'] = 1;
|
||||
} elseif (in_array($data['ext'], $officeExt)) {
|
||||
// office预览
|
||||
$data['file_mode'] = 2;
|
||||
} else {
|
||||
// 其他预览
|
||||
if (in_array($data['ext'], $localExt)) {
|
||||
$url = Base::fillUrl($data['path']);
|
||||
} else {
|
||||
$url = 'http://' . env('APP_IPPR') . '.3/' . $data['path'];
|
||||
}
|
||||
$data['url'] = base64_encode($url);
|
||||
$data['file_mode'] = 3;
|
||||
}
|
||||
//
|
||||
return Base::retSuccess('success', $data);
|
||||
return Base::retSuccess('success', File::formatFileData($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/filedown 24. 下载任务文件
|
||||
* @api {get} api/project/task/filedown 26. 下载任务文件
|
||||
*
|
||||
* @apiDescription 需要token身份(限:项目、任务负责人)
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName task__filedown
|
||||
@@ -1163,7 +1390,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
//
|
||||
try {
|
||||
ProjectTask::userTask($file->task_id, true, true);
|
||||
ProjectTask::userTask($file->task_id, null);
|
||||
} catch (\Exception $e) {
|
||||
abort(403, $e->getMessage() ?: "This file not support download.");
|
||||
}
|
||||
@@ -1172,7 +1399,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/project/task/add 25. 添加任务
|
||||
* @api {post} api/project/task/add 27. 添加任务
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1243,7 +1470,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/addsub 26. 添加子任务
|
||||
* @api {get} api/project/task/addsub 28. 添加子任务
|
||||
*
|
||||
* @apiDescription 需要token身份(限:项目、任务负责人)
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1264,7 +1491,7 @@ class ProjectController extends AbstractController
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
$name = Request::input('name');
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id, true, true);
|
||||
$task = ProjectTask::userTask($task_id, true, true, true);
|
||||
if ($task->complete_at) {
|
||||
return Base::retError('主任务已完成无法添加子任务');
|
||||
}
|
||||
@@ -1283,7 +1510,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/project/task/update 27. 修改任务、子任务
|
||||
* @api {post} api/project/task/update 29. 修改任务、子任务
|
||||
*
|
||||
* @apiDescription 需要token身份(限:项目、任务负责人)
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1316,7 +1543,7 @@ class ProjectController extends AbstractController
|
||||
parse_str(Request::getContent(), $data);
|
||||
$task_id = intval($data['task_id']);
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id, true, 2);
|
||||
$task = ProjectTask::userTask($task_id, true, true, 2);
|
||||
// 更新任务
|
||||
$updateMarking = [];
|
||||
$task->updateTask($data, $updateMarking);
|
||||
@@ -1329,7 +1556,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/dialog 28. 创建/获取聊天室
|
||||
* @api {get} api/project/task/dialog 30. 创建/获取聊天室
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1376,7 +1603,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/archived 29. 归档任务
|
||||
* @api {get} api/project/task/archived 31. 归档任务
|
||||
*
|
||||
* @apiDescription 需要token身份(限:项目、任务负责人)
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1399,7 +1626,7 @@ class ProjectController extends AbstractController
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
$type = Request::input('type', 'add');
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id, $type == 'add', true);
|
||||
$task = ProjectTask::userTask($task_id, $type == 'add', true, true);
|
||||
//
|
||||
if ($task->parent_id > 0) {
|
||||
return Base::retError('子任务不支持此功能');
|
||||
@@ -1418,7 +1645,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/remove 30. 删除任务
|
||||
* @api {get} api/project/task/remove 32. 删除任务
|
||||
*
|
||||
* @apiDescription 需要token身份(限:项目、任务负责人)
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1426,6 +1653,9 @@ class ProjectController extends AbstractController
|
||||
* @apiName task__remove
|
||||
*
|
||||
* @apiParam {Number} task_id 任务ID
|
||||
* @apiParam {String} type
|
||||
* - recovery: 还原
|
||||
* - delete: 删除(默认)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -1436,15 +1666,20 @@ class ProjectController extends AbstractController
|
||||
User::auth();
|
||||
//
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
$type = Request::input('type', 'delete');
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id, null, true);
|
||||
//
|
||||
$task->deleteTask();
|
||||
return Base::retSuccess('删除成功', ['id' => $task->id]);
|
||||
$task = ProjectTask::userTask($task_id, null, $type !== 'recovery', true);
|
||||
if ($type == 'recovery') {
|
||||
$task->recoveryTask();
|
||||
return Base::retSuccess('操作成功', ['id' => $task->id]);
|
||||
} else {
|
||||
$task->deleteTask();
|
||||
return Base::retSuccess('删除成功', ['id' => $task->id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/resetfromlog 31. 根据日志重置任务
|
||||
* @api {get} api/project/task/resetfromlog 33. 根据日志重置任务
|
||||
*
|
||||
* @apiDescription 需要token身份(限:项目、任务负责人)
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1468,7 +1703,7 @@ class ProjectController extends AbstractController
|
||||
return Base::retError('记录不存在');
|
||||
}
|
||||
//
|
||||
$task = ProjectTask::userTask($projectLog->task_id, true, true);
|
||||
$task = ProjectTask::userTask($projectLog->task_id, true, true, true);
|
||||
//
|
||||
$record = $projectLog->record;
|
||||
if ($record['flow'] && is_array($record['flow'])) {
|
||||
@@ -1491,6 +1726,7 @@ class ProjectController extends AbstractController
|
||||
$task->updateTask($data, $updateMarking);
|
||||
//
|
||||
$data = ProjectTask::oneTask($task->id)->toArray();
|
||||
$data["flow_item_name"] = $newFlowItem->status . "|" . $newFlowItem->name;
|
||||
$data['update_marking'] = $updateMarking ?: json_decode('{}');
|
||||
$task->pushMsg('update', $data);
|
||||
//
|
||||
@@ -1502,7 +1738,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/flow 32. 任务工作流信息
|
||||
* @api {get} api/project/task/flow 34. 任务工作流信息
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1521,7 +1757,7 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
//
|
||||
$projectTask = ProjectTask::select(['id', 'project_id', 'complete_at', 'flow_item_id', 'flow_item_name'])->find($task_id);
|
||||
$projectTask = ProjectTask::select(['id', 'project_id', 'complete_at', 'flow_item_id', 'flow_item_name'])->withTrashed()->find($task_id);
|
||||
if (empty($projectTask)) {
|
||||
return Base::retError('任务不存在', [ 'task_id' => $task_id ], -4002);
|
||||
}
|
||||
@@ -1584,9 +1820,9 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/flow/list 33. 工作流列表
|
||||
* @api {get} api/project/flow/list 35. 工作流列表
|
||||
*
|
||||
* @apiDescription 需要token身份(限:项目负责人)
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName flow__list
|
||||
@@ -1603,14 +1839,14 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
//
|
||||
$project = Project::userProject($project_id, true, true);
|
||||
$project = Project::userProject($project_id, true);
|
||||
//
|
||||
$list = ProjectFlow::with(['ProjectFlowItem'])->whereProjectId($project->id)->get();
|
||||
return Base::retSuccess('success', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/project/flow/save 34. 保存工作流
|
||||
* @api {post} api/project/flow/save 36. 保存工作流
|
||||
*
|
||||
* @apiDescription 需要token身份(限:项目负责人)
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1640,99 +1876,11 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$project = Project::userProject($project_id, true, true);
|
||||
//
|
||||
return AbstractModel::transaction(function() use ($project, $flows) {
|
||||
$projectFlow = ProjectFlow::whereProjectId($project->id)->first();
|
||||
if (empty($projectFlow)) {
|
||||
$projectFlow = ProjectFlow::createInstance([
|
||||
'project_id' => $project->id,
|
||||
'name' => 'Default'
|
||||
]);
|
||||
if (!$projectFlow->save()) {
|
||||
throw new ApiException('工作流创建失败');
|
||||
}
|
||||
}
|
||||
//
|
||||
$ids = [];
|
||||
$idc = [];
|
||||
$hasStart = false;
|
||||
$hasEnd = false;
|
||||
foreach ($flows as $item) {
|
||||
$id = intval($item['id']);
|
||||
$turns = Base::arrayRetainInt($item['turns'] ?: [], true);
|
||||
$userids = Base::arrayRetainInt($item['userids'] ?: [], true);
|
||||
$usertype = trim($item['usertype']);
|
||||
$userlimit = intval($item['userlimit']);
|
||||
if ($usertype == 'replace' && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置流转模式时必须填写状态负责人");
|
||||
}
|
||||
if ($usertype == 'merge' && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置剔除模式时必须填写状态负责人");
|
||||
}
|
||||
if ($userlimit && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置限制负责人时必须填写状态负责人");
|
||||
}
|
||||
$flow = ProjectFlowItem::updateInsert([
|
||||
'id' => $id,
|
||||
'project_id' => $project->id,
|
||||
'flow_id' => $projectFlow->id,
|
||||
], [
|
||||
'name' => trim($item['name']),
|
||||
'status' => trim($item['status']),
|
||||
'sort' => intval($item['sort']),
|
||||
'turns' => $turns,
|
||||
'userids' => $userids,
|
||||
'usertype' => trim($item['usertype']),
|
||||
'userlimit' => $userlimit,
|
||||
]);
|
||||
if ($flow) {
|
||||
$ids[] = $flow->id;
|
||||
if ($flow->id != $id) {
|
||||
$idc[$id] = $flow->id;
|
||||
}
|
||||
if ($flow->status == 'start') {
|
||||
$hasStart = true;
|
||||
}
|
||||
if ($flow->status == 'end') {
|
||||
$hasEnd = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$hasStart) {
|
||||
throw new ApiException('至少需要1个开始状态');
|
||||
}
|
||||
if (!$hasEnd) {
|
||||
throw new ApiException('至少需要1个结束状态');
|
||||
}
|
||||
ProjectFlowItem::whereFlowId($projectFlow->id)->whereNotIn('id', $ids)->chunk(100, function($list) {
|
||||
foreach ($list as $item) {
|
||||
$item->deleteFlowItem();
|
||||
}
|
||||
});
|
||||
//
|
||||
$projectFlow = ProjectFlow::with(['projectFlowItem'])->whereProjectId($project->id)->find($projectFlow->id);
|
||||
$itemIds = $projectFlow->projectFlowItem->pluck('id')->toArray();
|
||||
foreach ($projectFlow->projectFlowItem as $item) {
|
||||
$turns = $item->turns;
|
||||
foreach ($idc as $oid => $nid) {
|
||||
if (in_array($oid, $turns)) {
|
||||
$turns = array_diff($turns, [$oid]);
|
||||
$turns[] = $nid;
|
||||
}
|
||||
}
|
||||
if (!in_array($item->id, $turns)) {
|
||||
$turns[] = $item->id;
|
||||
}
|
||||
$turns = array_values(array_filter(array_unique(array_intersect($turns, $itemIds))));
|
||||
sort($turns);
|
||||
$item->turns = $turns;
|
||||
ProjectFlowItem::whereId($item->id)->update([ 'turns' => Base::array2json($turns) ]);
|
||||
}
|
||||
return Base::retSuccess('保存成功', $projectFlow);
|
||||
});
|
||||
return Base::retSuccess('保存成功', $project->addFlow($flows));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/flow/delete 35. 删除工作流
|
||||
* @api {get} api/project/flow/delete 37. 删除工作流
|
||||
*
|
||||
* @apiDescription 需要token身份(限:项目负责人)
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1764,7 +1912,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/log/lists 36. 获取项目、任务日志
|
||||
* @api {get} api/project/log/lists 38. 获取项目、任务日志
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1814,4 +1962,34 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
return Base::retSuccess('success', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/top 39. 项目置顶
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName top
|
||||
*
|
||||
* @apiParam {Number} project_id 项目ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function top()
|
||||
{
|
||||
$user = User::auth();
|
||||
$projectId = intval(Request::input('project_id'));
|
||||
$projectUser = ProjectUser::whereUserid($user->userid)->whereProjectId($projectId)->first();
|
||||
if (!$projectUser) {
|
||||
return Base::retError("项目不存在");
|
||||
}
|
||||
$projectUser->top_at = $projectUser->top_at ? null : Carbon::now();
|
||||
$projectUser->save();
|
||||
return Base::retSuccess("success", [
|
||||
'id' => $projectUser->project_id,
|
||||
'top_at' => $projectUser->top_at?->toDateTimeString(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Models\AbstractModel;
|
||||
use App\Models\ProjectTask;
|
||||
use App\Models\Report;
|
||||
use App\Models\ReportReceive;
|
||||
@@ -31,8 +32,9 @@ class ReportController extends AbstractController
|
||||
* @apiGroup report
|
||||
* @apiName my
|
||||
*
|
||||
* @apiParam {String} [type] 汇报类型,weekly:周报,daily:日报
|
||||
* @apiParam {Array} [created_at] 汇报时间
|
||||
* @apiParam {Object} [keys] 搜索条件
|
||||
* - keys.type: 汇报类型,weekly:周报,daily:日报
|
||||
* - keys.created_at: 汇报时间
|
||||
* @apiParam {Number} [page] 当前页,默认:1
|
||||
* @apiParam {Number} [pagesize] 每页显示数量,默认:20,最大:50
|
||||
*
|
||||
@@ -43,17 +45,19 @@ class ReportController extends AbstractController
|
||||
public function my(): array
|
||||
{
|
||||
$user = User::auth();
|
||||
// 搜索当前用户
|
||||
//
|
||||
$builder = Report::with(['receivesUser'])->whereUserid($user->userid);
|
||||
$type = trim(Request::input('type'));
|
||||
$createAt = Request::input('created_at');
|
||||
in_array($type, [Report::WEEKLY, Report::DAILY]) && $builder->whereType($type);
|
||||
$whereArray = [];
|
||||
if (is_array($createAt)) {
|
||||
if ($createAt[0] > 0) $whereArray[] = ['created_at', '>=', date('Y-m-d H:i:s', Base::dayTimeF($createAt[0]))];
|
||||
if ($createAt[1] > 0) $whereArray[] = ['created_at', '<=', date('Y-m-d H:i:s', Base::dayTimeE($createAt[1]))];
|
||||
$keys = Request::input('keys');
|
||||
if (is_array($keys)) {
|
||||
if (in_array($keys['type'], [Report::WEEKLY, Report::DAILY])) {
|
||||
$builder->whereType($keys['type']);
|
||||
}
|
||||
if (is_array($keys['created_at'])) {
|
||||
if ($keys['created_at'][0] > 0) $builder->where('created_at', '>=', date('Y-m-d H:i:s', Base::dayTimeF($keys['created_at'][0])));
|
||||
if ($keys['created_at'][1] > 0) $builder->where('created_at', '<=', date('Y-m-d H:i:s', Base::dayTimeE($keys['created_at'][1])));
|
||||
}
|
||||
}
|
||||
$list = $builder->where($whereArray)->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
|
||||
$list = $builder->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
|
||||
return Base::retSuccess('success', $list);
|
||||
}
|
||||
|
||||
@@ -64,9 +68,10 @@ class ReportController extends AbstractController
|
||||
* @apiGroup report
|
||||
* @apiName receive
|
||||
*
|
||||
* @apiParam {String} [username] 会员名
|
||||
* @apiParam {String} [type] 汇报类型,weekly:周报,daily:日报
|
||||
* @apiParam {Array} [created_at] 汇报时间
|
||||
* @apiParam {Object} [keys] 搜索条件
|
||||
* - keys.key: 关键词
|
||||
* - keys.type: 汇报类型,weekly:周报,daily:日报
|
||||
* - keys.created_at: 汇报时间
|
||||
* @apiParam {Number} [page] 当前页,默认:1
|
||||
* @apiParam {Number} [pagesize] 每页显示数量,默认:20,最大:50
|
||||
*
|
||||
@@ -81,21 +86,24 @@ class ReportController extends AbstractController
|
||||
$builder->whereHas("receivesUser", function ($query) use ($user) {
|
||||
$query->where("report_receives.userid", $user->userid);
|
||||
});
|
||||
$type = trim(Request::input('type'));
|
||||
$createAt = Request::input('created_at');
|
||||
$username = trim(Request::input('username', ''));
|
||||
$builder->whereHas('sendUser', function ($query) use ($username) {
|
||||
if (!empty($username)) {
|
||||
$query->where('users.email', 'LIKE', '%' . $username . '%');
|
||||
$keys = Request::input('keys');
|
||||
if (is_array($keys)) {
|
||||
if ($keys['key']) {
|
||||
$builder->where(function($query) use ($keys) {
|
||||
$query->whereHas('sendUser', function ($q2) use ($keys) {
|
||||
$q2->where("users.email", "LIKE", "%{$keys['key']}%");
|
||||
})->orWhere("title", "LIKE", "%{$keys['key']}%");
|
||||
});
|
||||
}
|
||||
if (in_array($keys['type'], [Report::WEEKLY, Report::DAILY])) {
|
||||
$builder->whereType($keys['type']);
|
||||
}
|
||||
if (is_array($keys['created_at'])) {
|
||||
if ($keys['created_at'][0] > 0) $builder->where('created_at', '>=', date('Y-m-d H:i:s', Base::dayTimeF($keys['created_at'][0])));
|
||||
if ($keys['created_at'][1] > 0) $builder->where('created_at', '<=', date('Y-m-d H:i:s', Base::dayTimeE($keys['created_at'][1])));
|
||||
}
|
||||
});
|
||||
in_array($type, [Report::WEEKLY, Report::DAILY]) && $builder->whereType($type);
|
||||
$whereArray = [];
|
||||
if (is_array($createAt)) {
|
||||
if ($createAt[0] > 0) $whereArray[] = ['created_at', '>=', date('Y-m-d H:i:s', Base::dayTimeF($createAt[0]))];
|
||||
if ($createAt[1] > 0) $whereArray[] = ['created_at', '<=', date('Y-m-d H:i:s', Base::dayTimeE($createAt[1]))];
|
||||
}
|
||||
$list = $builder->where($whereArray)->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
|
||||
$list = $builder->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
|
||||
if ($list->items()) {
|
||||
foreach ($list->items() as $item) {
|
||||
$item->receive_time = ReportReceive::query()->whereRid($item["id"])->whereUserid($user->userid)->value("receive_time");
|
||||
@@ -173,7 +181,7 @@ class ReportController extends AbstractController
|
||||
}
|
||||
|
||||
// 在事务中运行
|
||||
Report::transaction(function () use ($input, $user) {
|
||||
return AbstractModel::transaction(function () use ($input, $user) {
|
||||
$id = $input["id"];
|
||||
if ($id) {
|
||||
// 编辑
|
||||
@@ -188,7 +196,7 @@ class ReportController extends AbstractController
|
||||
$sign = Report::generateSign($input["type"], $input["offset"]);
|
||||
// 检查唯一标识是否存在
|
||||
if (empty($input["id"])) {
|
||||
if (Report::query()->whereSign($sign)->count() > 0)
|
||||
if (Report::query()->whereSign($sign)->whereType($input["type"])->count() > 0)
|
||||
throw new ApiException("请勿重复提交工作汇报");
|
||||
}
|
||||
$report = Report::createInstance([
|
||||
@@ -224,8 +232,9 @@ class ReportController extends AbstractController
|
||||
];
|
||||
Task::deliver(new PushTask($params, false));
|
||||
}
|
||||
//
|
||||
return Base::retSuccess('保存成功', $report);
|
||||
});
|
||||
return Base::retSuccess('保存成功');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,7 +282,7 @@ class ReportController extends AbstractController
|
||||
}
|
||||
// 生成唯一标识
|
||||
$sign = Report::generateSign($type, 0, Carbon::instance($start_time));
|
||||
$one = Report::query()->whereSign($sign)->first();
|
||||
$one = Report::whereSign($sign)->whereType($type)->first();
|
||||
// 如果已经提交了相关汇报
|
||||
if ($one && $id > 0) {
|
||||
return Base::retSuccess('success', [
|
||||
@@ -283,7 +292,6 @@ class ReportController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
// 已完成的任务
|
||||
$completeContent = "";
|
||||
$complete_task = ProjectTask::query()
|
||||
@@ -362,6 +370,7 @@ class ReportController extends AbstractController
|
||||
*/
|
||||
public function detail(): array
|
||||
{
|
||||
$user = User::auth();
|
||||
$id = intval(trim(Request::input("id")));
|
||||
if (empty($id))
|
||||
return Base::retError("缺少ID参数");
|
||||
@@ -369,7 +378,6 @@ class ReportController extends AbstractController
|
||||
$one = Report::getOne($id);
|
||||
$one->type_val = $one->getRawOriginal("type");
|
||||
|
||||
$user = User::auth();
|
||||
// 标记为已读
|
||||
if (!empty($one->receivesUser)) {
|
||||
foreach ($one->receivesUser as $item) {
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use Guanguans\Notify\Factory;
|
||||
use Guanguans\Notify\Messages\EmailMessage;
|
||||
use Request;
|
||||
use Response;
|
||||
|
||||
@@ -25,7 +27,7 @@ class SystemController extends AbstractController
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - all: 获取所有(需要管理员权限)
|
||||
* - save: 保存设置(参数:reg、reg_invite、login_code、password_policy、project_invite、chat_nickname、auto_archived、archived_day)
|
||||
* - save: 保存设置(参数:['reg', 'reg_invite', 'login_code', 'password_policy', 'project_invite', 'chat_nickname', 'auto_archived', 'archived_day', 'start_home', 'home_footer'])
|
||||
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -41,7 +43,18 @@ class SystemController extends AbstractController
|
||||
User::auth('admin');
|
||||
$all = Request::input();
|
||||
foreach ($all AS $key => $value) {
|
||||
if (!in_array($key, ['reg', 'reg_invite', 'login_code', 'password_policy', 'project_invite', 'chat_nickname', 'auto_archived', 'archived_day'])) {
|
||||
if (!in_array($key, [
|
||||
'reg',
|
||||
'reg_invite',
|
||||
'login_code',
|
||||
'password_policy',
|
||||
'project_invite',
|
||||
'chat_nickname',
|
||||
'auto_archived',
|
||||
'archived_day',
|
||||
'start_home',
|
||||
'home_footer'
|
||||
])) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
}
|
||||
@@ -72,12 +85,68 @@ class SystemController extends AbstractController
|
||||
$setting['chat_nickname'] = $setting['chat_nickname'] ?: 'optional';
|
||||
$setting['auto_archived'] = $setting['auto_archived'] ?: 'close';
|
||||
$setting['archived_day'] = floatval($setting['archived_day']) ?: 7;
|
||||
$setting['start_home'] = $setting['start_home'] ?: 'close';
|
||||
//
|
||||
return Base::retSuccess('success', $setting ?: json_decode('{}'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/demo 02. 获取演示账号
|
||||
* @api {get} api/system/setting/email 02. 获取邮箱设置、保存邮箱设置(限管理员)
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName setting__email
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存设置(参数:['smtp_server', 'port', 'account', 'password', 'reg_verify', 'notice', 'task_remind_hours', 'task_remind_hours2'])
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function setting__email()
|
||||
{
|
||||
User::auth('admin');
|
||||
//
|
||||
$type = trim(Request::input('type'));
|
||||
if ($type == 'save') {
|
||||
if (env("SYSTEM_SETTING") == 'disabled') {
|
||||
return Base::retError('当前环境禁止修改');
|
||||
}
|
||||
$all = Request::input();
|
||||
foreach ($all as $key => $value) {
|
||||
if (!in_array($key, [
|
||||
'smtp_server',
|
||||
'port',
|
||||
'account',
|
||||
'password',
|
||||
'reg_verify',
|
||||
'notice',
|
||||
'task_remind_hours',
|
||||
'task_remind_hours2'
|
||||
])) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
}
|
||||
$setting = Base::setting('emailSetting', Base::newTrim($all));
|
||||
} else {
|
||||
$setting = Base::setting('emailSetting');
|
||||
}
|
||||
//
|
||||
$setting['smtp_server'] = $setting['smtp_server'] ?: '';
|
||||
$setting['port'] = $setting['port'] ?: '';
|
||||
$setting['account'] = $setting['account'] ?: '';
|
||||
$setting['password'] = $setting['password'] ?: '';
|
||||
$setting['reg_verify'] = $setting['reg_verify'] ?: 'close';
|
||||
$setting['notice'] = $setting['notice'] ?: 'open';
|
||||
$setting['task_remind_hours'] = floatval($setting['task_remind_hours']) ?: 0;
|
||||
$setting['task_remind_hours2'] = floatval($setting['task_remind_hours2']) ?: 0;
|
||||
//
|
||||
return Base::retSuccess('success', $setting ?: json_decode('{}'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/demo 03. 获取演示账号
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -101,12 +170,16 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/priority 03. 获取优先级、保存优先级
|
||||
* @api {post} api/system/priority 04. 任务优先级
|
||||
*
|
||||
* @apiDescription 获取任务优先级、保存任务优先级
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName priority
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存(限管理员)
|
||||
* @apiParam {Array} list 优先级数据,格式:[{name,color,days,priority}]
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
@@ -146,7 +219,54 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/info 04. 获取终端详细信息
|
||||
* @api {post} api/system/column/template 05. 创建项目模板
|
||||
*
|
||||
* @apiDescription 获取创建项目模板、保存创建项目模板
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName column__template
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存(限管理员)
|
||||
* @apiParam {Array} list 优先级数据,格式:[{name,columns}]
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function column__template()
|
||||
{
|
||||
$type = trim(Request::input('type'));
|
||||
if ($type == 'save') {
|
||||
User::auth('admin');
|
||||
$list = Base::getPostValue('list');
|
||||
$array = [];
|
||||
if (empty($list) || !is_array($list)) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
foreach ($list AS $item) {
|
||||
if (empty($item['name']) || empty($item['columns'])) {
|
||||
continue;
|
||||
}
|
||||
$array[] = [
|
||||
'name' => $item['name'],
|
||||
'columns' => array_values(array_filter(array_unique(explode(",", $item['columns']))))
|
||||
];
|
||||
}
|
||||
if (empty($array)) {
|
||||
return Base::retError('参数为空');
|
||||
}
|
||||
$setting = Base::setting('columnTemplate', $array);
|
||||
} else {
|
||||
$setting = Base::setting('columnTemplate');
|
||||
}
|
||||
//
|
||||
return Base::retSuccess('success', $setting);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/info 06. 获取终端详细信息
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -175,7 +295,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ip 05. 获取IP地址
|
||||
* @api {get} api/system/get/ip 07. 获取IP地址
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -190,7 +310,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/cnip 06. 是否中国IP地址
|
||||
* @api {get} api/system/get/cnip 08. 是否中国IP地址
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -207,7 +327,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ipgcj02 07. 获取IP地址经纬度
|
||||
* @api {get} api/system/get/ipgcj02 09. 获取IP地址经纬度
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -224,7 +344,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ipinfo 08. 获取IP地址详细信息
|
||||
* @api {get} api/system/get/ipinfo 10. 获取IP地址详细信息
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -241,7 +361,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/imgupload 09. 上传图片
|
||||
* @api {post} api/system/imgupload 11. 上传图片
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -264,7 +384,7 @@ class SystemController extends AbstractController
|
||||
if (!$scale[0] && !$scale[1]) {
|
||||
$scale = [2160, 4160, -1];
|
||||
}
|
||||
$path = "uploads/picture/" . User::userid() . "/" . date("Ym") . "/";
|
||||
$path = "uploads/user/picture/" . User::userid() . "/" . date("Ym") . "/";
|
||||
$image64 = trim(Base::getPostValue('image64'));
|
||||
$fileName = trim(Base::getPostValue('filename'));
|
||||
if ($image64) {
|
||||
@@ -291,7 +411,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/imgview 10. 浏览图片空间
|
||||
* @api {get} api/system/get/imgview 12. 浏览图片空间
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -309,7 +429,7 @@ class SystemController extends AbstractController
|
||||
if (User::userid() === 0) {
|
||||
return Base::retError('身份失效,等重新登录');
|
||||
}
|
||||
$publicPath = "uploads/picture/" . User::userid() . "/";
|
||||
$publicPath = "uploads/user/picture/" . User::userid() . "/";
|
||||
$dirPath = public_path($publicPath);
|
||||
$dirs = $files = [];
|
||||
//
|
||||
@@ -387,7 +507,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/fileupload 11. 上传文件
|
||||
* @api {post} api/system/fileupload 13. 上传文件
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -407,7 +527,7 @@ class SystemController extends AbstractController
|
||||
if (User::userid() === 0) {
|
||||
return Base::retError('身份失效,等重新登录');
|
||||
}
|
||||
$path = "uploads/files/" . User::userid() . "/" . date("Ym") . "/";
|
||||
$path = "uploads/user/file/" . User::userid() . "/" . date("Ym") . "/";
|
||||
$image64 = trim(Base::getPostValue('image64'));
|
||||
$fileName = trim(Base::getPostValue('filename'));
|
||||
if ($image64) {
|
||||
@@ -427,4 +547,66 @@ class SystemController extends AbstractController
|
||||
//
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/starthome 14. 启动首页设置信息
|
||||
*
|
||||
* @apiDescription 用于判断注册是否需要启动首页
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName get__starthome
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function get__starthome()
|
||||
{
|
||||
return Base::retSuccess('success', [
|
||||
'need_start' => Base::settingFind('system', 'start_home') == 'open',
|
||||
'home_footer' => Base::settingFind('system', 'home_footer')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/email/check 15. 邮件发送测试(限管理员)
|
||||
*
|
||||
* @apiDescription 测试配置邮箱是否能发送邮件
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName email__check
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function email__check()
|
||||
{
|
||||
User::auth('admin');
|
||||
//
|
||||
$all = Request::input();
|
||||
if (!Base::isEmail($all['to'])) {
|
||||
return Base::retError('请输入正确的收件人地址');
|
||||
}
|
||||
try {
|
||||
Factory::mailer()
|
||||
->setDsn("smtp://{$all['account']}:{$all['password']}@{$all['smtp_server']}:{$all['port']}?verify_peer=0")
|
||||
->setMessage(EmailMessage::create()
|
||||
->from(env('APP_NAME', 'Task') . " <{$all['account']}>")
|
||||
->to($all['to'])
|
||||
->subject('Mail sending test')
|
||||
->html('<p>收到此电子邮件意味着您的邮箱配置正确。</p><p>Receiving this email means that your mailbox is configured correctly.</p>'))
|
||||
->send();
|
||||
return Base::retSuccess('成功发送');
|
||||
} catch (\Exception $e) {
|
||||
// 一般是请求超时
|
||||
if (str_contains($e->getMessage(), "Timed Out")) {
|
||||
return Base::retError("language.TimedOut");
|
||||
} elseif ($e->getCode() === 550) {
|
||||
return Base::retError('邮件内容被拒绝,请检查邮箱是否开启接收功能');
|
||||
} else {
|
||||
return Base::retError($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\UserEmailVerification;
|
||||
use App\Module\Base;
|
||||
use Arr;
|
||||
use Cache;
|
||||
@@ -42,6 +43,7 @@ class UsersController extends AbstractController
|
||||
$type = trim(Request::input('type'));
|
||||
$email = trim(Request::input('email'));
|
||||
$password = trim(Request::input('password'));
|
||||
$isRegVerify = Base::settingFind('emailSetting', 'reg_verify') === 'open';
|
||||
if ($type == 'reg') {
|
||||
$setting = Base::setting('system');
|
||||
if ($setting['reg'] == 'close') {
|
||||
@@ -53,6 +55,10 @@ class UsersController extends AbstractController
|
||||
}
|
||||
}
|
||||
$user = User::reg($email, $password);
|
||||
if ($isRegVerify) {
|
||||
UserEmailVerification::userEmailSend($user);
|
||||
return Base::retError('注册成功,请验证邮箱后登录', ['code' => 'email']);
|
||||
}
|
||||
} else {
|
||||
$needCode = !Base::isError(User::needCode($email));
|
||||
if ($needCode) {
|
||||
@@ -68,7 +74,7 @@ class UsersController extends AbstractController
|
||||
$retError = function ($msg) use ($email) {
|
||||
Cache::forever("code::" . $email, "need");
|
||||
$needCode = !Base::isError(User::needCode($email));
|
||||
$needData = [ 'code' => $needCode ? 'need' : 'no' ];
|
||||
$needData = ['code' => $needCode ? 'need' : 'no'];
|
||||
return Base::retError($msg, $needData);
|
||||
};
|
||||
$user = User::whereEmail($email)->first();
|
||||
@@ -83,6 +89,10 @@ class UsersController extends AbstractController
|
||||
return $retError('帐号已停用...');
|
||||
}
|
||||
Cache::forget("code::" . $email);
|
||||
if ($isRegVerify && $user->email_verity === 0) {
|
||||
UserEmailVerification::userEmailSend($user);
|
||||
return Base::retError('您还没有验证邮箱,请先登录邮箱通过验证邮件验证邮箱', ['code' => 'email']);
|
||||
}
|
||||
}
|
||||
//
|
||||
$array = [
|
||||
@@ -416,9 +426,12 @@ class UsersController extends AbstractController
|
||||
* @apiName lists
|
||||
*
|
||||
* @apiParam {Object} [keys] 搜索条件
|
||||
* - keys.key 邮箱/昵称/职位(赋值后keys.email、keys.nickname、keys.profession失效)
|
||||
* - keys.email 邮箱
|
||||
* - keys.nickname 昵称
|
||||
* - keys.profession 职位
|
||||
* - keys.identity 身份(如:admin、noadmin)
|
||||
* - keys.email_verity 邮箱是否认证(如:yes、no)
|
||||
* @apiParam {Number} [page] 当前页,默认:1
|
||||
* @apiParam {Number} [pagesize] 每页显示数量,默认:20,最大:50
|
||||
*
|
||||
@@ -434,14 +447,26 @@ class UsersController extends AbstractController
|
||||
//
|
||||
$keys = Request::input('keys');
|
||||
if (is_array($keys)) {
|
||||
if ($keys['email']) {
|
||||
$builder->where("email", "like", "%{$keys['email']}%");
|
||||
}
|
||||
if ($keys['nickname']) {
|
||||
$builder->where("nickname", "like", "%{$keys['nickname']}%");
|
||||
}
|
||||
if ($keys['profession']) {
|
||||
$builder->where("profession", "like", "%{$keys['profession']}%");
|
||||
if ($keys['key']) {
|
||||
if (str_contains($keys['key'], "@")) {
|
||||
$builder->where("email", "like", "%{$keys['key']}%");
|
||||
} else {
|
||||
$builder->where(function($query) use ($keys) {
|
||||
$query->where("email", "like", "%{$keys['key']}%")
|
||||
->orWhere("nickname", "like", "%{$keys['key']}%")
|
||||
->orWhere("profession", "like", "%{$keys['key']}%");
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if ($keys['email']) {
|
||||
$builder->where("email", "like", "%{$keys['email']}%");
|
||||
}
|
||||
if ($keys['nickname']) {
|
||||
$builder->where("nickname", "like", "%{$keys['nickname']}%");
|
||||
}
|
||||
if ($keys['profession']) {
|
||||
$builder->where("profession", "like", "%{$keys['profession']}%");
|
||||
}
|
||||
}
|
||||
if ($keys['identity']) {
|
||||
if (Base::leftExists($keys['identity'], "no")) {
|
||||
@@ -450,6 +475,11 @@ class UsersController extends AbstractController
|
||||
$builder->where("identity", "like", "%,{$keys['identity']},%");
|
||||
}
|
||||
}
|
||||
if ($keys['email_verity'] === 'yes') {
|
||||
$builder->whereEmailVerity(1);
|
||||
} elseif ($keys['email_verity'] === 'no') {
|
||||
$builder->whereEmailVerity(0);
|
||||
}
|
||||
}
|
||||
$list = $builder->orderByDesc('userid')->paginate(Base::getPaginate(50, 20));
|
||||
//
|
||||
@@ -559,4 +589,52 @@ class UsersController extends AbstractController
|
||||
//
|
||||
return Base::retSuccess('修改成功', $userInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/users/email/verification 13. 邮箱验证
|
||||
*
|
||||
* @apiDescription 不需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup users
|
||||
* @apiName email__verification
|
||||
*
|
||||
* @apiParam {String} code 验证参数
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据(同"获取我的信息"接口)
|
||||
*/
|
||||
public function email__verification()
|
||||
{
|
||||
$data = Request::input();
|
||||
// 表单验证
|
||||
Base::validator($data, [
|
||||
'code.required' => '验证码不能为空',
|
||||
]);
|
||||
//
|
||||
$res = UserEmailVerification::whereCode($data['code'])->first();
|
||||
if (empty($res)) {
|
||||
return Base::retError('无效连接,请重新注册');
|
||||
}
|
||||
|
||||
// 如果已经校验过
|
||||
if (intval($res->status) === 1)
|
||||
return Base::retError('链接已经使用过', ['code' => 2]);
|
||||
|
||||
$oldTime = Carbon::parse($res->created_at)->timestamp;
|
||||
$time = Base::Time();
|
||||
|
||||
// 30分钟失效
|
||||
if (abs($time - $oldTime) > 1800) {
|
||||
return Base::retError("链接已失效,请重新登录/注册");
|
||||
}
|
||||
UserEmailVerification::whereCode($data['code'])->update([
|
||||
'status' => 1
|
||||
]);
|
||||
User::whereUserid($res->userid)->update([
|
||||
'email_verity' => 1
|
||||
]);
|
||||
|
||||
return Base::retSuccess('绑定邮箱成功');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@ namespace App\Http\Controllers;
|
||||
use App\Module\Base;
|
||||
use App\Tasks\AutoArchivedTask;
|
||||
use App\Tasks\DeleteTmpTask;
|
||||
use App\Tasks\OverdueRemindEmailTask;
|
||||
use Arr;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Redirect;
|
||||
use Request;
|
||||
|
||||
|
||||
/**
|
||||
@@ -34,7 +37,41 @@ class IndexController extends InvokeController
|
||||
*/
|
||||
public function main()
|
||||
{
|
||||
return view('main', ['version' => Base::getVersion()]);
|
||||
$hash = 'no';
|
||||
$path = public_path('js/hash');
|
||||
if (file_exists($path)) {
|
||||
$hash = trim(file_get_contents(public_path('js/hash')));
|
||||
if (strlen($hash) > 16) {
|
||||
$hash = 'long';
|
||||
}
|
||||
}
|
||||
return view('main', [
|
||||
'version' => Base::getVersion(),
|
||||
'hash' => $hash
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取版本号
|
||||
* @return array
|
||||
*/
|
||||
public function version()
|
||||
{
|
||||
$url = url('');
|
||||
$package = Base::getPackage();
|
||||
$array = [
|
||||
'version' => Base::getVersion(),
|
||||
'publish' => Arr::get($package, 'app.0.publish'),
|
||||
];
|
||||
if (is_array($package['app'])) {
|
||||
foreach ($package['app'] as $item) {
|
||||
if (is_array($item['publish']) && Base::hostContrast($url, $item['url'])) {
|
||||
$array['publish'] = $item['publish'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,10 +98,63 @@ class IndexController extends InvokeController
|
||||
Task::deliver(new DeleteTmpTask('tmp', 24));
|
||||
// 自动归档任务
|
||||
Task::deliver(new AutoArchivedTask());
|
||||
// 任务到期邮件提醒
|
||||
Task::deliver(new OverdueRemindEmailTask());
|
||||
|
||||
return "success";
|
||||
}
|
||||
|
||||
/**
|
||||
* 桌面客户端发布
|
||||
*/
|
||||
public function desktop__publish($name = '')
|
||||
{
|
||||
$latestFile = public_path("uploads/desktop/latest");
|
||||
$genericVersion = Request::header('generic-version');
|
||||
// 上传
|
||||
if (preg_match("/^\d+\.\d+\.\d+$/", $genericVersion)) {
|
||||
$genericPath = "uploads/desktop/" . $genericVersion . "/";
|
||||
$res = Base::upload([
|
||||
"file" => Request::file('file'),
|
||||
"type" => 'desktop',
|
||||
"path" => $genericPath,
|
||||
"fileName" => true
|
||||
]);
|
||||
if (Base::isSuccess($res)) {
|
||||
file_put_contents($latestFile, $genericVersion);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
// 列表
|
||||
if (preg_match("/^\d+\.\d+\.\d+$/", $name)) {
|
||||
$path = "uploads/desktop/{$name}";
|
||||
$dirPath = public_path($path);
|
||||
$lists = Base::readDir($dirPath);
|
||||
$files = [];
|
||||
foreach ($lists as $file) {
|
||||
$fileName = Base::leftDelete($file, $dirPath);
|
||||
$files[] = [
|
||||
'name' => substr($fileName, 1),
|
||||
'time' => date("Y-m-d H:i:s", fileatime($file)),
|
||||
'size' => Base::readableBytes(filesize($file)),
|
||||
'url' => Base::fillUrl($path . $fileName),
|
||||
];
|
||||
}
|
||||
return view('desktop', ['version' => $name, 'files' => $files]);
|
||||
}
|
||||
// 下载
|
||||
if ($name && file_exists($latestFile)) {
|
||||
$genericVersion = file_get_contents($latestFile);
|
||||
if (preg_match("/^\d+\.\d+\.\d+$/", $genericVersion)) {
|
||||
$filePath = public_path("uploads/desktop/{$genericVersion}/{$name}");
|
||||
if (file_exists($filePath)) {
|
||||
return response()->download($filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return abort(404);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取所有中文
|
||||
* @return array|string
|
||||
|
||||
@@ -21,6 +21,9 @@ class VerifyCsrfToken extends Middleware
|
||||
// 保存任务优先级
|
||||
'api/system/priority/',
|
||||
|
||||
// 保存创建项目列表模板
|
||||
'api/system/column/template/',
|
||||
|
||||
// 添加任务
|
||||
'api/project/task/add/',
|
||||
|
||||
@@ -30,6 +33,9 @@ class VerifyCsrfToken extends Middleware
|
||||
// 修改任务
|
||||
'api/project/task/update/',
|
||||
|
||||
// 聊天发文本
|
||||
'api/dialog/msg/sendtext/',
|
||||
|
||||
// 聊天发文件
|
||||
'api/dialog/msg/sendfile/',
|
||||
|
||||
@@ -44,5 +50,8 @@ class VerifyCsrfToken extends Middleware
|
||||
|
||||
// 保存汇报
|
||||
'api/report/store/',
|
||||
|
||||
// 发布桌面端
|
||||
'desktop/publish/',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* App\Model\AbstractModel
|
||||
* App\Models\AbstractModel
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel newQuery()
|
||||
@@ -153,9 +153,10 @@ class AbstractModel extends Model
|
||||
* @param $where
|
||||
* @param array $update 存在时更新的内容
|
||||
* @param array $insert 不存在时插入的内容,如果没有则插入更新内容
|
||||
* @param bool $isInsert 是否是插入数据
|
||||
* @return AbstractModel|\Illuminate\Database\Eloquent\Builder|Model|object|static|null
|
||||
*/
|
||||
public static function updateInsert($where, $update = [], $insert = [])
|
||||
public static function updateInsert($where, $update = [], $insert = [], &$isInsert = true)
|
||||
{
|
||||
$row = static::where($where)->first();
|
||||
if (empty($row)) {
|
||||
@@ -165,8 +166,10 @@ class AbstractModel extends Model
|
||||
unset($array[$row->primaryKey]);
|
||||
}
|
||||
$row->updateInstance($array);
|
||||
$isInsert = true;
|
||||
} elseif ($update) {
|
||||
$row->updateInstance($update);
|
||||
$isInsert = false;
|
||||
}
|
||||
if (!$row->save()) {
|
||||
return null;
|
||||
|
||||
@@ -13,6 +13,7 @@ use Request;
|
||||
* App\Models\File
|
||||
*
|
||||
* @property int $id
|
||||
* @property string|null $pids 上级ID递归
|
||||
* @property int|null $pid 上级ID
|
||||
* @property int|null $cid 复制ID
|
||||
* @property string|null $name 名称
|
||||
@@ -37,6 +38,7 @@ use Request;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File wherePid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File wherePids($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereShare($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereSize($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereType($value)
|
||||
@@ -50,6 +52,46 @@ class File extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* 文件文件
|
||||
*/
|
||||
const codeExt = [
|
||||
'txt',
|
||||
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css',
|
||||
'dockerfile', 'go', 'golang', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
|
||||
'ocamlmakefile', 'make', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php',
|
||||
'inc', 'phtml', 'shtml', 'php3', 'php4', 'php5', 'phps', 'phpt', 'aw', 'ctp', 'module', 'ps1', 'py', 'r', 'rb', 'ru', 'gemspec', 'rake', 'guardfile', 'rakefile',
|
||||
'gemfile', 'rs', 'sass', 'scss', 'sh', 'bash', 'bashrc', 'sql', 'sqlserver', 'swift', 'ts', 'typescript', 'str', 'vbs', 'vb', 'v', 'vh', 'sv', 'svh', 'xml',
|
||||
'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
|
||||
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx'
|
||||
];
|
||||
|
||||
/**
|
||||
* office文件
|
||||
*/
|
||||
const officeExt = [
|
||||
'doc', 'docx',
|
||||
'xls', 'xlsx',
|
||||
'ppt', 'pptx',
|
||||
];
|
||||
|
||||
/**
|
||||
* 图片文件
|
||||
*/
|
||||
const imageExt = [
|
||||
'jpg', 'jpeg', 'png', 'gif', 'bmp'
|
||||
];
|
||||
|
||||
/**
|
||||
* 本地媒体文件
|
||||
*/
|
||||
const localExt = [
|
||||
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw',
|
||||
'tif', 'tiff',
|
||||
'mp3', 'wav', 'mp4', 'flv',
|
||||
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm',
|
||||
];
|
||||
|
||||
/**
|
||||
* 是否有访问权限
|
||||
* @param $userid
|
||||
@@ -123,7 +165,7 @@ class File extends AbstractModel
|
||||
* @param $share
|
||||
* @return bool
|
||||
*/
|
||||
public function setShare($share = null)
|
||||
public function updataShare($share = null)
|
||||
{
|
||||
if ($share === null) {
|
||||
$share = FileUser::whereFileId($this->id)->count() == 0 ? 0 : 1;
|
||||
@@ -132,10 +174,13 @@ class File extends AbstractModel
|
||||
AbstractModel::transaction(function () use ($share) {
|
||||
$this->share = $share;
|
||||
$this->save();
|
||||
if ($share === 0) {
|
||||
FileUser::deleteFileAll($this->id, $this->userid);
|
||||
}
|
||||
$list = self::wherePid($this->id)->get();
|
||||
if ($list->isNotEmpty()) {
|
||||
foreach ($list as $item) {
|
||||
$item->setShare(0);
|
||||
$item->updataShare(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -143,6 +188,40 @@ class File extends AbstractModel
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前更新pids
|
||||
* @return bool
|
||||
*/
|
||||
public function saveBeforePids()
|
||||
{
|
||||
$pid = $this->pid;
|
||||
$array = [];
|
||||
while ($pid > 0) {
|
||||
$array[] = $pid;
|
||||
$pid = intval(self::whereId($pid)->value('pid'));
|
||||
}
|
||||
$opids = $this->pids;
|
||||
if ($array) {
|
||||
$array = array_values(array_reverse($array));
|
||||
$this->pids = ',' . implode(',', $array) . ',';
|
||||
} else {
|
||||
$this->pids = '';
|
||||
}
|
||||
if (!$this->save()) {
|
||||
return false;
|
||||
}
|
||||
// 更新子文件(夹)
|
||||
if ($opids != $this->pids) {
|
||||
self::wherePid($this->id)->chunkById(100, function ($lists) {
|
||||
/** @var self $item */
|
||||
foreach ($lists as $item) {
|
||||
$item->saveBeforePids();
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历删除文件(夹)
|
||||
* @return bool
|
||||
@@ -152,8 +231,7 @@ class File extends AbstractModel
|
||||
AbstractModel::transaction(function () {
|
||||
$this->delete();
|
||||
$this->pushMsg('delete');
|
||||
FileLink::whereFileId($this->id)->delete();
|
||||
FileUser::whereFileId($this->id)->delete();
|
||||
FileUser::deleteFileAll($this->id);
|
||||
FileContent::whereFid($this->id)->delete();
|
||||
$list = self::wherePid($this->id)->get();
|
||||
if ($list->isNotEmpty()) {
|
||||
@@ -214,6 +292,21 @@ class File extends AbstractModel
|
||||
Task::deliver($task);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理返回图片地址
|
||||
* @param $item
|
||||
* @return void
|
||||
*/
|
||||
public static function handleImageUrl(&$item)
|
||||
{
|
||||
if (in_array($item['ext'], self::imageExt) ) {
|
||||
$content = Base::json2array(FileContent::whereFid($item['id'])->orderByDesc('id')->value('content'));
|
||||
if ($content) {
|
||||
$item['image_url'] = Base::fillUrl($content['url']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件并检测权限
|
||||
* @param $id
|
||||
@@ -232,11 +325,84 @@ class File extends AbstractModel
|
||||
if ($permission < $limit) {
|
||||
$msg = match ($limit) {
|
||||
1000 => '仅限所有者或创建者操作',
|
||||
1 => '没有读写权限',
|
||||
default => '没有访问权限',
|
||||
1 => '没有修改写入权限',
|
||||
default => '没有查看访问权限',
|
||||
};
|
||||
throw new ApiException($msg);
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化内容数据
|
||||
* @param array $data [path, size, ext, name]
|
||||
* @return array
|
||||
*/
|
||||
public static function formatFileData(array $data)
|
||||
{
|
||||
$filePath = $data['path'];
|
||||
$fileSize = $data['size'];
|
||||
$fileExt = $data['ext'];
|
||||
$fileDotExt = '.' . $fileExt;
|
||||
$fileName = Base::rightDelete($data['name'], $fileDotExt) . $fileDotExt;
|
||||
$publicPath = public_path($filePath);
|
||||
//
|
||||
switch ($fileExt) {
|
||||
case 'md':
|
||||
case 'text':
|
||||
// 文本
|
||||
$data['content'] = [
|
||||
'type' => $fileExt,
|
||||
'content' => file_get_contents($publicPath) ?: 'Content deleted',
|
||||
];
|
||||
$data['file_mode'] = $fileExt;
|
||||
break;
|
||||
|
||||
case 'drawio':
|
||||
// 图表
|
||||
$data['content'] = [
|
||||
'xml' => file_get_contents($publicPath)
|
||||
];
|
||||
$data['file_mode'] = $fileExt;
|
||||
break;
|
||||
|
||||
case 'mind':
|
||||
// 思维导图
|
||||
$data['content'] = Base::json2array(file_get_contents($publicPath));
|
||||
$data['file_mode'] = $fileExt;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (in_array($fileExt, self::codeExt) && $fileSize < 2 * 1024 * 1024)
|
||||
{
|
||||
// 文本预览,限制2M内的文件
|
||||
$data['content'] = file_get_contents($publicPath) ?: 'Content deleted';
|
||||
$data['file_mode'] = 'code';
|
||||
}
|
||||
elseif (in_array($fileExt, File::officeExt))
|
||||
{
|
||||
// office预览
|
||||
$data['content'] = '';
|
||||
$data['file_mode'] = 'office';
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其他预览
|
||||
if (in_array($fileExt, File::localExt)) {
|
||||
$url = Base::fillUrl($filePath);
|
||||
} else {
|
||||
$url = 'http://' . env('APP_IPPR') . '.3/' . $filePath;
|
||||
}
|
||||
$data['content'] = [
|
||||
'preview' => true,
|
||||
'url' => base64_encode(Base::urlAddparameter($url, [
|
||||
'fullfilename' => $fileName
|
||||
])),
|
||||
];
|
||||
$data['file_mode'] = 'preview';
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class FileContent extends AbstractModel
|
||||
if (empty($content)) {
|
||||
$content = match ($file->type) {
|
||||
'document' => [
|
||||
"type" => "md",
|
||||
"type" => $file->ext,
|
||||
"content" => "",
|
||||
],
|
||||
default => json_decode('{}'),
|
||||
@@ -69,24 +69,20 @@ class FileContent extends AbstractModel
|
||||
abort(403, "This file is empty.");
|
||||
}
|
||||
} else {
|
||||
$content['preview'] = false;
|
||||
$path = $content['url'];
|
||||
if ($file->ext) {
|
||||
$filePath = public_path($content['url']);
|
||||
if (in_array($file->type, ['txt', 'code']) && $file->size < 2 * 1024 * 1024) {
|
||||
// 支持编辑,限制2M内的文件
|
||||
$content['content'] = file_get_contents($filePath);
|
||||
} else {
|
||||
// 支持预览
|
||||
if (in_array($file->type, ['picture', 'image', 'tif', 'media'])) {
|
||||
$url = Base::fillUrl($content['url']);
|
||||
} else {
|
||||
$url = 'http://' . env('APP_IPPR') . '.3/' . $content['url'];
|
||||
}
|
||||
$content['url'] = base64_encode($url);
|
||||
$content['preview'] = true;
|
||||
}
|
||||
$res = File::formatFileData([
|
||||
'path' => $path,
|
||||
'ext' => $file->ext,
|
||||
'size' => $file->size,
|
||||
'name' => $file->name,
|
||||
]);
|
||||
$content = $res['content'];
|
||||
} else {
|
||||
$content['preview'] = false;
|
||||
}
|
||||
if ($download) {
|
||||
$filePath = public_path($path);
|
||||
if (isset($filePath)) {
|
||||
return Response::download($filePath, $name);
|
||||
} else {
|
||||
|
||||
@@ -6,9 +6,10 @@ namespace App\Models;
|
||||
* App\Models\FileLink
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $file_id 项目ID
|
||||
* @property int|null $file_id 文件ID
|
||||
* @property int|null $num 累计访问
|
||||
* @property string|null $code 链接码
|
||||
* @property int|null $userid 会员ID
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\File|null $file
|
||||
@@ -21,6 +22,7 @@ namespace App\Models;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereNum($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class FileLink extends AbstractModel
|
||||
|
||||
@@ -25,4 +25,34 @@ namespace App\Models;
|
||||
*/
|
||||
class FileUser extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* 删除所有共享成员(同时删除成员分享的链接)
|
||||
* @param $file_id
|
||||
* @param int $retain_link_userid 保留指定会员的链接
|
||||
* @return mixed
|
||||
*/
|
||||
public static function deleteFileAll($file_id, $retain_link_userid = 0)
|
||||
{
|
||||
return AbstractModel::transaction(function() use ($retain_link_userid, $file_id) {
|
||||
if ($retain_link_userid > 0) {
|
||||
FileLink::whereFileId($file_id)->where('userid', '!=', $retain_link_userid)->delete();
|
||||
} else {
|
||||
FileLink::whereFileId($file_id)->delete();
|
||||
}
|
||||
FileUser::whereFileId($file_id)->delete();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 删除指定共享成员(同时删除成员分享的链接)
|
||||
* @param $file_id
|
||||
* @param $userid
|
||||
* @return mixed
|
||||
*/
|
||||
public static function deleteFileUser($file_id, $userid)
|
||||
{
|
||||
return AbstractModel::transaction(function() use ($userid, $file_id) {
|
||||
FileLink::whereFileId($file_id)->whereUserid($userid)->delete();
|
||||
return self::whereFileId($file_id)->whereUserid($userid)->delete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use App\Tasks\PushTask;
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
@@ -112,6 +113,7 @@ class Project extends AbstractModel
|
||||
->select([
|
||||
'projects.*',
|
||||
'project_users.owner',
|
||||
'project_users.top_at',
|
||||
])
|
||||
->leftJoin('project_users', function ($leftJoin) use ($userid) {
|
||||
$leftJoin
|
||||
@@ -135,6 +137,7 @@ class Project extends AbstractModel
|
||||
->select([
|
||||
'projects.*',
|
||||
'project_users.owner',
|
||||
'project_users.top_at',
|
||||
])
|
||||
->join('project_users', 'projects.id', '=', 'project_users.project_id')
|
||||
->where('project_users.userid', $userid);
|
||||
@@ -211,7 +214,7 @@ class Project extends AbstractModel
|
||||
*/
|
||||
public function relationUserids()
|
||||
{
|
||||
return $this->projectUser->pluck('userid')->toArray();
|
||||
return ProjectUser::whereProjectId($this->id)->orderBy('id')->pluck('userid')->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,6 +353,114 @@ class Project extends AbstractModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加工作流
|
||||
* @param $flows
|
||||
* @return mixed
|
||||
*/
|
||||
public function addFlow($flows)
|
||||
{
|
||||
return AbstractModel::transaction(function() use ($flows) {
|
||||
$projectFlow = ProjectFlow::whereProjectId($this->id)->first();
|
||||
if (empty($projectFlow)) {
|
||||
$projectFlow = ProjectFlow::createInstance([
|
||||
'project_id' => $this->id,
|
||||
'name' => 'Default'
|
||||
]);
|
||||
if (!$projectFlow->save()) {
|
||||
throw new ApiException('工作流创建失败');
|
||||
}
|
||||
}
|
||||
//
|
||||
$ids = [];
|
||||
$idc = [];
|
||||
$hasStart = false;
|
||||
$hasEnd = false;
|
||||
$upTaskList = [];
|
||||
foreach ($flows as $item) {
|
||||
$id = intval($item['id']);
|
||||
$turns = Base::arrayRetainInt($item['turns'] ?: [], true);
|
||||
$userids = Base::arrayRetainInt($item['userids'] ?: [], true);
|
||||
$usertype = trim($item['usertype']);
|
||||
$userlimit = intval($item['userlimit']);
|
||||
if ($usertype == 'replace' && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置流转模式时必须填写状态负责人");
|
||||
}
|
||||
if ($usertype == 'merge' && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置剔除模式时必须填写状态负责人");
|
||||
}
|
||||
if ($userlimit && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置限制负责人时必须填写状态负责人");
|
||||
}
|
||||
$flow = ProjectFlowItem::updateInsert([
|
||||
'id' => $id,
|
||||
'project_id' => $this->id,
|
||||
'flow_id' => $projectFlow->id,
|
||||
], [
|
||||
'name' => trim($item['name']),
|
||||
'status' => trim($item['status']),
|
||||
'sort' => intval($item['sort']),
|
||||
'turns' => $turns,
|
||||
'userids' => $userids,
|
||||
'usertype' => trim($item['usertype']),
|
||||
'userlimit' => $userlimit,
|
||||
], [], $isInsert);
|
||||
if ($flow) {
|
||||
$ids[] = $flow->id;
|
||||
if ($flow->id != $id) {
|
||||
$idc[$id] = $flow->id;
|
||||
}
|
||||
if ($flow->status == 'start') {
|
||||
$hasStart = true;
|
||||
}
|
||||
if ($flow->status == 'end') {
|
||||
$hasEnd = true;
|
||||
}
|
||||
if (!$isInsert) {
|
||||
$upTaskList[$flow->id] = $flow->status . "|" . $flow->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$hasStart) {
|
||||
throw new ApiException('至少需要1个开始状态');
|
||||
}
|
||||
if (!$hasEnd) {
|
||||
throw new ApiException('至少需要1个结束状态');
|
||||
}
|
||||
ProjectFlowItem::whereFlowId($projectFlow->id)->whereNotIn('id', $ids)->chunk(100, function($list) {
|
||||
foreach ($list as $item) {
|
||||
$item->deleteFlowItem();
|
||||
}
|
||||
});
|
||||
//
|
||||
foreach ($upTaskList as $id => $value) {
|
||||
ProjectTask::whereFlowItemId($id)->update([
|
||||
'flow_item_name' => $value
|
||||
]);
|
||||
}
|
||||
//
|
||||
$projectFlow = ProjectFlow::with(['projectFlowItem'])->whereProjectId($this->id)->find($projectFlow->id);
|
||||
$itemIds = $projectFlow->projectFlowItem->pluck('id')->toArray();
|
||||
foreach ($projectFlow->projectFlowItem as $item) {
|
||||
$turns = $item->turns;
|
||||
foreach ($idc as $oid => $nid) {
|
||||
if (in_array($oid, $turns)) {
|
||||
$turns = array_diff($turns, [$oid]);
|
||||
$turns[] = $nid;
|
||||
}
|
||||
}
|
||||
if (!in_array($item->id, $turns)) {
|
||||
$turns[] = $item->id;
|
||||
}
|
||||
$turns = array_values(array_filter(array_unique(array_intersect($turns, $itemIds))));
|
||||
sort($turns);
|
||||
$item->turns = $turns;
|
||||
ProjectFlowItem::whereId($item->id)->update([ 'turns' => Base::array2json($turns) ]);
|
||||
}
|
||||
return $projectFlow;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目信息(用于判断会员是否存在项目内)
|
||||
* @param int $project_id
|
||||
|
||||
@@ -13,7 +13,7 @@ use App\Module\Base;
|
||||
* @property string|null $name 名称
|
||||
* @property string|null $status 状态
|
||||
* @property array $turns 可流转
|
||||
* @property array $userids 自动负责人ID
|
||||
* @property array $userids 状态负责人ID
|
||||
* @property string|null $usertype 流转模式
|
||||
* @property int|null $userlimit 限制负责人
|
||||
* @property int|null $sort 排序
|
||||
|
||||
@@ -10,7 +10,7 @@ use App\Module\Base;
|
||||
* @property int $id
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property int|null $column_id 列表ID
|
||||
* @property int|null $task_id 项目ID
|
||||
* @property int|null $task_id 任务ID
|
||||
* @property int|null $userid 会员ID
|
||||
* @property string|null $detail 详细信息
|
||||
* @property array $record 记录数据
|
||||
|
||||
@@ -7,10 +7,12 @@ use App\Module\Base;
|
||||
use App\Tasks\PushTask;
|
||||
use Arr;
|
||||
use Carbon\Carbon;
|
||||
use Config;
|
||||
use DB;
|
||||
use Exception;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Mail;
|
||||
use Request;
|
||||
|
||||
/**
|
||||
@@ -40,6 +42,7 @@ use Request;
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property int|null $deleted_userid 删除会员
|
||||
* @property-read \App\Models\ProjectTaskContent|null $content
|
||||
* @property-read int $file_num
|
||||
* @property-read int $msg_num
|
||||
@@ -58,7 +61,7 @@ use Request;
|
||||
* @property-read int|null $task_user_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask allData($userid = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask authData($userid = null, $owner = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask betweenTime($start, $end)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask betweenTime($start, $end, $type = 'taskTime')
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newQuery()
|
||||
* @method static \Illuminate\Database\Query\Builder|ProjectTask onlyTrashed()
|
||||
@@ -71,6 +74,7 @@ use Request;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereCompleteAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDeletedUserid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDesc($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDialogId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereEndAt($value)
|
||||
@@ -235,7 +239,7 @@ class ProjectTask extends AbstractModel
|
||||
*/
|
||||
public function content(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(ProjectTaskContent::class, 'task_id', 'id');
|
||||
return $this->hasOne(ProjectTaskContent::class, 'task_id', 'id')->orderByDesc('id');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,7 +247,7 @@ class ProjectTask extends AbstractModel
|
||||
*/
|
||||
public function taskFile(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(ProjectTaskFile::class, 'task_id', 'id')->orderBy('id');
|
||||
return $this->hasMany(ProjectTaskFile::class, 'task_id', 'id')->orderByDesc('id')->limit(50);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,18 +316,33 @@ class ProjectTask extends AbstractModel
|
||||
* @param $query
|
||||
* @param $start
|
||||
* @param $end
|
||||
* @param $type
|
||||
* @return mixed
|
||||
*/
|
||||
public function scopeBetweenTime($query, $start, $end)
|
||||
public function scopeBetweenTime($query, $start, $end, $type = 'taskTime')
|
||||
{
|
||||
$query->where(function ($q1) use ($start, $end) {
|
||||
$q1->where(function ($q2) use ($start) {
|
||||
$q2->where('project_tasks.start_at', '<=', $start)->where('project_tasks.end_at', '>=', $start);
|
||||
})->orWhere(function ($q2) use ($end) {
|
||||
$q2->where('project_tasks.start_at', '<=', $end)->where('project_tasks.end_at', '>=', $end);
|
||||
})->orWhere(function ($q2) use ($start, $end) {
|
||||
$q2->where('project_tasks.start_at', '>', $start)->where('project_tasks.end_at', '<', $end);
|
||||
});
|
||||
$query->where(function ($q1) use ($start, $end, $type) {
|
||||
switch ($type) {
|
||||
case 'createdTime':
|
||||
$q1->where(function ($q2) use ($start) {
|
||||
$q2->where('project_tasks.created_at', '>=', $start);
|
||||
})->orWhere(function ($q2) use ($end) {
|
||||
$q2->where('project_tasks.created_at', '<=', $end);
|
||||
})->orWhere(function ($q2) use ($start, $end) {
|
||||
$q2->where('project_tasks.created_at', '>', $start)->where('project_tasks.created_at', '<', $end);
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
$q1->where(function ($q2) use ($start) {
|
||||
$q2->where('project_tasks.start_at', '<=', $start)->where('project_tasks.end_at', '>=', $start);
|
||||
})->orWhere(function ($q2) use ($end) {
|
||||
$q2->where('project_tasks.start_at', '<=', $end)->where('project_tasks.end_at', '>=', $end);
|
||||
})->orWhere(function ($q2) use ($start, $end) {
|
||||
$q2->where('project_tasks.start_at', '>', $start)->where('project_tasks.end_at', '<', $end);
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
return $query;
|
||||
}
|
||||
@@ -467,7 +486,9 @@ class ProjectTask extends AbstractModel
|
||||
ProjectTaskContent::createInstance([
|
||||
'project_id' => $task->project_id,
|
||||
'task_id' => $task->id,
|
||||
'content' => $content,
|
||||
'content' => [
|
||||
'url' => ProjectTaskContent::saveContent($task->id, $content)
|
||||
],
|
||||
])->save();
|
||||
}
|
||||
if ($task->parent_id == 0 && $subtasks && is_array($subtasks)) {
|
||||
@@ -508,13 +529,15 @@ class ProjectTask extends AbstractModel
|
||||
{
|
||||
AbstractModel::transaction(function () use ($data, &$updateMarking) {
|
||||
// 判断版本
|
||||
if (version_compare(Base::getClientVersion(), '0.6.0', '<')) {
|
||||
throw new ApiException('当前版本过低');
|
||||
}
|
||||
Base::checkClientVersion('0.6.0');
|
||||
// 主任务
|
||||
$mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null;
|
||||
// 工作流
|
||||
if (Arr::exists($data, 'flow_item_id')) {
|
||||
$isProjectOwner = $this->useridInTheProject(User::userid()) === 2;
|
||||
if (!$isProjectOwner && !$this->isOwner()) {
|
||||
throw new ApiException('仅限项目或任务负责人修改任务状态');
|
||||
}
|
||||
if ($this->flow_item_id == $data['flow_item_id']) {
|
||||
throw new ApiException('任务状态未发生改变');
|
||||
}
|
||||
@@ -535,8 +558,7 @@ class ProjectTask extends AbstractModel
|
||||
throw new ApiException("当前状态[{$currentFlowItem->name}]不可流转到[{$newFlowItem->name}]");
|
||||
}
|
||||
if ($currentFlowItem->userlimit) {
|
||||
if (!in_array(User::userid(), $currentFlowItem->userids)
|
||||
&& !ProjectUser::whereProjectId($this->project_id)->whereOwner(1)->exists()) {
|
||||
if (!$isProjectOwner && !in_array(User::userid(), $currentFlowItem->userids)) {
|
||||
throw new ApiException("当前状态[{$currentFlowItem->name}]仅限状态负责人或项目负责人修改");
|
||||
}
|
||||
}
|
||||
@@ -584,6 +606,14 @@ class ProjectTask extends AbstractModel
|
||||
'flow' => $flowData,
|
||||
'change' => [$currentFlowItem?->name, $newFlowItem->name]
|
||||
]);
|
||||
ProjectTaskFlowChange::createInstance([
|
||||
'task_id' => $this->id,
|
||||
'userid' => User::userid(),
|
||||
'before_flow_item_id' => $flowData['flow_item_id'],
|
||||
'before_flow_item_name' => $flowData['flow_item_name'],
|
||||
'after_flow_item_id' => $this->flow_item_id,
|
||||
'after_flow_item_name' => $this->flow_item_name,
|
||||
])->save();
|
||||
}
|
||||
// 状态
|
||||
if (Arr::exists($data, 'complete_at')) {
|
||||
@@ -605,7 +635,6 @@ class ProjectTask extends AbstractModel
|
||||
$this->completeTask(null);
|
||||
}
|
||||
$updateMarking['is_update_project'] = true;
|
||||
return;
|
||||
}
|
||||
// 标题
|
||||
if (Arr::exists($data, 'name') && $this->name != $data['name']) {
|
||||
@@ -664,6 +693,7 @@ class ProjectTask extends AbstractModel
|
||||
// 计划时间(原则:子任务时间在主任务时间内)
|
||||
if (Arr::exists($data, 'times')) {
|
||||
$oldAt = [Carbon::parse($this->start_at), Carbon::parse($this->end_at)];
|
||||
$oldStringAt = $this->start_at ? ($oldAt[0]->toDateTimeString() . '~' . $oldAt[1]->toDateTimeString()) : '';
|
||||
$this->start_at = null;
|
||||
$this->end_at = null;
|
||||
$times = $data['times'];
|
||||
@@ -727,7 +757,13 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
});
|
||||
}
|
||||
$this->addLog("修改{任务}时间");
|
||||
$newStringAt = $this->start_at ? ($this->start_at->toDateTimeString() . '~' . $this->end_at->toDateTimeString()) : '';
|
||||
$this->addLog("修改{任务}时间", [
|
||||
'change' => [$oldStringAt, $newStringAt]
|
||||
]);
|
||||
|
||||
//修改计划时间需要重置任务邮件提醒日志
|
||||
ProjectTaskMailLog::whereTaskId($this->id)->delete();
|
||||
}
|
||||
// 以下紧顶级任务可修改
|
||||
if ($this->parent_id === 0) {
|
||||
@@ -785,18 +821,20 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
// 内容
|
||||
if (Arr::exists($data, 'content')) {
|
||||
ProjectTaskContent::updateInsert([
|
||||
ProjectTaskContent::createInstance([
|
||||
'project_id' => $this->project_id,
|
||||
'task_id' => $this->id,
|
||||
], [
|
||||
'content' => $data['content'],
|
||||
]);
|
||||
'content' => [
|
||||
'url' => ProjectTaskContent::saveContent($this->id, $data['content'])
|
||||
],
|
||||
])->save();
|
||||
$this->desc = Base::getHtml($data['content'], 100);
|
||||
$this->addLog("修改{任务}详细描述");
|
||||
$updateMarking['is_update_content'] = true;
|
||||
}
|
||||
// 优先级
|
||||
$p = false;
|
||||
$oldPName = $this->p_name;
|
||||
if (Arr::exists($data, 'p_level') && $this->p_level != $data['p_level']) {
|
||||
$this->p_level = intval($data['p_level']);
|
||||
$p = true;
|
||||
@@ -810,7 +848,9 @@ class ProjectTask extends AbstractModel
|
||||
$p = true;
|
||||
}
|
||||
if ($p) {
|
||||
$this->addLog("修改{任务}优先级");
|
||||
$this->addLog("修改{任务}优先级", [
|
||||
'change' => [$oldPName, $this->p_name]
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->save();
|
||||
@@ -851,7 +891,7 @@ class ProjectTask extends AbstractModel
|
||||
*/
|
||||
public function relationUserids()
|
||||
{
|
||||
$userids = $this->taskUser->pluck('userid')->toArray();
|
||||
$userids = ProjectTaskUser::whereTaskId($this->id)->orderByDesc('owner')->orderByDesc('id')->pluck('userid')->toArray();
|
||||
$items = ProjectTask::with(['taskUser'])->where('parent_id', $this->id)->whereNull('archived_at')->get();
|
||||
foreach ($items as $item) {
|
||||
$userids = array_merge($userids, $item->taskUser->pluck('userid')->toArray());
|
||||
@@ -887,6 +927,44 @@ class ProjectTask extends AbstractModel
|
||||
return $user->owner ? 2 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限版本
|
||||
* @param int $level 1-负责人,2-协助人/负责人,3-创建人/协助人/负责人
|
||||
* @return bool
|
||||
*/
|
||||
public function permission($level = 1)
|
||||
{
|
||||
if ($level >= 3 && $this->isCreater()) {
|
||||
return true;
|
||||
}
|
||||
if ($level >= 2 && $this->isAssister()) {
|
||||
return true;
|
||||
}
|
||||
return $this->isOwner();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否创建者
|
||||
* @return bool
|
||||
*/
|
||||
public function isCreater()
|
||||
{
|
||||
return $this->userid == User::userid();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否协助人员
|
||||
* @return bool
|
||||
*/
|
||||
public function isAssister()
|
||||
{
|
||||
$row = $this;
|
||||
while ($row->parent_id > 0) {
|
||||
$row = self::find($row->parent_id);
|
||||
}
|
||||
return ProjectTaskUser::whereTaskId($row->id)->whereUserid(User::userid())->whereOwner(0)->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否负责人(或者是主任务的负责人)
|
||||
* @return bool
|
||||
@@ -954,6 +1032,16 @@ class ProjectTask extends AbstractModel
|
||||
*/
|
||||
public function archivedTask($archived_at, $isAuto = false)
|
||||
{
|
||||
if (!$this->complete_at) {
|
||||
$flowItems = ProjectFlowItem::whereProjectId($this->project_id)->whereStatus('end')->pluck('name');
|
||||
if ($flowItems) {
|
||||
$flowItems = implode(",", array_values(array_unique($flowItems->toArray())));
|
||||
}
|
||||
if (empty($flowItems)) {
|
||||
$flowItems = "已完成";
|
||||
}
|
||||
throw new ApiException('仅限【' . $flowItems . '】状态的任务归档');
|
||||
}
|
||||
AbstractModel::transaction(function () use ($isAuto, $archived_at) {
|
||||
if ($archived_at === null) {
|
||||
// 取消归档
|
||||
@@ -1003,6 +1091,8 @@ class ProjectTask extends AbstractModel
|
||||
$dialog?->deleteDialog();
|
||||
}
|
||||
self::whereParentId($this->id)->delete();
|
||||
$this->deleted_userid = User::userid();
|
||||
$this->save();
|
||||
$this->addLog("删除{任务}");
|
||||
$this->delete();
|
||||
});
|
||||
@@ -1012,6 +1102,28 @@ class ProjectTask extends AbstractModel
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 还原任务
|
||||
* @param bool $pushMsg 是否推送
|
||||
* @return bool
|
||||
*/
|
||||
public function recoveryTask($pushMsg = true)
|
||||
{
|
||||
AbstractModel::transaction(function () {
|
||||
if ($this->dialog_id) {
|
||||
$dialog = WebSocketDialog::withTrashed()->find($this->dialog_id);
|
||||
$dialog?->recoveryDialog();
|
||||
}
|
||||
self::whereParentId($this->id)->withTrashed()->restore();
|
||||
$this->addLog("还原{任务}");
|
||||
$this->restore();
|
||||
});
|
||||
if ($pushMsg) {
|
||||
$this->pushMsg('restore');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务日志
|
||||
* @param string $detail
|
||||
@@ -1106,22 +1218,29 @@ class ProjectTask extends AbstractModel
|
||||
* 获取任务(会员有任务权限 或 会员存在项目内)
|
||||
* @param int $task_id
|
||||
* @param bool $archived true:仅限未归档, false:仅限已归档, null:不限制
|
||||
* @param int|bool $mustOwner 0|false:不限制, 1|true:限制任务或项目负责人, 2:已有负责人才限制任务或项目负责人(子任务时如果是主任务负责人也可以)
|
||||
* @param bool $trashed true:仅限未删除, false:仅限已删除, null:不限制
|
||||
* @param int|bool $permission 0|false:不限制, 1|true:限制项目负责人、任务负责人、协助人员及任务创建者, 2:已有负责人才限制true (子任务时如果是主任务负责人也可以)
|
||||
* @param array $with
|
||||
* @return self
|
||||
*/
|
||||
public static function userTask($task_id, $archived = true, $mustOwner = 0, $with = [])
|
||||
public static function userTask($task_id, $archived = true, $trashed = true, $permission = false, $with = [])
|
||||
{
|
||||
$task = self::with($with)->allData()->where("project_tasks.id", intval($task_id))->first();
|
||||
$builder = self::with($with)->allData()->where("project_tasks.id", intval($task_id));
|
||||
if ($trashed === false) {
|
||||
$builder->onlyTrashed();
|
||||
} elseif ($trashed === null) {
|
||||
$builder->withTrashed();
|
||||
}
|
||||
$task = $builder->first();
|
||||
//
|
||||
if (empty($task)) {
|
||||
throw new ApiException('任务不存在', [ 'task_id' => $task_id ], -4002);
|
||||
throw new ApiException('任务不存在', ['task_id' => $task_id], -4002);
|
||||
}
|
||||
if ($archived === true && $task->archived_at != null) {
|
||||
throw new ApiException('任务已归档', [ 'task_id' => $task_id ]);
|
||||
throw new ApiException('任务已归档', ['task_id' => $task_id]);
|
||||
}
|
||||
if ($archived === false && $task->archived_at == null) {
|
||||
throw new ApiException('任务未归档', [ 'task_id' => $task_id ]);
|
||||
throw new ApiException('任务未归档', ['task_id' => $task_id]);
|
||||
}
|
||||
//
|
||||
try {
|
||||
@@ -1136,11 +1255,11 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
}
|
||||
//
|
||||
if ($mustOwner === 2) {
|
||||
$mustOwner = $task->hasOwner() ? 1 : 0;
|
||||
if ($permission === 2) {
|
||||
$permission = $task->hasOwner() ? 1 : 0;
|
||||
}
|
||||
if (($mustOwner === 1 || $mustOwner === true) && !$task->isOwner() && !$project->owner) {
|
||||
throw new ApiException('仅限项目或任务负责人操作');
|
||||
if (($permission === 1 || $permission === true) && !$project->owner && !$task->permission(3)) {
|
||||
throw new ApiException('仅限项目负责人、任务负责人、协助人员或任务创建者操作');
|
||||
}
|
||||
//
|
||||
return $task;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectTaskContent
|
||||
*
|
||||
@@ -28,4 +30,54 @@ class ProjectTaskContent extends AbstractModel
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取内容详情
|
||||
* @return array
|
||||
*/
|
||||
public function getContentInfo()
|
||||
{
|
||||
$content = Base::json2array($this->content);
|
||||
if (isset($content['url'])) {
|
||||
$filePath = public_path($content['url']);
|
||||
$array = $this->toArray();
|
||||
$array['content'] = file_get_contents($filePath) ?: '';
|
||||
if ($array['content']) {
|
||||
$replace = Base::fillUrl('uploads/task');
|
||||
$array['content'] = str_replace('{{RemoteURL}}uploads/task', $replace, $array['content']);
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存任务详情至文件并返回文件路径
|
||||
* @param $task_id
|
||||
* @param $content
|
||||
* @return string
|
||||
*/
|
||||
public static function saveContent($task_id, $content)
|
||||
{
|
||||
$path = 'uploads/task/content/' . date("Ym") . '/' . $task_id . '/';
|
||||
//
|
||||
preg_match_all("/<img\s*src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"/s", $content, $matchs);
|
||||
foreach ($matchs[2] as $key => $text) {
|
||||
$tmpPath = $path . 'attached/';
|
||||
Base::makeDir(public_path($tmpPath));
|
||||
$tmpPath .= md5($text) . "." . $matchs[1][$key];
|
||||
if (file_put_contents(public_path($tmpPath), base64_decode($text))) {
|
||||
$content = str_replace($matchs[0][$key], '<img src="{{RemoteURL}}' . $tmpPath . '"', $content);
|
||||
}
|
||||
}
|
||||
$pattern = '/<img(.*?)src=("|\')https*:\/\/(.*?)\/(uploads\/task\/content\/(.*?))\2/is';
|
||||
$content = preg_replace($pattern, '<img$1src=$2{{RemoteURL}}$4$2', $content);
|
||||
//
|
||||
$filePath = $path . md5($content);
|
||||
$publicPath = public_path($filePath);
|
||||
Base::makeDir(dirname($publicPath));
|
||||
file_put_contents($publicPath, $content);
|
||||
//
|
||||
return $filePath;
|
||||
}
|
||||
}
|
||||
|
||||
34
app/Models/ProjectTaskFlowChange.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectTaskFlowChange
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $task_id 任务ID
|
||||
* @property int|null $userid 会员ID
|
||||
* @property int|null $before_flow_item_id (变化前)工作流状态ID
|
||||
* @property string|null $before_flow_item_name (变化前)工作流状态名称
|
||||
* @property int|null $after_flow_item_id (变化后)工作流状态ID
|
||||
* @property string|null $after_flow_item_name (变化后)工作流状态名称
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereAfterFlowItemId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereAfterFlowItemName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereBeforeFlowItemId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereBeforeFlowItemName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereTaskId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ProjectTaskFlowChange extends AbstractModel
|
||||
{
|
||||
|
||||
}
|
||||
45
app/Models/ProjectTaskMailLog.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectTaskMailLog
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $userid 用户id
|
||||
* @property int|null $task_id 任务id
|
||||
* @property string|null $email 电子邮箱
|
||||
* @property int|null $type 提醒类型:1第一次任务提醒,2第二次任务超期提醒
|
||||
* @property int|null $is_send 邮件发送是否成功:0否,1是
|
||||
* @property string|null $send_error 邮件发送错误详情
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog newQuery()
|
||||
* @method static \Illuminate\Database\Query\Builder|ProjectTaskMailLog onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereEmail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereIsSend($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereSendError($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereTaskId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereUserid($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|ProjectTaskMailLog withTrashed()
|
||||
* @method static \Illuminate\Database\Query\Builder|ProjectTaskMailLog withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ProjectTaskMailLog extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use App\Module\Base;
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property int|null $userid 成员ID
|
||||
* @property int|null $owner 是否负责人
|
||||
* @property string|null $top_at 置顶时间
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Project|null $project
|
||||
@@ -21,6 +22,7 @@ use App\Module\Base;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereOwner($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereProjectId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereTopAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
|
||||
@@ -101,15 +101,13 @@ class Report extends AbstractModel
|
||||
/**
|
||||
* 获取单条记录
|
||||
* @param $id
|
||||
* @param User|null $user
|
||||
* @return Report|Builder|Model|object|null
|
||||
* @throw ApiException
|
||||
*/
|
||||
public static function getOne($id, User $user = null)
|
||||
public static function getOne($id)
|
||||
{
|
||||
$user === null && $user = User::auth();
|
||||
$one = self::whereUserid($user->userid)->whereId($id)->first();
|
||||
if ( empty($one) )
|
||||
$one = self::whereId($id)->first();
|
||||
if (empty($one))
|
||||
throw new ApiException("记录不存在");
|
||||
return $one;
|
||||
}
|
||||
@@ -144,15 +142,15 @@ class Report extends AbstractModel
|
||||
// 如果设置了周期偏移量
|
||||
empty( $offset ) || $now_dt->subWeeks( abs( $offset ) );
|
||||
$now_dt->startOfWeek(); // 设置为当周第一天
|
||||
return $now_dt->year . $now_dt->weekOfYear . $now_dt->month . $now_dt->weekOfMonth;
|
||||
return $now_dt->year . $now_dt->weekOfYear;
|
||||
},
|
||||
Report::DAILY => function() use ($now_dt, $offset) {
|
||||
// 如果设置了周期偏移量
|
||||
empty( $offset ) || $now_dt->subDays( abs( $offset ) );
|
||||
return $now_dt->year . $now_dt->dayOfYear . $now_dt->month . $now_dt->daysInMonth;
|
||||
return $now_dt->format("Ymd");
|
||||
},
|
||||
default => "",
|
||||
};
|
||||
return md5( $user->userid . ( is_callable($time_s) ? $time_s() : "" ) . $type );
|
||||
return $user->userid . ( is_callable($time_s) ? $time_s() : "" );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ use Carbon\Carbon;
|
||||
* @property int|null $task_dialog_id 最后打开的任务会话ID
|
||||
* @property string|null $created_ip 注册IP
|
||||
* @property string|null $disable_at 禁用时间
|
||||
* @property int|null $email_verity 邮箱是否已验证
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Database\Factories\UserFactory factory(...$parameters)
|
||||
@@ -41,6 +42,7 @@ use Carbon\Carbon;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedIp($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereDisableAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereEmail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereEmailVerity($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereEncrypt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereIdentity($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereLastAt($value)
|
||||
@@ -181,10 +183,16 @@ class User extends AbstractModel
|
||||
public static function reg($email, $password, $other = [])
|
||||
{
|
||||
//邮箱
|
||||
if (!Base::isMail($email)) {
|
||||
if (!Base::isEmail($email)) {
|
||||
throw new ApiException('请输入正确的邮箱地址');
|
||||
}
|
||||
if (User::email2userid($email) > 0) {
|
||||
$isRegVerify = Base::settingFind('emailSetting', 'reg_verify') === 'open';
|
||||
$user = self::whereUserid(User::email2userid($email))->first();
|
||||
if ($isRegVerify && $user->email_verity === 0) {
|
||||
UserEmailVerification::userEmailSend($user);
|
||||
throw new ApiException('您的账号已注册过,请验证邮箱', ['code' => 'email']);
|
||||
}
|
||||
throw new ApiException('邮箱地址已存在');
|
||||
}
|
||||
//密码
|
||||
|
||||
80
app/Models/UserEmailVerification.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Guanguans\Notify\Factory;
|
||||
use Guanguans\Notify\Messages\EmailMessage;
|
||||
|
||||
/**
|
||||
* App\Models\UserEmailVerification
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $userid 用户id
|
||||
* @property string|null $code 验证参数
|
||||
* @property string|null $email 电子邮箱
|
||||
* @property int|null $status 0-未验证,1-已验证
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereCode($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereEmail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereStatus($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class UserEmailVerification extends AbstractModel
|
||||
{
|
||||
|
||||
/**
|
||||
* 发验证邮箱
|
||||
* @param User $user
|
||||
*/
|
||||
public static function userEmailSend(User $user)
|
||||
{
|
||||
$res = self::whereUserid($user->userid)->where('created_at', '>', Carbon::now()->subMinutes(30))->first();
|
||||
if ($res) return;
|
||||
//删除
|
||||
self::whereUserid($user->userid)->delete();
|
||||
$userEmailVerification = self::createInstance([
|
||||
'userid' => $user->userid,
|
||||
'email' => $user->email,
|
||||
'code' => Base::generatePassword(64),
|
||||
'status' => 0,
|
||||
]);
|
||||
$userEmailVerification->save();
|
||||
|
||||
$setting = Base::setting('emailSetting');
|
||||
$url = Base::fillUrl('single/valid/email') . '?code=' . $userEmailVerification->code;
|
||||
try {
|
||||
if (!Base::isEmail($user->email)) {
|
||||
throw new \Exception("User email '{$user->email}' address error");
|
||||
}
|
||||
$subject = env('APP_NAME') . " 绑定邮箱验证";
|
||||
$content = "<p>{$user->nickname} 您好,您正在绑定 " . env('APP_NAME') . " 的邮箱,请于30分钟之内点击以下链接完成验证 :</p><p style='display: flex; justify-content: center;'><a href='{$url}' target='_blank'>{$url}</a></p>";
|
||||
Factory::mailer()
|
||||
->setDsn("smtp://{$setting['account']}:{$setting['password']}@{$setting['smtp_server']}:{$setting['port']}?verify_peer=0")
|
||||
->setMessage(EmailMessage::create()
|
||||
->from(env('APP_NAME', 'Task') . " <{$setting['account']}>")
|
||||
->to($user->email)
|
||||
->subject($subject)
|
||||
->html($content))
|
||||
->send();
|
||||
} catch (\Exception $e) {
|
||||
if (str_contains($e->getMessage(), "Timed Out")) {
|
||||
throw new ApiException("language.TimedOut");
|
||||
} elseif ($e->getCode() === 550) {
|
||||
throw new ApiException('邮件内容被拒绝,请检查邮箱是否开启接收功能');
|
||||
} else {
|
||||
throw new ApiException($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,14 +54,26 @@ class WebSocketDialog extends AbstractModel
|
||||
public function deleteDialog()
|
||||
{
|
||||
AbstractModel::transaction(function () {
|
||||
WebSocketDialogMsgRead::whereDialogId($this->id)->whereNull('read_at')->update([
|
||||
'read_at' => Carbon::now()
|
||||
]);
|
||||
WebSocketDialogMsgRead::whereDialogId($this->id)
|
||||
->whereNull('read_at')
|
||||
->chunkById(100, function ($list) {
|
||||
WebSocketDialogMsgRead::onlyMarkRead($list);
|
||||
});
|
||||
$this->delete();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 还原会话
|
||||
* @return bool
|
||||
*/
|
||||
public function recoveryDialog()
|
||||
{
|
||||
$this->restore();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对话(同时检验对话身份)
|
||||
* @param $dialog_id
|
||||
@@ -106,12 +118,14 @@ class WebSocketDialog extends AbstractModel
|
||||
$dialog->last_msg = $last_msg;
|
||||
// 未读信息
|
||||
$dialog->unread = WebSocketDialogMsgRead::whereDialogId($dialog->id)->whereUserid($userid)->whereReadAt(null)->count();
|
||||
$dialog->mark_unread = $dialog->mark_unread ?? WebSocketDialogUser::whereDialogId($dialog->id)->whereUserid($userid)->value('mark_unread');
|
||||
// 对话人数
|
||||
$builder = WebSocketDialogUser::whereDialogId($dialog->id);
|
||||
$dialog->people = $builder->count();
|
||||
// 对方信息
|
||||
$dialog->dialog_user = null;
|
||||
$dialog->group_info = null;
|
||||
$dialog->top_at = $dialog->top_at ?? WebSocketDialogUser::whereDialogId($dialog->id)->whereUserid($userid)->value('top_at');
|
||||
switch ($dialog->type) {
|
||||
case "user":
|
||||
$dialog_user = $builder->where('userid', '!=', $userid)->first();
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Tasks\PushTask;
|
||||
use App\Tasks\WebSocketDialogMsgTask;
|
||||
use Carbon\Carbon;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* App\Models\WebSocketDialogMsg
|
||||
@@ -21,11 +22,15 @@ use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
* @property int|null $send 发送数量
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property-read int|mixed $percentage
|
||||
* @property-read \App\Models\WebSocketDialog|null $webSocketDialog
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newQuery()
|
||||
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDialogId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereMsg($value)
|
||||
@@ -34,10 +39,14 @@ use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUserid($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg withTrashed()
|
||||
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class WebSocketDialogMsg extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $appends = [
|
||||
'percentage',
|
||||
];
|
||||
@@ -46,6 +55,14 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function webSocketDialog(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(WebSocketDialog::class, 'id', 'dialog_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 阅读占比
|
||||
* @return int|mixed
|
||||
@@ -53,11 +70,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
public function getPercentageAttribute()
|
||||
{
|
||||
if (!isset($this->appendattrs['percentage'])) {
|
||||
if ($this->read > $this->send || empty($this->send)) {
|
||||
$this->appendattrs['percentage'] = 100;
|
||||
} else {
|
||||
$this->appendattrs['percentage'] = intval($this->read / $this->send * 100);
|
||||
}
|
||||
$this->generatePercentage();
|
||||
}
|
||||
return $this->appendattrs['percentage'];
|
||||
}
|
||||
@@ -81,6 +94,22 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取占比
|
||||
* @param bool|int $increment 是否新增阅读数
|
||||
* @return int
|
||||
*/
|
||||
public function generatePercentage($increment = false) {
|
||||
if ($increment) {
|
||||
$this->increment('read', is_bool($increment) ? 1 : $increment);
|
||||
}
|
||||
if ($this->read > $this->send || empty($this->send)) {
|
||||
return $this->appendattrs['percentage'] = 100;
|
||||
} else {
|
||||
return $this->appendattrs['percentage'] = intval($this->read / $this->send * 100);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记已送达 同时 告诉发送人已送达
|
||||
* @param $userid
|
||||
@@ -110,13 +139,17 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
if (!$msgRead->read_at) {
|
||||
$msgRead->read_at = Carbon::now();
|
||||
$msgRead->save();
|
||||
$this->increment('read');
|
||||
$this->generatePercentage(true);
|
||||
PushTask::push([
|
||||
'userid' => $this->userid,
|
||||
'msg' => [
|
||||
'type' => 'dialog',
|
||||
'mode' => 'update',
|
||||
'data' => $this->toArray(),
|
||||
'mode' => 'readed',
|
||||
'data' => [
|
||||
'id' => $this->id,
|
||||
'read' => $this->read,
|
||||
'percentage' => $this->percentage,
|
||||
],
|
||||
]
|
||||
]);
|
||||
}
|
||||
@@ -124,6 +157,47 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除消息
|
||||
* @return void
|
||||
*/
|
||||
public function deleteMsg()
|
||||
{
|
||||
$send_dt = Carbon::parse($this->created_at)->addDay();
|
||||
if ($send_dt->lt(Carbon::now())) {
|
||||
throw new ApiException('已超过24小时,此消息不能撤回');
|
||||
}
|
||||
AbstractModel::transaction(function() {
|
||||
$deleteRead = WebSocketDialogMsgRead::whereMsgId($this->id)->whereNull('read_at')->delete(); // 未阅读记录不需要软删除,直接删除即可
|
||||
$this->delete();
|
||||
//
|
||||
$last_msg = null;
|
||||
if ($this->webSocketDialog) {
|
||||
$last_msg = WebSocketDialogMsg::whereDialogId($this->dialog_id)->orderByDesc('id')->first();
|
||||
$this->webSocketDialog->last_at = $last_msg->created_at;
|
||||
$this->webSocketDialog->save();
|
||||
}
|
||||
//
|
||||
$dialog = WebSocketDialog::find($this->dialog_id);
|
||||
if ($dialog) {
|
||||
$userids = $dialog->dialogUser->pluck('userid')->toArray();
|
||||
PushTask::push([
|
||||
'userid' => $userids,
|
||||
'msg' => [
|
||||
'type' => 'dialog',
|
||||
'mode' => 'delete',
|
||||
'data' => [
|
||||
'id' => $this->id,
|
||||
'dialog_id' => $this->dialog_id,
|
||||
'last_msg' => $last_msg,
|
||||
'update_read' => $deleteRead ? 1 : 0
|
||||
],
|
||||
]
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @param int $dialog_id 会话ID(即 聊天室ID)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* App\Models\WebSocketDialogMsgRead
|
||||
*
|
||||
@@ -11,6 +13,7 @@ namespace App\Models;
|
||||
* @property int|null $userid 发送会员ID
|
||||
* @property int|null $after 在阅读之后才添加的记录
|
||||
* @property string|null $read_at 阅读时间
|
||||
* @property-read \App\Models\WebSocketDialogMsg|null $webSocketDialogMsg
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead query()
|
||||
@@ -29,4 +32,38 @@ class WebSocketDialogMsgRead extends AbstractModel
|
||||
parent::__construct($attributes);
|
||||
$this->timestamps = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function webSocketDialogMsg(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(WebSocketDialogMsg::class, 'id', 'msg_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅标记成阅读
|
||||
* @param $list
|
||||
* @return void
|
||||
*/
|
||||
public static function onlyMarkRead($list)
|
||||
{
|
||||
$dialogMsg = [];
|
||||
/** @var WebSocketDialogMsgRead $item */
|
||||
foreach ($list as $item) {
|
||||
$item->read_at = Carbon::now();
|
||||
$item->save();
|
||||
if (isset($dialogMsg[$item->msg_id])) {
|
||||
$dialogMsg[$item->msg_id]['readNum']++;
|
||||
} else {
|
||||
$dialogMsg[$item->msg_id] = [
|
||||
'dialogMsg' => $item->webSocketDialogMsg,
|
||||
'readNum' => 1
|
||||
];
|
||||
}
|
||||
}
|
||||
foreach ($dialogMsg as $item) {
|
||||
$item['dialogMsg']?->generatePercentage($item['readNum']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace App\Models;
|
||||
* @property int $id
|
||||
* @property int|null $dialog_id 对话ID
|
||||
* @property int|null $userid 会员ID
|
||||
* @property string|null $top_at 置顶时间
|
||||
* @property int|null $mark_unread 是否标记为未读:0否,1是
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newModelQuery()
|
||||
@@ -16,6 +18,8 @@ namespace App\Models;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereDialogId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereMarkUnread($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereTopAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Tmp;
|
||||
use Cache;
|
||||
@@ -11,6 +12,7 @@ use Illuminate\Support\Facades\Config;
|
||||
use Redirect;
|
||||
use Request;
|
||||
use Storage;
|
||||
use Validator;
|
||||
|
||||
class Base
|
||||
{
|
||||
@@ -58,20 +60,30 @@ class Base
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取package配置文件
|
||||
* @return array
|
||||
*/
|
||||
public static function getPackage()
|
||||
{
|
||||
return Cache::remember("Base::package", now()->addSeconds(10), function () {
|
||||
$file = base_path('package.json');
|
||||
if (file_exists($file)) {
|
||||
$package = json_decode(file_get_contents($file), true);
|
||||
return is_array($package) ? $package : [];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取版本号
|
||||
* @return string
|
||||
*/
|
||||
public static function getVersion()
|
||||
{
|
||||
return Cache::remember("Base::version", now()->addSeconds(10), function () {
|
||||
$file = base_path('package.json');
|
||||
if (file_exists($file)) {
|
||||
$packageArray = json_decode(file_get_contents($file), true);
|
||||
return $packageArray['version'] ?? '1.0.0';
|
||||
}
|
||||
return '1.0.0';
|
||||
});
|
||||
$package = self::getPackage();
|
||||
return $package['version'] ?? '1.0.0';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,6 +99,18 @@ class Base
|
||||
return $_A["__static_client_version"];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查客户端版本
|
||||
* @param string $min 最小版本
|
||||
* @return void
|
||||
*/
|
||||
public static function checkClientVersion($min)
|
||||
{
|
||||
if (version_compare(Base::getClientVersion(), $min, '<')) {
|
||||
throw new ApiException('当前版本 (v' . Base::getClientVersion() . ') 过低');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否域名格式
|
||||
* @param $domain
|
||||
@@ -329,19 +353,15 @@ class Base
|
||||
{
|
||||
if (strtolower($charset) == 'utf-8') {
|
||||
if (Base::getStrlen($string) <= $length) return $string;
|
||||
$strcut = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $string);
|
||||
$strcut = Base::utf8Substr($strcut, $length, $start);
|
||||
$strcut = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $strcut);
|
||||
$strcut = Base::utf8Substr($string, $length, $start);
|
||||
return $strcut . $dot;
|
||||
} else {
|
||||
$length = $length * 2;
|
||||
if (strlen($string) <= $length) return $string;
|
||||
$string = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $string);
|
||||
$strcut = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$strcut .= ord($string[$i]) > 127 ? $string[$i] . $string[++$i] : $string[$i];
|
||||
}
|
||||
$strcut = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $strcut);
|
||||
}
|
||||
return $strcut . $dot;
|
||||
}
|
||||
@@ -799,6 +819,31 @@ class Base
|
||||
return $scheme.($_SERVER['HTTP_HOST'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 地址后拼接参数
|
||||
* @param $url
|
||||
* @param $parames
|
||||
* @return mixed|string
|
||||
*/
|
||||
public static function urlAddparameter($url, $parames)
|
||||
{
|
||||
if ($parames && is_array($parames)) {
|
||||
$array = [];
|
||||
foreach ($parames as $key => $val) {
|
||||
$array[] = $key . "=" . $val;
|
||||
}
|
||||
if ($array) {
|
||||
$query = implode("&", $array);
|
||||
if (str_contains($url, "?")) {
|
||||
$url .= "&" . $query;
|
||||
} else {
|
||||
$url .= "?" . $query;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化内容图片地址
|
||||
* @param $content
|
||||
@@ -951,13 +996,16 @@ class Base
|
||||
|
||||
/**
|
||||
* 检测邮箱格式
|
||||
* @param string $str 需要检测的字符串
|
||||
* @return int
|
||||
* @param $str
|
||||
* @return bool
|
||||
*/
|
||||
public static function isMail($str)
|
||||
public static function isEmail($str)
|
||||
{
|
||||
$RegExp = '/^[a-z0-9][a-z\.0-9-_]+@[a-z0-9_-]+(?:\.[a-z]{0,3}\.[a-z]{0,2}|\.[a-z]{0,3}|\.[a-z]{0,2})$/i';
|
||||
return preg_match($RegExp, $str);
|
||||
if (filter_var($str, FILTER_VALIDATE_EMAIL)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1173,11 +1221,12 @@ class Base
|
||||
|
||||
/**
|
||||
* 获取或设置
|
||||
* @param $setname //配置名称
|
||||
* @param bool $array //保存内容
|
||||
* @param $setname // 配置名称
|
||||
* @param bool $array // 保存内容
|
||||
* @param false $isUpdate // 保存内容为更新模式,默认否
|
||||
* @return array
|
||||
*/
|
||||
public static function setting($setname, $array = false)
|
||||
public static function setting($setname, $array = false, $isUpdate = false)
|
||||
{
|
||||
global $_A;
|
||||
if (empty($setname)) {
|
||||
@@ -1188,15 +1237,19 @@ class Base
|
||||
}
|
||||
$setting = [];
|
||||
$row = Setting::whereName($setname)->first();
|
||||
if (!empty($row)) {
|
||||
if ($row) {
|
||||
$setting = Base::string2array($row->setting);
|
||||
} else {
|
||||
$row = Setting::createInstance(['name' => $setname]);
|
||||
$row->save();
|
||||
}
|
||||
if ($array !== false) {
|
||||
$setting = $array;
|
||||
$row->updateInstance(['setting' => $array]);
|
||||
if ($isUpdate && is_array($array)) {
|
||||
$setting = array_merge($setting, $array);
|
||||
} else {
|
||||
$setting = $array;
|
||||
}
|
||||
$row->updateInstance(['setting' => $setting]);
|
||||
$row->save();
|
||||
}
|
||||
$_A["__static_setting_" . $setname] = $setting;
|
||||
@@ -1678,24 +1731,46 @@ class Base
|
||||
*/
|
||||
public static function timeDiff($s, $e)
|
||||
{
|
||||
$d = $e - $s;
|
||||
if ($d > 86400) {
|
||||
$day = floor($d / 86400);
|
||||
$hour = ceil(($d - ($day * 86400)) / 3600);
|
||||
if ($hour > 0) {
|
||||
return $day . '天' . $hour . '小时';
|
||||
} else {
|
||||
return $day . '天';
|
||||
}
|
||||
} elseif ($d > 3600) {
|
||||
return ceil($d / 3600) . '小时';
|
||||
} elseif ($d > 60) {
|
||||
return ceil($d / 60) . '分钟';
|
||||
} elseif ($d > 1) {
|
||||
return '1分钟内';
|
||||
} else {
|
||||
return '0秒';
|
||||
$time = $e - $s;
|
||||
$days = 0;
|
||||
if ($time >= 86400) { // 如果大于1天
|
||||
$days = (int)($time / 86400);
|
||||
$time = $time % 86400; // 计算天后剩余的毫秒数
|
||||
}
|
||||
$hours = 0;
|
||||
if ($time >= 3600) { // 如果大于1小时
|
||||
$hours = (int)($time / 3600);
|
||||
$time = $time % 3600; // 计算小时后剩余的毫秒数
|
||||
}
|
||||
$minutes = ceil($time / 60); // 剩下的毫秒数都算作分
|
||||
$daysStr = $days > 0 ? $days . '天' : '';
|
||||
$hoursStr = ($hours > 0 || ($days > 0 && $minutes > 0)) ? $hours . '时' : '';
|
||||
$minuteStr = ($minutes > 0) ? $minutes . '分' : '';
|
||||
return $daysStr . $hoursStr . $minuteStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间秒数格式化
|
||||
* @param int $time 时间秒数
|
||||
* @return string
|
||||
*/
|
||||
public static function timeFormat($time)
|
||||
{
|
||||
$days = 0;
|
||||
if ($time >= 86400) { // 如果大于1天
|
||||
$days = (int)($time / 86400);
|
||||
$time = $time % 86400; // 计算天后剩余的毫秒数
|
||||
}
|
||||
$hours = 0;
|
||||
if ($time >= 3600) { // 如果大于1小时
|
||||
$hours = (int)($time / 3600);
|
||||
$time = $time % 3600; // 计算小时后剩余的毫秒数
|
||||
}
|
||||
$minutes = ceil($time / 60); // 剩下的毫秒数都算作分
|
||||
$daysStr = $days > 0 ? $days . '天' : '';
|
||||
$hoursStr = ($hours > 0 || ($days > 0 && $minutes > 0)) ? $hours . '时' : '';
|
||||
$minuteStr = ($minutes > 0) ? $minutes . '分' : '';
|
||||
return $daysStr . $hoursStr . $minuteStr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2236,7 +2311,7 @@ class Base
|
||||
$type = ['zip'];
|
||||
break;
|
||||
case 'file':
|
||||
$type = ['jpg', 'jpeg', 'png', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'esp', 'pdf', 'rar', 'zip', 'gz'];
|
||||
$type = ['jpg', 'jpeg', 'png', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'esp', 'pdf', 'rar', 'zip', 'gz', 'ai', 'avi', 'bmp', 'cdr', 'eps', 'mov', 'mp3', 'mp4', 'pr', 'psd', 'svg', 'tif'];
|
||||
break;
|
||||
case 'firmware':
|
||||
$type = ['img', 'tar', 'bin'];
|
||||
@@ -2244,25 +2319,33 @@ class Base
|
||||
case 'md':
|
||||
$type = ['md'];
|
||||
break;
|
||||
case 'desktop':
|
||||
$type = ['yml', 'yaml', 'dmg', 'pkg', 'blockmap', 'zip', 'exe', 'msi'];
|
||||
break;
|
||||
case 'more':
|
||||
$type = [
|
||||
'text', 'md', 'markdown',
|
||||
'drawio',
|
||||
'mind',
|
||||
'docx', 'wps', 'doc', 'xls', 'xlsx', 'ppt', 'pptx',
|
||||
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw',
|
||||
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z',
|
||||
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw', 'svg',
|
||||
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z', 'gz', 'apk', 'dmg',
|
||||
'tif', 'tiff',
|
||||
'dwg', 'dxf',
|
||||
'ofd',
|
||||
'pdf',
|
||||
'txt',
|
||||
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css',
|
||||
'dockerfile', 'go', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
|
||||
'ocamlmakefile', 'make', 'md', 'markdown', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php',
|
||||
'dockerfile', 'go', 'golang', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
|
||||
'ocamlmakefile', 'make', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php',
|
||||
'inc', 'phtml', 'shtml', 'php3', 'php4', 'php5', 'phps', 'phpt', 'aw', 'ctp', 'module', 'ps1', 'py', 'r', 'rb', 'ru', 'gemspec', 'rake', 'guardfile', 'rakefile',
|
||||
'gemfile', 'rs', 'sass', 'scss', 'sh', 'bash', 'bashrc', 'sql', 'sqlserver', 'swift', 'ts', 'typescript', 'str', 'vbs', 'vb', 'v', 'vh', 'sv', 'svh', 'xml',
|
||||
'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
|
||||
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx',
|
||||
'mp3', 'wav', 'mp4', 'flv',
|
||||
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm',
|
||||
'xmind',
|
||||
'rp',
|
||||
];
|
||||
break;
|
||||
default:
|
||||
@@ -2281,7 +2364,9 @@ class Base
|
||||
$fileSize = 0;
|
||||
}
|
||||
$scaleName = "";
|
||||
if ($param['fileName']) {
|
||||
if ($param['fileName'] === true) {
|
||||
$fileName = $file->getClientOriginalName();
|
||||
} elseif ($param['fileName']) {
|
||||
$fileName = $param['fileName'];
|
||||
} else {
|
||||
if ($param['scale'] && is_array($param['scale'])) {
|
||||
@@ -2384,6 +2469,37 @@ class Base
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件移动
|
||||
* @param array $uploadResult
|
||||
* @param string $newPath "/" 结尾
|
||||
* @return array
|
||||
*/
|
||||
public static function uploadMove($uploadResult, $newPath)
|
||||
{
|
||||
if (str_ends_with($newPath, "/") && file_exists($uploadResult['file'])) {
|
||||
Base::makeDir(public_path($newPath));
|
||||
$oldPath = dirname($uploadResult['path']) . "/";
|
||||
$newFile = str_replace($oldPath, $newPath, $uploadResult['file']);
|
||||
if (rename($uploadResult['file'], $newFile)) {
|
||||
$oldUrl = $uploadResult['url'];
|
||||
$uploadResult['file'] = $newFile;
|
||||
$uploadResult['path'] = str_replace($oldPath, $newPath, $uploadResult['path']);
|
||||
$uploadResult['url'] = str_replace($oldPath, $newPath, $uploadResult['url']);
|
||||
if ($uploadResult['thumb'] == $oldUrl) {
|
||||
$uploadResult['thumb'] = $uploadResult['url'];
|
||||
} elseif ($uploadResult['thumb']) {
|
||||
$oldThumb = substr($uploadResult['thumb'], strpos($uploadResult['thumb'], $newPath));
|
||||
$newThumb = str_replace($oldPath, $newPath, $oldThumb);
|
||||
if (file_exists(public_path($oldThumb)) && rename(public_path($oldThumb), public_path($newThumb))) {
|
||||
$uploadResult['thumb'] = str_replace($oldPath, $newPath, $uploadResult['thumb']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $uploadResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成缩略图
|
||||
* @param string $src_img 源图绝对完整地址{带文件名及后缀名}
|
||||
@@ -2655,16 +2771,19 @@ class Base
|
||||
/**
|
||||
* 遍历获取文件
|
||||
* @param $dir
|
||||
* @param bool $subdirectory 是否遍历子目录
|
||||
* @return array
|
||||
*/
|
||||
public static function readDir($dir)
|
||||
public static function readDir($dir, $subdirectory = true)
|
||||
{
|
||||
$files = array();
|
||||
$dir_list = scandir($dir);
|
||||
foreach ($dir_list as $file) {
|
||||
if ($file != '..' && $file != '.') {
|
||||
if (is_dir($dir . '/' . $file)) {
|
||||
$files = array_merge($files, self::readDir($dir . '/' . $file));
|
||||
if ($subdirectory) {
|
||||
$files = array_merge($files, self::readDir($dir . '/' . $file, $subdirectory));
|
||||
}
|
||||
} else {
|
||||
$files[] = $dir . "/" . $file;
|
||||
}
|
||||
@@ -2893,4 +3012,54 @@ class Base
|
||||
$matrix = array_unique($matrix, SORT_REGULAR);
|
||||
return array_merge($matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节转格式
|
||||
* @param $bytes
|
||||
* @return string
|
||||
*/
|
||||
public static function readableBytes($bytes)
|
||||
{
|
||||
$i = floor(log($bytes) / log(1024));
|
||||
$sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
return sprintf('%.02F', $bytes / pow(1024, $i)) * 1 . ' ' . $sizes[$i];
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除emoji表情
|
||||
* @param $str
|
||||
* @return string|string[]|null
|
||||
*/
|
||||
public static function filterEmoji($str)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'/./u',
|
||||
function (array $match) {
|
||||
return strlen($match[0]) >= 4 ? '' : $match[0];
|
||||
},
|
||||
$str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一验证器
|
||||
* @param $data
|
||||
* @param $messages
|
||||
*/
|
||||
public static function validator($data, $messages) {
|
||||
$rules = [];
|
||||
foreach ($messages as $key => $item) {
|
||||
$keys = explode(".", $key);
|
||||
if (isset($keys[1])) {
|
||||
if (isset($rules[$keys[0]])) {
|
||||
$rules[$keys[0]] = $rules[$keys[0]] . '|' . $keys[1];
|
||||
} else {
|
||||
$rules[$keys[0]] = $keys[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
$validator = Validator::make($data, $rules, $messages);
|
||||
if ($validator->fails()) {
|
||||
throw new ApiException($validator->errors()->first());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
144
app/Module/BillExport.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
use Excel;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithEvents;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithStrictNullComparison;
|
||||
use Maatwebsite\Excel\Concerns\WithTitle;
|
||||
use Maatwebsite\Excel\Events\AfterSheet;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Exception;
|
||||
|
||||
class BillExport implements WithHeadings, WithEvents, FromCollection, WithTitle, WithStrictNullComparison
|
||||
{
|
||||
public $title;
|
||||
public $headings = [];
|
||||
public $data = [];
|
||||
public $typeLists = [];
|
||||
public $typeNumber = 0;
|
||||
|
||||
public function __construct($title, array $data)
|
||||
{
|
||||
$this->title = $title;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public static function create($data = [], $title = "Sheet1") {
|
||||
if (is_string($data)) {
|
||||
list($title, $data) = [$data, $title];
|
||||
}
|
||||
if (!is_array($data)) {
|
||||
$data = [];
|
||||
}
|
||||
return new BillExport($title, $data);
|
||||
}
|
||||
|
||||
public function setTitle($title) {
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setHeadings(array $headings) {
|
||||
$this->headings = $headings;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setData(array $data) {
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTypeList(array $typeList, $number = 0) {
|
||||
$this->typeLists = $typeList;
|
||||
$this->typeNumber = $number;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function store($fileName = '') {
|
||||
if (empty($fileName)) {
|
||||
$fileName = date("YmdHis") . '.xls';
|
||||
}
|
||||
try {
|
||||
return Excel::store($this, $fileName);
|
||||
} catch (Exception $e) {
|
||||
return "导出错误:" . $e->getMessage();
|
||||
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
|
||||
return "导出错误:" . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function download($fileName = '') {
|
||||
if (empty($fileName)) {
|
||||
$fileName = date("YmdHis") . '.xls';
|
||||
}
|
||||
try {
|
||||
return Excel::download($this, $fileName);
|
||||
} catch (Exception $e) {
|
||||
return "导出错误:" . $e->getMessage();
|
||||
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
|
||||
return "导出错误:" . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出的文件标题
|
||||
* @return string
|
||||
*/
|
||||
public function title(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标题行
|
||||
* @return array
|
||||
*/
|
||||
public function headings(): array
|
||||
{
|
||||
return $this->headings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出的内容
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
return collect($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置单元格事件
|
||||
* @return array
|
||||
*/
|
||||
public function registerEvents(): array
|
||||
{
|
||||
return [
|
||||
AfterSheet::Class => function (AfterSheet $event) {
|
||||
$count = count($this->data);
|
||||
foreach ($this->typeLists AS $cell => $typeList) {
|
||||
if ($cell && $typeList) {
|
||||
$p = $this->headings ? 1 : 0;
|
||||
for ($i = 1 + $p; $i <= max($count, $this->typeNumber) + $p; $i++) {
|
||||
$validation = $event->sheet->getDelegate()->getCell($cell . $i)->getDataValidation();
|
||||
$validation->setType(DataValidation::TYPE_LIST);
|
||||
$validation->setErrorStyle(DataValidation::STYLE_WARNING);
|
||||
$validation->setAllowBlank(false);
|
||||
$validation->setShowDropDown(true);
|
||||
$validation->setShowInputMessage(true);
|
||||
$validation->setShowErrorMessage(true);
|
||||
$validation->setErrorTitle('输入的值不合法');
|
||||
$validation->setError('选择的值不在列表中,请选择列表中的值');
|
||||
$validation->setPromptTitle('从列表中选择');
|
||||
$validation->setPrompt('请选择下拉列表中的值');
|
||||
$validation->setFormula1('"' . implode(',', $typeList) . '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
16
app/Module/BillImport.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
|
||||
use Maatwebsite\Excel\Concerns\ToArray;
|
||||
|
||||
class BillImport implements ToArray
|
||||
{
|
||||
public function Array(Array $tables)
|
||||
{
|
||||
return $tables;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -127,9 +127,11 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
case 'readMsg':
|
||||
$ids = is_array($data['id']) ? $data['id'] : [$data['id']];
|
||||
$userid = $this->getUserid($frame->fd);
|
||||
$list = WebSocketDialogMsg::whereIn('id', $ids)->get();
|
||||
$list->transform(function(WebSocketDialogMsg $item) use ($userid) {
|
||||
$item->readSuccess($userid);
|
||||
WebSocketDialogMsg::whereIn('id', $ids)->chunkById(20, function($list) use ($userid) {
|
||||
/** @var WebSocketDialogMsg $item */
|
||||
foreach ($list as $item) {
|
||||
$item->readSuccess($userid);
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -204,7 +206,19 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
*/
|
||||
private function deleteUser($fd)
|
||||
{
|
||||
WebSocket::whereFd($fd)->delete();
|
||||
$array = [];
|
||||
WebSocket::whereFd($fd)->chunk(10, function($list) use (&$array) {
|
||||
/** @var WebSocket $item */
|
||||
foreach ($list as $item) {
|
||||
$item->delete();
|
||||
if ($item->path && str_starts_with($item->path, "file/content/")) {
|
||||
$array[$item->path] = $item->path;
|
||||
}
|
||||
}
|
||||
});
|
||||
foreach ($array as $path) {
|
||||
$this->pushPath($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
116
app/Tasks/OverdueRemindEmailTask.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tasks;
|
||||
|
||||
use App\Models\ProjectTask;
|
||||
use App\Models\ProjectTaskMailLog;
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Guanguans\Notify\Factory;
|
||||
use Guanguans\Notify\Messages\EmailMessage;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
class OverdueRemindEmailTask extends AbstractTask
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
$setting = Base::setting('emailSetting');
|
||||
if ($setting['notice'] === 'open') {
|
||||
$hours = floatval($setting['task_remind_hours']);
|
||||
$hours2 = floatval($setting['task_remind_hours2']);
|
||||
if ($hours > 0) {
|
||||
ProjectTask::whereNull('complete_at')
|
||||
->whereNull('archived_at')
|
||||
->whereBetween("end_at", [
|
||||
Carbon::now()->addMinutes($hours * 60),
|
||||
Carbon::now()->addMinutes($hours * 60 + 10)
|
||||
])->chunkById(100, function ($tasks) {
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
$this->overdueBeforeAfterEmail($task, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($hours2 > 0) {
|
||||
ProjectTask::whereNull('complete_at')
|
||||
->whereNull('archived_at')
|
||||
->whereBetween("end_at", [
|
||||
Carbon::now()->subMinutes($hours2 * 60 + 10),
|
||||
Carbon::now()->subMinutes($hours2 * 60)
|
||||
])->chunkById(100, function ($tasks) {
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
$this->overdueBeforeAfterEmail($task, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 过期前、超期后提醒
|
||||
* @param ProjectTask $task
|
||||
* @param $isBefore
|
||||
* @return void
|
||||
*/
|
||||
private function overdueBeforeAfterEmail(ProjectTask $task, $isBefore)
|
||||
{
|
||||
$userids = $task->taskUser->where('owner', 1)->pluck('userid')->toArray();
|
||||
if (empty($userids)) {
|
||||
return;
|
||||
}
|
||||
$users = User::whereIn('userid', $userids)->get();
|
||||
if (empty($users)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$setting = Base::setting('emailSetting');
|
||||
$hours = floatval($setting['task_remind_hours']);
|
||||
$hours2 = floatval($setting['task_remind_hours2']);
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$data = [
|
||||
'type' => $isBefore ? 1 : 2,
|
||||
'userid' => $user->userid,
|
||||
'task_id' => $task->id,
|
||||
];
|
||||
$emailLog = ProjectTaskMailLog::where($data)->first();
|
||||
if ($emailLog) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (!Base::isEmail($user->email)) {
|
||||
throw new \Exception("User email '{$user->email}' address error");
|
||||
}
|
||||
if ($isBefore) {
|
||||
$subject = env('APP_NAME') . " 任务提醒";
|
||||
$content = "<p>{$user->nickname} 您好:</p><p>您有一个任务【{$task->name}】还有{$hours}小时即将超时,请及时处理。</p>";
|
||||
} else {
|
||||
$subject = env('APP_NAME') . " 任务过期提醒";
|
||||
$content = "<p>{$user->nickname} 您好:</p><p>您的任务【{$task->name}】已经超时{$hours2}小时,请及时处理。</p>";
|
||||
}
|
||||
Factory::mailer()
|
||||
->setDsn("smtp://{$setting['account']}:{$setting['password']}@{$setting['smtp_server']}:{$setting['port']}?verify_peer=0")
|
||||
->setMessage(EmailMessage::create()
|
||||
->from(env('APP_NAME', 'Task') . " <{$setting['account']}>")
|
||||
->to($user->email)
|
||||
->subject($subject)
|
||||
->html($content))
|
||||
->send();
|
||||
$data['is_send'] = 1;
|
||||
} catch (\Exception $e) {
|
||||
$data['send_error'] = $e->getMessage();
|
||||
}
|
||||
$data['email'] = $user->email;
|
||||
ProjectTaskMailLog::createInstance($data)->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
54
cmd
@@ -13,6 +13,7 @@ Error="${Red}[错误]${Font}"
|
||||
|
||||
cur_path="$(pwd)"
|
||||
cur_arg=$@
|
||||
COMPOSE="docker-compose"
|
||||
|
||||
judge() {
|
||||
if [[ 0 -eq $? ]]; then
|
||||
@@ -55,15 +56,24 @@ check_docker() {
|
||||
echo -e "${Error} ${RedBG} 未安装 Docker!${Font}"
|
||||
exit 1
|
||||
fi
|
||||
docker-compose --version &> /dev/null
|
||||
docker-compose version &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${Error} ${RedBG} 未安装 Docker-compose!${Font}"
|
||||
docker compose version &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${Error} ${RedBG} 未安装 Docker-compose!${Font}"
|
||||
exit 1
|
||||
fi
|
||||
COMPOSE="docker compose"
|
||||
fi
|
||||
if [[ -n `$COMPOSE version | grep -E "\sv*1"` ]]; then
|
||||
$COMPOSE version
|
||||
echo -e "${Error} ${RedBG} Docker-compose 版本过低,请升级至v2+!${Font}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_node() {
|
||||
npm --version > /dev/null
|
||||
npm --version &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${Error} ${RedBG} 未安装nodejs!${Font}"
|
||||
exit 1
|
||||
@@ -71,7 +81,7 @@ check_node() {
|
||||
}
|
||||
|
||||
docker_name() {
|
||||
echo `docker-compose ps | awk '{print $1}' | grep "\-$1\-"`
|
||||
echo `$COMPOSE ps | awk '{print $1}' | grep "\-$1\-"`
|
||||
}
|
||||
|
||||
run_compile() {
|
||||
@@ -86,6 +96,7 @@ run_compile() {
|
||||
if [ "$type" = "prod" ]; then
|
||||
rm -rf "./public/js/build"
|
||||
npx mix --production
|
||||
echo "$(rand_string 16)" > ./public/js/hash
|
||||
else
|
||||
npx mix watch --hot
|
||||
fi
|
||||
@@ -255,16 +266,19 @@ if [ $# -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
# 初始化文件
|
||||
rm -rf composer.lock
|
||||
rm -rf package-lock.json
|
||||
if [[ -n "$(arg_get relock)" ]]; then
|
||||
rm -rf node_modules
|
||||
rm -rf package-lock.json
|
||||
rm -rf vendor
|
||||
rm -rf composer.lock
|
||||
fi
|
||||
mkdir -p "${cur_path}/docker/log/supervisor"
|
||||
mkdir -p "${cur_path}/docker/mysql/data"
|
||||
chmod -R 775 "${cur_path}/docker/log/supervisor"
|
||||
chmod -R 775 "${cur_path}/docker/mysql/data"
|
||||
# 启动容器
|
||||
[[ "$(arg_get port)" -gt 0 ]] && env_set APP_PORT "$(arg_get port)"
|
||||
docker-compose up -d
|
||||
docker-compose restart php
|
||||
$COMPOSE up php -d
|
||||
# 安装composer依赖
|
||||
run_exec php "composer install"
|
||||
if [ ! -f "${cur_path}/vendor/autoload.php" ]; then
|
||||
@@ -283,17 +297,21 @@ if [ $# -gt 0 ]; then
|
||||
while [ ! -f "${cur_path}/docker/mysql/data/$(env_get DB_DATABASE)/db.opt" ]; do
|
||||
((remaining=$remaining-1))
|
||||
if [ $remaining -lt 0 ]; then
|
||||
echo -e "${Error} ${RedBG} 数据库安装失败! ${Font}"
|
||||
echo -e "${Error} ${RedBG} 数据库初始化失败! ${Font}"
|
||||
exit 1
|
||||
fi
|
||||
chmod -R 775 "${cur_path}/docker/mysql/data"
|
||||
sleep 3
|
||||
done
|
||||
run_exec php "php artisan migrate --seed"
|
||||
if [ ! -f "${cur_path}/docker/mysql/data/$(env_get DB_DATABASE)/$(env_get DB_PREFIX)migrations.ibd" ]; then
|
||||
echo -e "${Error} ${RedBG} 数据库安装失败! ${Font}"
|
||||
exit 1
|
||||
fi
|
||||
# 设置初始化密码
|
||||
res=`run_exec mariadb "sh /etc/mysql/repassword.sh"`
|
||||
docker-compose stop
|
||||
docker-compose start
|
||||
$COMPOSE up -d
|
||||
supervisorctl_restart php
|
||||
echo -e "${OK} ${GreenBG} 安装完成 ${Font}"
|
||||
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
|
||||
echo -e "$res"
|
||||
@@ -306,7 +324,7 @@ if [ $# -gt 0 ]; then
|
||||
run_exec php "composer update"
|
||||
run_exec php "php artisan migrate"
|
||||
supervisorctl_restart php
|
||||
docker-compose up -d
|
||||
$COMPOSE up -d
|
||||
elif [[ "$1" == "uninstall" ]]; then
|
||||
shift 1
|
||||
read -rp "确定要卸载(含:删除容器、数据库、日志)吗?(y/n): " uninstall
|
||||
@@ -320,7 +338,7 @@ if [ $# -gt 0 ]; then
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
docker-compose down
|
||||
$COMPOSE down
|
||||
rm -rf "./docker/mysql/data"
|
||||
rm -rf "./docker/log/supervisor"
|
||||
find "./storage/logs" -name "*.log" | xargs rm -rf
|
||||
@@ -333,7 +351,7 @@ if [ $# -gt 0 ]; then
|
||||
elif [[ "$1" == "port" ]]; then
|
||||
shift 1
|
||||
env_set APP_PORT "$1"
|
||||
docker-compose up -d
|
||||
$COMPOSE up -d
|
||||
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
|
||||
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
|
||||
elif [[ "$1" == "repassword" ]]; then
|
||||
@@ -406,11 +424,11 @@ if [ $# -gt 0 ]; then
|
||||
e="./vendor/bin/phpunit $@" && run_exec php "$e"
|
||||
elif [[ "$1" == "restart" ]]; then
|
||||
shift 1
|
||||
docker-compose stop "$@"
|
||||
docker-compose start "$@"
|
||||
$COMPOSE stop "$@"
|
||||
$COMPOSE start "$@"
|
||||
else
|
||||
docker-compose "$@"
|
||||
$COMPOSE "$@"
|
||||
fi
|
||||
else
|
||||
docker-compose ps
|
||||
$COMPOSE ps
|
||||
fi
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"ext-simplexml": "*",
|
||||
"fideloper/proxy": "^4.4.1",
|
||||
"fruitcake/laravel-cors": "^2.0.4",
|
||||
"guanguans/notify": "^1.20",
|
||||
"guzzlehttp/guzzle": "^7.3.0",
|
||||
"laravel/framework": "^v8.48.1",
|
||||
"laravel/tinker": "^v2.6.1",
|
||||
|
||||
9928
composer.lock
generated
Normal file
@@ -300,9 +300,9 @@ return [
|
||||
'log_level' => 4,
|
||||
'log_file' => storage_path(sprintf('logs/swoole-%s.log', date('Y-m'))),
|
||||
'document_root' => base_path('public'),
|
||||
'buffer_output_size' => 200 * 1024 * 1024,
|
||||
'socket_buffer_size' => 256 * 1024 * 1024,
|
||||
'package_max_length' => 200 * 1024 * 1024,
|
||||
'buffer_output_size' => 2 * 1024 * 1024,
|
||||
'socket_buffer_size' => 8 * 1024 * 1024,
|
||||
'package_max_length' => 1024 * 1024 * 1024, // 1GB
|
||||
'reload_async' => true,
|
||||
'max_wait_time' => 60,
|
||||
'enable_reuse_port' => true,
|
||||
|
||||
@@ -17,7 +17,7 @@ class CreateProjectLogsTable extends Migration
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('project_id')->nullable()->default(0)->comment('项目ID');
|
||||
$table->bigInteger('column_id')->nullable()->default(0)->comment('列表ID');
|
||||
$table->bigInteger('task_id')->nullable()->default(0)->comment('项目ID');
|
||||
$table->bigInteger('task_id')->nullable()->default(0)->comment('任务ID');
|
||||
$table->bigInteger('userid')->nullable()->default(0)->comment('会员ID');
|
||||
$table->string('detail', 500)->nullable()->default('')->comment('详细信息');
|
||||
$table->timestamps();
|
||||
|
||||
@@ -15,7 +15,7 @@ class CreateFileLinksTable extends Migration
|
||||
{
|
||||
Schema::create('file_links', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('file_id')->nullable()->default(0)->comment('项目ID');
|
||||
$table->bigInteger('file_id')->nullable()->default(0)->comment('文件ID');
|
||||
$table->integer('num')->nullable()->default(0)->comment('累计访问');
|
||||
$table->string('code')->nullable()->default('')->comment('链接码');
|
||||
$table->timestamps();
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class WebSocketDialogMsgsAddDeletes extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('web_socket_dialog_msgs', 'deleted_at')) {
|
||||
$table->softDeletes();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class InsertSettingColumnTemplate extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$array = \App\Module\Base::setting('columnTemplate');
|
||||
if (empty($array)) {
|
||||
\App\Module\Base::setting('columnTemplate', [
|
||||
[
|
||||
'name' => '软件开发',
|
||||
'columns' => ['产品规划', '前端开发', '后端开发', '测试', '发布', '其他'],
|
||||
],
|
||||
[
|
||||
'name' => '产品开发',
|
||||
'columns' => ['产品计划', '正在设计', '正在研发', '测试', '准备发布', '发布成功'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class WebSocketDialogUsersAddTopAt extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('web_socket_dialog_users', 'top_at')) {
|
||||
$table->timestamp('top_at')->nullable()->after('userid')->comment('置顶时间');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
|
||||
$table->dropColumn("top_at");
|
||||
});
|
||||
}
|
||||
}
|
||||
30
database/migrations/2022_02_20_171030_files_update_type.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use App\Models\File;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class FilesUpdateType extends Migration
|
||||
{
|
||||
/**
|
||||
* 更改流程图文件类型
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
File::whereType('flow')->update([
|
||||
'type' => 'drawio'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
File::whereType('drawio')->update([
|
||||
'type' => 'flow'
|
||||
]);
|
||||
}
|
||||
}
|
||||
75
database/migrations/2022_02_21_203230_files_update_ext.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
use App\Models\File;
|
||||
use App\Models\FileContent;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class FilesUpdateExt extends Migration
|
||||
{
|
||||
/**
|
||||
* 更新后缀
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
File::whereIn('type', ['mind', 'drawio', 'document'])->where('ext', '')->orderBy('id')->chunk(100, function($files) {
|
||||
/** @var File $file */
|
||||
foreach ($files as $file) {
|
||||
$fileContent = FileContent::whereFid($file->id)->orderByDesc('id')->first();
|
||||
$contentArray = Base::json2array($fileContent?->content);
|
||||
$contentString = '';
|
||||
//
|
||||
switch ($file->type) {
|
||||
case 'document':
|
||||
$file->ext = $contentArray['type'] ?: 'md';
|
||||
$contentString = $contentArray['content'];
|
||||
break;
|
||||
case 'drawio':
|
||||
$file->ext = 'drawio';
|
||||
$contentString = $contentArray['xml'];
|
||||
break;
|
||||
case 'mind':
|
||||
$file->ext = 'mind';
|
||||
$contentString = $fileContent?->content;
|
||||
break;
|
||||
}
|
||||
$file->save();
|
||||
//
|
||||
$path = 'uploads/file/' . $file->type . '/' . date("Ym", Carbon::parse($file->created_at)->timestamp) . '/' . $file->id . '/' . md5($contentString);
|
||||
$save = public_path($path);
|
||||
Base::makeDir(dirname($save));
|
||||
file_put_contents($save, $contentString);
|
||||
$content = [
|
||||
'type' => $file->ext,
|
||||
'url' => $path
|
||||
];
|
||||
//
|
||||
$content = FileContent::createInstance([
|
||||
'fid' => $file->id,
|
||||
'content' => $content,
|
||||
'text' => $fileContent?->text,
|
||||
'size' => $file->size,
|
||||
'userid' => $file->userid,
|
||||
]);
|
||||
$content->save();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
File::whereIn('ext', ['mind', 'drawio', 'md'])->update([
|
||||
'ext' => ''
|
||||
]);
|
||||
// ... 退回去意义不大,文件内容不做回滚操作
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ProjectUsersAddTopAt extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('project_users', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('project_users', 'top_at')) {
|
||||
$table->timestamp('top_at')->nullable()->after('owner')->comment('置顶时间');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('project_users', function (Blueprint $table) {
|
||||
$table->dropColumn("top_at");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateProjectTaskFlowChangesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('project_task_flow_changes', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('task_id')->nullable()->default(0)->comment('任务ID');
|
||||
$table->bigInteger('userid')->nullable()->default(0)->comment('会员ID');
|
||||
$table->bigInteger('before_item_id')->nullable()->default(0)->comment('(变化前)工作流状态ID');
|
||||
$table->string('before_item_name', 50)->nullable()->default('')->comment('(变化前)工作流状态名称');
|
||||
$table->bigInteger('after_item_id')->nullable()->default(0)->comment('(变化后)工作流状态ID');
|
||||
$table->string('after_item_name', 50)->nullable()->default('')->comment('(变化后)工作流状态名称');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('project_task_flow_changes');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class RenamePreProjectTaskFlowChangesItem extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('project_task_flow_changes', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('project_task_flow_changes', 'before_item_id')) {
|
||||
$table->renameColumn('before_item_id', 'before_flow_item_id');
|
||||
$table->renameColumn('before_item_name', 'before_flow_item_name');
|
||||
$table->renameColumn('after_item_id', 'after_flow_item_id');
|
||||
$table->renameColumn('after_item_name', 'after_flow_item_name');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('project_task_flow_changes', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddIsEmailVerityToUsers extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('users', 'is_email_verity')) {
|
||||
$table->boolean('is_email_verity')->default(0)->nullable()->after('disable_at')->comment('邮箱是否已验证');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn("is_email_verity");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateUserEmailVerificationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('user_email_verifications', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->bigInteger('userid')->nullable()->default(0)->comment('用户id');
|
||||
$table->string('code')->nullable()->default('')->comment('验证参数');
|
||||
$table->string('email')->nullable()->default('')->comment('电子邮箱');
|
||||
$table->integer('status')->nullable()->default(0)->comment('0-未验证,1-已验证');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('user_email_verifications');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateProjectTaskMailLogsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('project_task_mail_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->bigInteger('userid')->nullable()->default(0)->comment('用户id');
|
||||
$table->integer('task_id')->nullable()->default(0)->comment('任务id');
|
||||
$table->string('email')->nullable()->default('')->comment('电子邮箱');
|
||||
$table->tinyInteger('type')->nullable()->default(0)->comment('提醒类型:1第一次任务提醒,2第二次任务超期提醒');
|
||||
$table->tinyInteger('is_send')->nullable()->default(0)->comment('邮件发送是否成功:0否,1是');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('project_task_mail_logs');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
use App\Models\ProjectTaskContent;
|
||||
use App\Module\Base;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class ProjectTaskContentsUpdateContent extends Migration
|
||||
{
|
||||
/**
|
||||
* 任务详细描述保存至文件
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
ProjectTaskContent::orderBy('id')->chunk(100, function($items) {
|
||||
/** @var ProjectTaskContent $item */
|
||||
foreach ($items as $item) {
|
||||
$content = Base::json2array($item->content);
|
||||
if (!isset($content['url'])) {
|
||||
$item->content = Base::array2json([
|
||||
'url' => ProjectTaskContent::saveContent($item->task_id, $item->content)
|
||||
]);
|
||||
$item->save();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
// ... 退回去意义不大,文件内容不做回滚操作
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddWebSocketDialogUsersAddIsMarkUnread extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('web_socket_dialog_users', 'is_mark_unread')) {
|
||||
$table->boolean('is_mark_unread')->default(0)->nullable()->after('top_at')->comment('是否标记为未读:0否,1是');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
|
||||
$table->dropColumn("is_mark_unread");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddProjectTaskMailLogsAddDeletes extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('project_task_mail_logs', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('project_task_mail_logs', 'deleted_at')) {
|
||||
$table->softDeletes();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('project_task_mail_logs', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddProjectTasksDeletedUserid extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('project_tasks', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('project_tasks', 'deleted_userid')) {
|
||||
$table->bigInteger('deleted_userid')->nullable()->default(0)->after('deleted_at')->comment('删除会员');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('project_tasks', function (Blueprint $table) {
|
||||
$table->dropColumn("deleted_userid");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class RenamePreUsersIsEmailVerity extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$isAdd = false;
|
||||
Schema::table('users', function (Blueprint $table) use (&$isAdd) {
|
||||
if (Schema::hasColumn('users', 'is_email_verity')) {
|
||||
$isAdd = true;
|
||||
$table->renameColumn('is_email_verity', 'email_verity');
|
||||
}
|
||||
});
|
||||
if ($isAdd) {
|
||||
\App\Models\User::where("identity", "like", "%,admin,%")->chunkById(100, function ($lists) {
|
||||
foreach ($lists as $item) {
|
||||
if (!$item->email_verity) {
|
||||
$item->email_verity = 1;
|
||||
$item->save();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class RenamePreWebSocketDialogUsersIsMarkUnread extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('web_socket_dialog_users', 'is_mark_unread')) {
|
||||
$table->renameColumn('is_mark_unread', 'mark_unread');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
||||
51
database/migrations/2022_03_30_070029_add_files_pids.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddFilesPids extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$isAdd = false;
|
||||
Schema::table('files', function (Blueprint $table) use (&$isAdd) {
|
||||
if (!Schema::hasColumn('files', 'pids')) {
|
||||
$isAdd = true;
|
||||
$table->string('pids', 255)->nullable()->default('')->after('pid')->comment('上级ID递归');
|
||||
}
|
||||
});
|
||||
if ($isAdd) {
|
||||
// 更新数据
|
||||
\App\Models\File::where('pid', '>', 0)->chunkById(100, function ($lists) {
|
||||
/** @var \App\Models\File $item */
|
||||
foreach ($lists as $item) {
|
||||
$item->saveBeforePids();
|
||||
}
|
||||
});
|
||||
\App\Models\File::whereShare(0)->chunkById(100, function ($lists) {
|
||||
/** @var \App\Models\File $item */
|
||||
foreach ($lists as $item) {
|
||||
\App\Models\FileUser::whereFileId($item->id)->delete();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('files', function (Blueprint $table) {
|
||||
$table->dropColumn("pids");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddFileLinksUserid extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$isAdd = false;
|
||||
Schema::table('file_links', function (Blueprint $table) use (&$isAdd) {
|
||||
if (!Schema::hasColumn('file_links', 'userid')) {
|
||||
$isAdd = true;
|
||||
$table->bigInteger('userid')->nullable()->default(0)->after('code')->comment('会员ID');
|
||||
}
|
||||
});
|
||||
if ($isAdd) {
|
||||
// 更新数据
|
||||
\App\Models\FileLink::chunkById(100, function ($lists) {
|
||||
/** @var \App\Models\FileLink $item */
|
||||
foreach ($lists as $item) {
|
||||
$item->userid = intval(\App\Models\File::whereId($item->file_id)->value('userid'));
|
||||
$item->save();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('file_links', function (Blueprint $table) {
|
||||
$table->dropColumn("userid");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddProjectTaskMailLogsSendError extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('project_task_mail_logs', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('project_task_mail_logs', 'send_error')) {
|
||||
$table->text('send_error')->nullable()->after('is_send')->comment('邮件发送错误详情');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('project_task_mail_logs', function (Blueprint $table) {
|
||||
$table->dropColumn("send_error");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -599,7 +599,5 @@ curl -O https://task.hitosea.com/uploads/files/3/202105/ba786dfc2f4c2fe916880474
|
||||
'deleted_at' => NULL,
|
||||
),
|
||||
));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\File;
|
||||
use App\Models\FileContent;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class FilesTableSeeder extends Seeder
|
||||
@@ -203,7 +207,7 @@ class FilesTableSeeder extends Seeder
|
||||
'pid' => 0,
|
||||
'cid' => 0,
|
||||
'name' => '流程图',
|
||||
'type' => 'flow',
|
||||
'type' => 'drawio',
|
||||
'ext' => '',
|
||||
'size' => 5418,
|
||||
'userid' => 1,
|
||||
@@ -280,5 +284,54 @@ class FilesTableSeeder extends Seeder
|
||||
));
|
||||
|
||||
|
||||
File::whereIn('type', ['mind', 'drawio', 'document'])->where('ext', '')->orderBy('id')->chunk(100, function($files) {
|
||||
/** @var File $file */
|
||||
foreach ($files as $file) {
|
||||
$fileContent = FileContent::whereFid($file->id)->orderByDesc('id')->first();
|
||||
$contentArray = Base::json2array($fileContent?->content);
|
||||
$contentString = '';
|
||||
//
|
||||
switch ($file->type) {
|
||||
case 'document':
|
||||
$file->ext = $contentArray['type'] ?: 'md';
|
||||
$contentString = $contentArray['content'];
|
||||
break;
|
||||
case 'drawio':
|
||||
$file->ext = 'drawio';
|
||||
$contentString = $contentArray['xml'];
|
||||
break;
|
||||
case 'mind':
|
||||
$file->ext = 'mind';
|
||||
$contentString = $fileContent?->content;
|
||||
break;
|
||||
}
|
||||
$file->save();
|
||||
//
|
||||
$path = 'uploads/file/' . $file->type . '/' . date("Ym", Carbon::parse($file->created_at)->timestamp) . '/' . $file->id . '/' . md5($contentString);
|
||||
$save = public_path($path);
|
||||
Base::makeDir(dirname($save));
|
||||
file_put_contents($save, $contentString);
|
||||
$content = [
|
||||
'type' => $file->ext,
|
||||
'url' => $path
|
||||
];
|
||||
//
|
||||
$content = FileContent::createInstance([
|
||||
'fid' => $file->id,
|
||||
'content' => $content,
|
||||
'text' => $fileContent?->text,
|
||||
'size' => $file->size,
|
||||
'userid' => $file->userid,
|
||||
]);
|
||||
$content->save();
|
||||
}
|
||||
});
|
||||
|
||||
File::where('pid', '>', 0)->chunkById(100, function ($lists) {
|
||||
/** @var File $item */
|
||||
foreach ($lists as $item) {
|
||||
$item->saveBeforePids();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 1,
|
||||
'name' => '待处理',
|
||||
'status' => 'start',
|
||||
'turns' => '[1,2,3,4]',
|
||||
'turns' => '[1,2,3,4,26]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 0,
|
||||
'created_at' => '2022-01-15 23:43:15',
|
||||
'updated_at' => '2022-01-15 23:43:15',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
1 =>
|
||||
array (
|
||||
@@ -43,13 +43,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 1,
|
||||
'name' => '进行中',
|
||||
'status' => 'progress',
|
||||
'turns' => '[1,2,3,4]',
|
||||
'turns' => '[1,2,3,4,26]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 1,
|
||||
'created_at' => '2022-01-15 23:43:15',
|
||||
'updated_at' => '2022-01-15 23:43:15',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
2 =>
|
||||
array (
|
||||
@@ -58,13 +58,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 1,
|
||||
'name' => '已完成',
|
||||
'status' => 'end',
|
||||
'turns' => '[1,2,3,4]',
|
||||
'turns' => '[1,2,3,4,26]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 2,
|
||||
'created_at' => '2022-01-15 23:43:15',
|
||||
'updated_at' => '2022-01-15 23:43:15',
|
||||
'sort' => 3,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
3 =>
|
||||
array (
|
||||
@@ -73,13 +73,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 1,
|
||||
'name' => '已取消',
|
||||
'status' => 'end',
|
||||
'turns' => '[1,2,3,4]',
|
||||
'turns' => '[1,2,3,4,26]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 3,
|
||||
'created_at' => '2022-01-15 23:43:15',
|
||||
'updated_at' => '2022-01-15 23:43:15',
|
||||
'sort' => 4,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
4 =>
|
||||
array (
|
||||
@@ -88,13 +88,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 2,
|
||||
'name' => '待处理',
|
||||
'status' => 'start',
|
||||
'turns' => '[5,6,7,8]',
|
||||
'turns' => '[5,6,7,8,27]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 0,
|
||||
'created_at' => '2022-01-15 23:43:23',
|
||||
'updated_at' => '2022-01-15 23:43:23',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
5 =>
|
||||
array (
|
||||
@@ -103,13 +103,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 2,
|
||||
'name' => '进行中',
|
||||
'status' => 'progress',
|
||||
'turns' => '[5,6,7,8]',
|
||||
'turns' => '[5,6,7,8,27]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 1,
|
||||
'created_at' => '2022-01-15 23:43:23',
|
||||
'updated_at' => '2022-01-15 23:43:23',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
6 =>
|
||||
array (
|
||||
@@ -118,13 +118,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 2,
|
||||
'name' => '已完成',
|
||||
'status' => 'end',
|
||||
'turns' => '[5,6,7,8]',
|
||||
'turns' => '[5,6,7,8,27]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 2,
|
||||
'created_at' => '2022-01-15 23:43:23',
|
||||
'updated_at' => '2022-01-15 23:43:23',
|
||||
'sort' => 3,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
7 =>
|
||||
array (
|
||||
@@ -133,13 +133,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 2,
|
||||
'name' => '已取消',
|
||||
'status' => 'end',
|
||||
'turns' => '[5,6,7,8]',
|
||||
'turns' => '[5,6,7,8,27]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 3,
|
||||
'created_at' => '2022-01-15 23:43:23',
|
||||
'updated_at' => '2022-01-15 23:43:23',
|
||||
'sort' => 4,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
8 =>
|
||||
array (
|
||||
@@ -148,13 +148,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 3,
|
||||
'name' => '待处理',
|
||||
'status' => 'start',
|
||||
'turns' => '[9,10,11,12]',
|
||||
'turns' => '[9,10,11,12,28]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 0,
|
||||
'created_at' => '2022-01-15 23:43:28',
|
||||
'updated_at' => '2022-01-15 23:43:28',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
9 =>
|
||||
array (
|
||||
@@ -163,13 +163,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 3,
|
||||
'name' => '进行中',
|
||||
'status' => 'progress',
|
||||
'turns' => '[9,10,11,12]',
|
||||
'turns' => '[9,10,11,12,28]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 1,
|
||||
'created_at' => '2022-01-15 23:43:28',
|
||||
'updated_at' => '2022-01-15 23:43:28',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
10 =>
|
||||
array (
|
||||
@@ -178,13 +178,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 3,
|
||||
'name' => '已完成',
|
||||
'status' => 'end',
|
||||
'turns' => '[9,10,11,12]',
|
||||
'turns' => '[9,10,11,12,28]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 2,
|
||||
'created_at' => '2022-01-15 23:43:28',
|
||||
'updated_at' => '2022-01-15 23:43:28',
|
||||
'sort' => 3,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
11 =>
|
||||
array (
|
||||
@@ -193,13 +193,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 3,
|
||||
'name' => '已取消',
|
||||
'status' => 'end',
|
||||
'turns' => '[9,10,11,12]',
|
||||
'turns' => '[9,10,11,12,28]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 3,
|
||||
'created_at' => '2022-01-15 23:43:28',
|
||||
'updated_at' => '2022-01-15 23:43:28',
|
||||
'sort' => 4,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
12 =>
|
||||
array (
|
||||
@@ -208,13 +208,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 4,
|
||||
'name' => '待处理',
|
||||
'status' => 'start',
|
||||
'turns' => '[13,14,15,16]',
|
||||
'turns' => '[13,14,15,16,29]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 0,
|
||||
'created_at' => '2022-01-15 23:43:34',
|
||||
'updated_at' => '2022-01-15 23:43:34',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
13 =>
|
||||
array (
|
||||
@@ -223,13 +223,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 4,
|
||||
'name' => '进行中',
|
||||
'status' => 'progress',
|
||||
'turns' => '[13,14,15,16]',
|
||||
'turns' => '[13,14,15,16,29]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 1,
|
||||
'created_at' => '2022-01-15 23:43:34',
|
||||
'updated_at' => '2022-01-15 23:43:34',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
14 =>
|
||||
array (
|
||||
@@ -238,13 +238,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 4,
|
||||
'name' => '已完成',
|
||||
'status' => 'end',
|
||||
'turns' => '[13,14,15,16]',
|
||||
'turns' => '[13,14,15,16,29]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 2,
|
||||
'created_at' => '2022-01-15 23:43:34',
|
||||
'updated_at' => '2022-01-15 23:43:34',
|
||||
'sort' => 3,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
15 =>
|
||||
array (
|
||||
@@ -253,13 +253,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 4,
|
||||
'name' => '已取消',
|
||||
'status' => 'end',
|
||||
'turns' => '[13,14,15,16]',
|
||||
'turns' => '[13,14,15,16,29]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 3,
|
||||
'created_at' => '2022-01-15 23:43:34',
|
||||
'updated_at' => '2022-01-15 23:43:34',
|
||||
'sort' => 4,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
16 =>
|
||||
array (
|
||||
@@ -268,13 +268,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 5,
|
||||
'name' => '待处理',
|
||||
'status' => 'start',
|
||||
'turns' => '[17,18,19,20]',
|
||||
'turns' => '[17,18,19,20,30]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 0,
|
||||
'created_at' => '2022-01-15 23:43:40',
|
||||
'updated_at' => '2022-01-15 23:43:40',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
17 =>
|
||||
array (
|
||||
@@ -283,13 +283,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 5,
|
||||
'name' => '进行中',
|
||||
'status' => 'progress',
|
||||
'turns' => '[17,18,19,20]',
|
||||
'turns' => '[17,18,19,20,30]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 1,
|
||||
'created_at' => '2022-01-15 23:43:40',
|
||||
'updated_at' => '2022-01-15 23:43:40',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
18 =>
|
||||
array (
|
||||
@@ -298,13 +298,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 5,
|
||||
'name' => '已完成',
|
||||
'status' => 'end',
|
||||
'turns' => '[17,18,19,20]',
|
||||
'turns' => '[17,18,19,20,30]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 2,
|
||||
'created_at' => '2022-01-15 23:43:40',
|
||||
'updated_at' => '2022-01-15 23:43:40',
|
||||
'sort' => 3,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
19 =>
|
||||
array (
|
||||
@@ -313,13 +313,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 5,
|
||||
'name' => '已取消',
|
||||
'status' => 'end',
|
||||
'turns' => '[17,18,19,20]',
|
||||
'turns' => '[17,18,19,20,30]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 3,
|
||||
'created_at' => '2022-01-15 23:43:40',
|
||||
'updated_at' => '2022-01-15 23:43:40',
|
||||
'sort' => 4,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
20 =>
|
||||
array (
|
||||
@@ -328,13 +328,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 6,
|
||||
'name' => '待处理',
|
||||
'status' => 'start',
|
||||
'turns' => '[21,22,23,24]',
|
||||
'turns' => '[21,22,23,24,25]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 0,
|
||||
'created_at' => '2022-01-15 23:43:45',
|
||||
'updated_at' => '2022-01-15 23:43:45',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
21 =>
|
||||
array (
|
||||
@@ -343,13 +343,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 6,
|
||||
'name' => '进行中',
|
||||
'status' => 'progress',
|
||||
'turns' => '[21,22,23,24]',
|
||||
'turns' => '[21,22,23,24,25]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 1,
|
||||
'created_at' => '2022-01-15 23:43:45',
|
||||
'updated_at' => '2022-01-15 23:43:45',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
22 =>
|
||||
array (
|
||||
@@ -358,13 +358,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 6,
|
||||
'name' => '已完成',
|
||||
'status' => 'end',
|
||||
'turns' => '[21,22,23,24]',
|
||||
'turns' => '[21,22,23,24,25]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 2,
|
||||
'created_at' => '2022-01-15 23:43:45',
|
||||
'updated_at' => '2022-01-15 23:43:45',
|
||||
'sort' => 3,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
23 =>
|
||||
array (
|
||||
@@ -373,13 +373,103 @@ class ProjectFlowItemsTableSeeder extends Seeder
|
||||
'flow_id' => 6,
|
||||
'name' => '已取消',
|
||||
'status' => 'end',
|
||||
'turns' => '[21,22,23,24]',
|
||||
'turns' => '[21,22,23,24,25]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 3,
|
||||
'created_at' => '2022-01-15 23:43:45',
|
||||
'updated_at' => '2022-01-15 23:43:45',
|
||||
'sort' => 4,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
24 =>
|
||||
array (
|
||||
'id' => 25,
|
||||
'project_id' => 7,
|
||||
'flow_id' => 6,
|
||||
'name' => '待测试',
|
||||
'status' => 'test',
|
||||
'turns' => '[21,22,23,24,25]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 2,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
25 =>
|
||||
array (
|
||||
'id' => 26,
|
||||
'project_id' => 2,
|
||||
'flow_id' => 1,
|
||||
'name' => '待测试',
|
||||
'status' => 'test',
|
||||
'turns' => '[1,2,3,4,26]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 2,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
26 =>
|
||||
array (
|
||||
'id' => 27,
|
||||
'project_id' => 3,
|
||||
'flow_id' => 2,
|
||||
'name' => '待测试',
|
||||
'status' => 'test',
|
||||
'turns' => '[5,6,7,8,27]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 2,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
27 =>
|
||||
array (
|
||||
'id' => 28,
|
||||
'project_id' => 4,
|
||||
'flow_id' => 3,
|
||||
'name' => '待测试',
|
||||
'status' => 'test',
|
||||
'turns' => '[9,10,11,12,28]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 2,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
28 =>
|
||||
array (
|
||||
'id' => 29,
|
||||
'project_id' => 5,
|
||||
'flow_id' => 4,
|
||||
'name' => '待测试',
|
||||
'status' => 'test',
|
||||
'turns' => '[13,14,15,16,29]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 2,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
29 =>
|
||||
array (
|
||||
'id' => 30,
|
||||
'project_id' => 6,
|
||||
'flow_id' => 5,
|
||||
'name' => '待测试',
|
||||
'status' => 'test',
|
||||
'turns' => '[17,18,19,20,30]',
|
||||
'userids' => '[]',
|
||||
'usertype' => 'add',
|
||||
'userlimit' => 0,
|
||||
'sort' => 2,
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
));
|
||||
|
||||
|
||||
@@ -26,48 +26,48 @@ class ProjectFlowsTableSeeder extends Seeder
|
||||
'id' => 1,
|
||||
'project_id' => 2,
|
||||
'name' => 'Default',
|
||||
'created_at' => '2022-01-15 23:43:15',
|
||||
'updated_at' => '2022-01-15 23:43:15',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
1 =>
|
||||
array (
|
||||
'id' => 2,
|
||||
'project_id' => 3,
|
||||
'name' => 'Default',
|
||||
'created_at' => '2022-01-15 23:43:23',
|
||||
'updated_at' => '2022-01-15 23:43:23',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
2 =>
|
||||
array (
|
||||
'id' => 3,
|
||||
'project_id' => 4,
|
||||
'name' => 'Default',
|
||||
'created_at' => '2022-01-15 23:43:28',
|
||||
'updated_at' => '2022-01-15 23:43:28',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
3 =>
|
||||
array (
|
||||
'id' => 4,
|
||||
'project_id' => 5,
|
||||
'name' => 'Default',
|
||||
'created_at' => '2022-01-15 23:43:34',
|
||||
'updated_at' => '2022-01-15 23:43:34',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
4 =>
|
||||
array (
|
||||
'id' => 5,
|
||||
'project_id' => 6,
|
||||
'name' => 'Default',
|
||||
'created_at' => '2022-01-15 23:43:40',
|
||||
'updated_at' => '2022-01-15 23:43:40',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
5 =>
|
||||
array (
|
||||
'id' => 6,
|
||||
'project_id' => 7,
|
||||
'name' => 'Default',
|
||||
'created_at' => '2022-01-15 23:43:45',
|
||||
'updated_at' => '2022-01-15 23:43:45',
|
||||
'created_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
'updated_at' => seeders_at('2021-07-01 12:27:12'),
|
||||
),
|
||||
));
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\ProjectTaskContent;
|
||||
use App\Module\Base;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ProjectTaskContentsTableSeeder extends Seeder
|
||||
@@ -302,6 +304,17 @@ class ProjectTaskContentsTableSeeder extends Seeder
|
||||
),
|
||||
));
|
||||
|
||||
|
||||
ProjectTaskContent::orderBy('id')->chunk(100, function($items) {
|
||||
/** @var ProjectTaskContent $item */
|
||||
foreach ($items as $item) {
|
||||
$content = Base::json2array($item->content);
|
||||
if (!isset($content['url'])) {
|
||||
$item->content = Base::array2json([
|
||||
'url' => ProjectTaskContent::saveContent($item->task_id, $item->content)
|
||||
]);
|
||||
$item->save();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,13 @@ class SettingsTableSeeder extends Seeder
|
||||
{
|
||||
|
||||
|
||||
if (\DB::table('settings')->count() > 0) {
|
||||
if (\DB::table('settings')->where('name', 'system')->count() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
\DB::table('settings')->insert(array (
|
||||
0 =>
|
||||
array (
|
||||
'id' => 1,
|
||||
'name' => 'system',
|
||||
'desc' => '',
|
||||
'setting' => '{"reg":"open","project_invite":"open","login_code":"auto"}',
|
||||
@@ -32,7 +31,6 @@ class SettingsTableSeeder extends Seeder
|
||||
),
|
||||
1 =>
|
||||
array (
|
||||
'id' => 2,
|
||||
'name' => 'priority',
|
||||
'desc' => '',
|
||||
'setting' => '[{"name":"\\u91cd\\u8981\\u4e14\\u7d27\\u6025","color":"#ED4014","days":1,"priority":1},{"name":"\\u91cd\\u8981\\u4e0d\\u7d27\\u6025","color":"#F16B62","days":3,"priority":2},{"name":"\\u7d27\\u6025\\u4e0d\\u91cd\\u8981","color":"#19C919","days":5,"priority":3},{"name":"\\u4e0d\\u91cd\\u8981\\u4e0d\\u7d27\\u6025","color":"#2D8CF0","days":0,"priority":4}]',
|
||||
|
||||
@@ -39,6 +39,7 @@ class UsersTableSeeder extends Seeder
|
||||
'line_ip' => '127.0.0.1',
|
||||
'line_at' => seeders_at('2021-07-01 17:43:48'),
|
||||
'task_dialog_id' => 18,
|
||||
'email_verity' => 1,
|
||||
'created_ip' => '',
|
||||
'disable_at' => null,
|
||||
'created_at' => seeders_at('2021-07-01 11:01:14'),
|
||||
@@ -62,6 +63,7 @@ class UsersTableSeeder extends Seeder
|
||||
'line_ip' => '127.0.0.1',
|
||||
'line_at' => seeders_at('2021-07-01 16:57:40'),
|
||||
'task_dialog_id' => 16,
|
||||
'email_verity' => 1,
|
||||
'created_ip' => '',
|
||||
'disable_at' => null,
|
||||
'created_at' => seeders_at('2021-07-01 11:01:14'),
|
||||
|
||||
@@ -45,6 +45,8 @@ services:
|
||||
- php
|
||||
- office
|
||||
- fileview
|
||||
- drawio-webapp
|
||||
- drawio-export
|
||||
restart: unless-stopped
|
||||
|
||||
redis:
|
||||
@@ -59,7 +61,7 @@ services:
|
||||
|
||||
mariadb:
|
||||
container_name: "dootask-mariadb-${APP_ID}"
|
||||
image: "mariadb"
|
||||
image: "mariadb:10.7.3"
|
||||
ports: # mysql ports item
|
||||
- "33062:3306" # mysql ports value
|
||||
volumes:
|
||||
@@ -80,13 +82,16 @@ services:
|
||||
|
||||
office:
|
||||
container_name: "dootask-office-${APP_ID}"
|
||||
image: "onlyoffice/documentserver:6.4.2.6"
|
||||
image: "onlyoffice/documentserver:7.0.0.132"
|
||||
volumes:
|
||||
- ./docker/office/data:/var/www/onlyoffice/Data
|
||||
- ./docker/office/logs:/var/log/onlyoffice
|
||||
- ./docker/office/resources/documenteditor/css/app.css:/var/www/onlyoffice/documentserver/web-apps/apps/documenteditor/main/resources/css/app.css
|
||||
- ./docker/office/resources/presentationeditor/css/app.css:/var/www/onlyoffice/documentserver/web-apps/apps/presentationeditor/main/resources/css/app.css
|
||||
- ./docker/office/resources/spreadsheeteditor/css/app.css:/var/www/onlyoffice/documentserver/web-apps/apps/spreadsheeteditor/main/resources/css/app.css
|
||||
- ./docker/office/resources/documenteditor/mobile/css/app.css:/var/www/onlyoffice/documentserver/web-apps/apps/documenteditor/mobile/css/app.css
|
||||
- ./docker/office/resources/presentationeditor/mobile/css/app.css:/var/www/onlyoffice/documentserver/web-apps/apps/presentationeditor/mobile/css/app.css
|
||||
- ./docker/office/resources/spreadsheeteditor/mobile/css/app.css:/var/www/onlyoffice/documentserver/web-apps/apps/spreadsheeteditor/mobile/css/app.css
|
||||
environment:
|
||||
TZ: "Asia/Shanghai"
|
||||
networks:
|
||||
@@ -96,7 +101,7 @@ services:
|
||||
|
||||
fileview:
|
||||
container_name: "dootask-fileview-${APP_ID}"
|
||||
image: "kuaifan/fileview:4.1.0-SNAPSHOT-RC3"
|
||||
image: "kuaifan/fileview:4.1.0-SNAPSHOT-RC4"
|
||||
environment:
|
||||
TZ: "Asia/Shanghai"
|
||||
KK_CONTEXT_PATH: "/fileview"
|
||||
@@ -105,6 +110,36 @@ services:
|
||||
ipv4_address: "${APP_IPPR}.7"
|
||||
restart: unless-stopped
|
||||
|
||||
drawio-webapp:
|
||||
container_name: "dootask-drawio-webapp-${APP_ID}"
|
||||
image: "jgraph/drawio:16.6.1"
|
||||
volumes:
|
||||
- ./docker/drawio/webapp/index.html:/usr/local/tomcat/webapps/draw/index.html
|
||||
- ./docker/drawio/webapp/stencils:/usr/local/tomcat/webapps/draw/stencils
|
||||
- ./docker/drawio/webapp/js/app.min.js:/usr/local/tomcat/webapps/draw/js/app.min.js
|
||||
- ./docker/drawio/webapp/js/croppie/croppie.min.css:/usr/local/tomcat/webapps/draw/js/croppie/croppie.min.css
|
||||
- ./docker/drawio/webapp/js/diagramly/ElectronApp.js:/usr/local/tomcat/webapps/draw/js/diagramly/ElectronApp.js
|
||||
networks:
|
||||
extnetwork:
|
||||
ipv4_address: "${APP_IPPR}.8"
|
||||
environment:
|
||||
TZ: "Asia/Shanghai"
|
||||
depends_on:
|
||||
- drawio-export
|
||||
restart: unless-stopped
|
||||
|
||||
drawio-export:
|
||||
container_name: "dootask-drawio-export-${APP_ID}"
|
||||
image: "jgraph/export-server"
|
||||
networks:
|
||||
extnetwork:
|
||||
ipv4_address: "${APP_IPPR}.9"
|
||||
environment:
|
||||
TZ: "Asia/Shanghai"
|
||||
volumes:
|
||||
- ./docker/drawio/export/fonts:/usr/share/fonts/drawio
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
extnetwork:
|
||||
name: "dootask-networks-${APP_ID}"
|
||||
|
||||
1
docker/drawio/export/fonts/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
17
docker/drawio/webapp/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Change
|
||||
|
||||
## js/diagramly/ElectronApp.js
|
||||
|
||||
- 隐藏文件中的无用菜单
|
||||
|
||||
## js/app.min.js
|
||||
|
||||
- 隐藏帮助菜单
|
||||
- 取消未保存关闭窗口提示
|
||||
- `EmbedFile.prototype.getTitle=...` 改 `EmbedFile.prototype.getTitle=function(){return this.desc.title||(urlParams.title?decodeURIComponent(urlParams.title):"")}`
|
||||
- `c.insertTemplateEnabled&&!c.isOffline()&&this.addMenuItems(b,["insertTemplate"],d)` 改 `c.insertTemplateEnabled&&this.addMenuItems(b,["insertTemplate"],d)`
|
||||
- `390:270` 改 `390:285`
|
||||
|
||||
## index.html
|
||||
|
||||
- 隐藏加载中的提示
|
||||
477
docker/drawio/webapp/index.html
Normal file
@@ -0,0 +1,477 @@
|
||||
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5" ><![endif]-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Flowchart Maker & Online Diagram Software</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="Description" content="diagrams.net is free online diagram software for making flowcharts, process diagrams, org charts, UML, ER and network diagrams">
|
||||
<meta name="Keywords" content="diagram, online, flow chart, flowchart maker, uml, erd">
|
||||
<meta itemprop="name" content="diagrams.net - free flowchart maker and diagrams online">
|
||||
<meta itemprop="description" content="diagrams.net is a free online diagramming application and flowchart maker . You can use it to create UML, entity relationship,
|
||||
org charts, BPMN and BPM, database schema and networks. Also possible are telecommunication network, workflow, flowcharts, maps overlays and GIS, electronic
|
||||
circuit and social network diagrams.">
|
||||
<meta itemprop="image" content="https://lh4.googleusercontent.com/-cLKEldMbT_E/Tx8qXDuw6eI/AAAAAAAAAAs/Ke0pnlk8Gpg/w500-h344-k/BPMN%2Bdiagram%2Brc2f.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="msapplication-config" content="images/browserconfig.xml">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="theme-color" content="#d89000">
|
||||
<script type="text/javascript">
|
||||
window.EXPORT_URL = window.location.origin + "/drawio/export/";
|
||||
window.DRAWIO_LIGHTBOX_URL = window.location.origin + "/drawio/webapp";
|
||||
|
||||
/**
|
||||
* URL Parameters and protocol description are here:
|
||||
*
|
||||
* https://desk.draw.io/support/solutions/articles/16000042546-what-url-parameters-are-supported
|
||||
*
|
||||
* Parameters for developers:
|
||||
*
|
||||
* - dev=1: For developers only
|
||||
* - test=1: For developers only
|
||||
* - export=URL for export: For developers only
|
||||
* - ignoremime=1: For developers only (see DriveClient.js). Use Cmd-S to override mime.
|
||||
* - createindex=1: For developers only (see etc/build/README)
|
||||
* - filesupport=0: For developers only (see Editor.js in core)
|
||||
* - savesidebar=1: For developers only (see Sidebar.js)
|
||||
* - pages=1: For developers only (see Pages.js)
|
||||
* - lic=email: For developers only (see LicenseServlet.java)
|
||||
* --
|
||||
* - networkshapes=1: For testing network shapes (temporary)
|
||||
*/
|
||||
var urlParams = (function()
|
||||
{
|
||||
var result = new Object();
|
||||
var params = window.location.search.slice(1).split('&');
|
||||
|
||||
for (var i = 0; i < params.length; i++)
|
||||
{
|
||||
idx = params[i].indexOf('=');
|
||||
|
||||
if (idx > 0)
|
||||
{
|
||||
result[params[i].substring(0, idx)] = params[i].substring(idx + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
})();
|
||||
|
||||
// Forces CDN caches by passing URL parameters via URL hash
|
||||
if (window.location.hash != null && window.location.hash.substring(0, 2) == '#P')
|
||||
{
|
||||
try
|
||||
{
|
||||
urlParams = JSON.parse(decodeURIComponent(window.location.hash.substring(2)));
|
||||
|
||||
if (urlParams.hash != null)
|
||||
{
|
||||
window.location.hash = urlParams.hash;
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Global variable for desktop
|
||||
var mxIsElectron = window && window.process && window.process.type;
|
||||
|
||||
// Redirects page if required
|
||||
if (urlParams['dev'] != '1')
|
||||
{
|
||||
(function()
|
||||
{
|
||||
var proto = window.location.protocol;
|
||||
|
||||
if (!mxIsElectron)
|
||||
{
|
||||
var host = window.location.host;
|
||||
|
||||
// Redirects apex, drive and rt to www
|
||||
if (host === 'draw.io' || host === 'rt.draw.io' || host === 'drive.draw.io')
|
||||
{
|
||||
host = 'www.draw.io';
|
||||
}
|
||||
|
||||
var href = proto + '//' + host + window.location.href.substring(
|
||||
window.location.protocol.length +
|
||||
window.location.host.length + 2);
|
||||
|
||||
// Redirects if href changes
|
||||
if (href != window.location.href)
|
||||
{
|
||||
window.location.href = href;
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds meta tag to the page.
|
||||
*/
|
||||
function mxmeta(name, content, httpEquiv)
|
||||
{
|
||||
try
|
||||
{
|
||||
var s = document.createElement('meta');
|
||||
|
||||
if (name != null)
|
||||
{
|
||||
s.setAttribute('name', name);
|
||||
}
|
||||
|
||||
s.setAttribute('content', content);
|
||||
|
||||
if (httpEquiv != null)
|
||||
{
|
||||
s.setAttribute('http-equiv', httpEquiv);
|
||||
}
|
||||
|
||||
var t = document.getElementsByTagName('meta')[0];
|
||||
t.parentNode.insertBefore(s, t);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronously adds scripts to the page.
|
||||
*/
|
||||
function mxscript(src, onLoad, id, dataAppKey, noWrite)
|
||||
{
|
||||
var defer = onLoad == null && !noWrite;
|
||||
|
||||
if ((urlParams['dev'] != '1' && typeof document.createElement('canvas').getContext === "function") ||
|
||||
onLoad != null || noWrite)
|
||||
{
|
||||
var s = document.createElement('script');
|
||||
s.setAttribute('type', 'text/javascript');
|
||||
s.setAttribute('defer', 'true');
|
||||
s.setAttribute('src', src);
|
||||
|
||||
if (id != null)
|
||||
{
|
||||
s.setAttribute('id', id);
|
||||
}
|
||||
|
||||
if (dataAppKey != null)
|
||||
{
|
||||
s.setAttribute('data-app-key', dataAppKey);
|
||||
}
|
||||
|
||||
if (onLoad != null)
|
||||
{
|
||||
var r = false;
|
||||
|
||||
s.onload = s.onreadystatechange = function()
|
||||
{
|
||||
if (!r && (!this.readyState || this.readyState == 'complete'))
|
||||
{
|
||||
r = true;
|
||||
onLoad();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var t = document.getElementsByTagName('script')[0];
|
||||
|
||||
if (t != null)
|
||||
{
|
||||
t.parentNode.insertBefore(s, t);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
document.write('<script src="' + src + '"' + ((id != null) ? ' id="' + id +'" ' : '') +
|
||||
((dataAppKey != null) ? ' data-app-key="' + dataAppKey +'" ' : '') + '></scr' + 'ipt>');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Asynchronously adds scripts to the page.
|
||||
*/
|
||||
function mxinclude(src)
|
||||
{
|
||||
var g = document.createElement('script');
|
||||
g.type = 'text/javascript';
|
||||
g.async = true;
|
||||
g.src = src;
|
||||
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(g, s);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds meta tags with application name (depends on offline URL parameter)
|
||||
*/
|
||||
(function()
|
||||
{
|
||||
var name = 'diagrams.net';
|
||||
mxmeta('apple-mobile-web-app-title', name);
|
||||
mxmeta('application-name', name);
|
||||
|
||||
if (mxIsElectron)
|
||||
{
|
||||
mxmeta(null, 'default-src \'self\' \'unsafe-inline\'; connect-src \'self\' https://*.draw.io https://fonts.googleapis.com https://fonts.gstatic.com; img-src * data:; media-src *; font-src *; style-src-elem \'self\' \'unsafe-inline\' https://fonts.googleapis.com', 'Content-Security-Policy');
|
||||
}
|
||||
})();
|
||||
|
||||
// Checks for local storage
|
||||
var isLocalStorage = false;
|
||||
|
||||
try
|
||||
{
|
||||
isLocalStorage = urlParams['local'] != '1' && typeof(localStorage) != 'undefined';
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
var mxScriptsLoaded = false, mxWinLoaded = false;
|
||||
|
||||
function checkAllLoaded()
|
||||
{
|
||||
if (mxScriptsLoaded && mxWinLoaded)
|
||||
{
|
||||
App.main();
|
||||
}
|
||||
};
|
||||
|
||||
var t0 = new Date();
|
||||
|
||||
// Changes paths for local development environment
|
||||
if (urlParams['dev'] == '1')
|
||||
{
|
||||
// Used to request grapheditor/mxgraph sources in dev mode
|
||||
var mxDevUrl = document.location.protocol + '//devhost.jgraph.com/drawio/src/main';
|
||||
|
||||
// Used to request draw.io sources in dev mode
|
||||
var drawDevUrl = document.location.protocol + '//devhost.jgraph.com/drawio/src/main/webapp/';
|
||||
var geBasePath = drawDevUrl + '/js/grapheditor';
|
||||
var mxBasePath = mxDevUrl + '/mxgraph';
|
||||
|
||||
if (document.location.protocol == 'file:')
|
||||
{
|
||||
geBasePath = './js/grapheditor';
|
||||
mxBasePath = './mxgraph';
|
||||
drawDevUrl = './';
|
||||
|
||||
// Forces includes for dev environment in node.js
|
||||
mxForceIncludes = true;
|
||||
}
|
||||
|
||||
mxscript(drawDevUrl + 'js/PreConfig.js');
|
||||
mxscript(drawDevUrl + 'js/diagramly/Init.js');
|
||||
mxscript(geBasePath + '/Init.js');
|
||||
mxscript(mxBasePath + '/mxClient.js');
|
||||
|
||||
// Adds all JS code that depends on mxClient. This indirection via Devel.js is
|
||||
// required in some browsers to make sure mxClient.js (and the files that it
|
||||
// loads asynchronously) are available when the code loaded in Devel.js runs.
|
||||
mxscript(drawDevUrl + 'js/diagramly/Devel.js');
|
||||
|
||||
// Electron
|
||||
if (mxIsElectron)
|
||||
{
|
||||
mxscript('js/diagramly/DesktopLibrary.js');
|
||||
mxscript('js/diagramly/ElectronApp.js');
|
||||
}
|
||||
|
||||
mxscript(drawDevUrl + 'js/PostConfig.js');
|
||||
}
|
||||
else
|
||||
{
|
||||
(function()
|
||||
{
|
||||
var hostName = window.location.hostname;
|
||||
|
||||
// Supported domains are *.draw.io and the packaged version in Quip
|
||||
var supportedDomain = (hostName.substring(hostName.length - 8, hostName.length) === '.draw.io') ||
|
||||
(hostName.substring(hostName.length - 13, hostName.length) === '.diagrams.net');
|
||||
|
||||
function loadAppJS()
|
||||
{
|
||||
mxscript('js/app.min.js', function()
|
||||
{
|
||||
mxScriptsLoaded = true;
|
||||
checkAllLoaded();
|
||||
|
||||
// Electron
|
||||
if (mxIsElectron)
|
||||
{
|
||||
mxscript('js/diagramly/DesktopLibrary.js', function()
|
||||
{
|
||||
mxscript('js/diagramly/ElectronApp.js', function()
|
||||
{
|
||||
mxscript('js/extensions.min.js', function()
|
||||
{
|
||||
mxscript('js/stencils.min.js', function()
|
||||
{
|
||||
mxscript('js/shapes-14-6-5.min.js', function()
|
||||
{
|
||||
mxscript('js/PostConfig.js');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
else if (!supportedDomain)
|
||||
{
|
||||
mxscript('js/PostConfig.js');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (!supportedDomain || mxIsElectron)
|
||||
{
|
||||
mxscript('js/PreConfig.js', loadAppJS);
|
||||
}
|
||||
else
|
||||
{
|
||||
loadAppJS();
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
// Adds basic error handling
|
||||
window.onerror = function()
|
||||
{
|
||||
var status = document.getElementById('geStatus');
|
||||
|
||||
if (status != null)
|
||||
{
|
||||
status.innerHTML = 'Page could not be loaded. Please try refreshing.';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/plgmlhohecdddhbmmkncjdmlhcmaachm">
|
||||
<link rel="stylesheet" type="text/css" href="js/croppie/croppie.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="styles/grapheditor.css">
|
||||
<link rel="preconnect" href="https://storage.googleapis.com">
|
||||
<link rel="canonical" href="https://app.diagrams.net">
|
||||
<link rel="manifest" href="images/manifest.json">
|
||||
<style type="text/css">
|
||||
body { overflow:hidden; }
|
||||
div.picker { z-index: 10007; }
|
||||
.geSidebarContainer .geTitle input {
|
||||
font-size:8pt;
|
||||
color:#606060;
|
||||
}
|
||||
.geBlock {
|
||||
display: none;
|
||||
z-index:-3;
|
||||
margin:100px;
|
||||
margin-top:40px;
|
||||
margin-bottom:30px;
|
||||
padding:20px;
|
||||
text-align:center;
|
||||
min-width:50%;
|
||||
}
|
||||
.geBlock h1, .geBlock h2 {
|
||||
margin-top:0px;
|
||||
padding-top:0px;
|
||||
}
|
||||
.geEditor *:not(.geScrollable)::-webkit-scrollbar {
|
||||
width:14px;
|
||||
height:14px;
|
||||
}
|
||||
.geEditor ::-webkit-scrollbar-track {
|
||||
background-clip:padding-box;
|
||||
border:solid transparent;
|
||||
border-width:1px;
|
||||
}
|
||||
.geEditor ::-webkit-scrollbar-corner {
|
||||
background-color:transparent;
|
||||
}
|
||||
.geEditor ::-webkit-scrollbar-thumb {
|
||||
background-color:rgba(0,0,0,.1);
|
||||
background-clip:padding-box;
|
||||
border:solid transparent;
|
||||
border-radius:10px;
|
||||
}
|
||||
.geEditor ::-webkit-scrollbar-thumb:hover {
|
||||
background-color:rgba(0,0,0,.4);
|
||||
}
|
||||
.geTemplate {
|
||||
border:1px solid transparent;
|
||||
display:inline-block;
|
||||
_display:inline;
|
||||
vertical-align:top;
|
||||
border-radius:3px;
|
||||
overflow:hidden;
|
||||
font-size:14pt;
|
||||
cursor:pointer;
|
||||
margin:5px;
|
||||
}
|
||||
.geDialog h2 {
|
||||
line-height: 1.2;
|
||||
}
|
||||
</style>
|
||||
<!-- Workaround for binary XHR in IE 9/10, see App.loadUrl -->
|
||||
<!--[if (IE 9)|(IE 10)]><!-->
|
||||
<script type="text/vbscript">
|
||||
Function mxUtilsBinaryToArray(Binary)
|
||||
Dim i
|
||||
ReDim byteArray(LenB(Binary))
|
||||
For i = 1 To LenB(Binary)
|
||||
byteArray(i-1) = AscB(MidB(Binary, i, 1))
|
||||
Next
|
||||
mxUtilsBinaryToArray = byteArray
|
||||
End Function
|
||||
</script>
|
||||
<!--<![endif]-->
|
||||
</head>
|
||||
<body class="geEditor">
|
||||
<div id="geInfo">
|
||||
<div class="geBlock">
|
||||
<h1>Flowchart Maker and Online Diagram Software</h1>
|
||||
<p>
|
||||
diagrams.net (formerly draw.io) is free online diagram software. You can use it as a flowchart maker, network diagram software, to create UML online, as an ER diagram tool,
|
||||
to design database schema, to build BPMN online, as a circuit diagram maker, and more. draw.io can import .vsdx, Gliffy™ and Lucidchart™ files .
|
||||
</p>
|
||||
<h2 id="geStatus">Loading...</h2>
|
||||
<p>
|
||||
Please ensure JavaScript is enabled.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/**
|
||||
* Main
|
||||
*/
|
||||
if (navigator.userAgent != null && navigator.userAgent.toLowerCase().
|
||||
indexOf(' electron/') >= 0 && typeof process !== 'undefined' && process.versions.electron < 5)
|
||||
{
|
||||
// Redirects old Electron app to latest version
|
||||
var div = document.getElementById('geInfo');
|
||||
|
||||
if (div != null)
|
||||
{
|
||||
div.innerHTML = '<center><h2>You are using an out of date version of this app.<br>Please download the latest version ' +
|
||||
'<a href="https://github.com/jgraph/drawio-desktop/releases/latest" target="_blank">here</a>.</h2></center>';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (urlParams['dev'] != '1' && typeof document.createElement('canvas').getContext === "function")
|
||||
{
|
||||
window.addEventListener('load', function()
|
||||
{
|
||||
mxWinLoaded = true;
|
||||
checkAllLoaded();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
App.main();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
12282
docker/drawio/webapp/js/app.min.js
vendored
Normal file
1
docker/drawio/webapp/js/croppie/croppie.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.croppie-container{width:100%;height:100%}.croppie-container .cr-image{z-index:-1;position:absolute;top:0;left:0;transform-origin:0 0;max-height:none;max-width:none}.croppie-container .cr-boundary{position:relative;overflow:hidden;margin:0 auto;z-index:1;width:100%;height:100%}.croppie-container .cr-resizer,.croppie-container .cr-viewport{position:absolute;border:2px solid #fff;margin:auto;top:0;bottom:0;right:0;left:0;box-shadow:0 0 2000px 2000px rgba(0,0,0,.5);z-index:0}.croppie-container .cr-resizer{z-index:2;box-shadow:none;pointer-events:none}.croppie-container .cr-resizer-horisontal,.croppie-container .cr-resizer-vertical{position:absolute;pointer-events:all}.croppie-container .cr-resizer-horisontal::after,.croppie-container .cr-resizer-vertical::after{display:block;position:absolute;box-sizing:border-box;border:1px solid #000;background:#fff;width:10px;height:10px;content:''}.croppie-container .cr-resizer-vertical{bottom:-5px;cursor:row-resize;width:100%;height:10px}.croppie-container .cr-resizer-vertical::after{left:50%;margin-left:-5px}.croppie-container .cr-resizer-horisontal{right:-5px;cursor:col-resize;width:10px;height:100%}.croppie-container .cr-resizer-horisontal::after{top:50%;margin-top:-5px}.croppie-container .cr-original-image{display:none}.croppie-container .cr-vp-circle{border-radius:50%}.croppie-container .cr-overlay{z-index:1;position:absolute;cursor:move;touch-action:none}.croppie-container .cr-slider-wrap{width:75%;margin:15px auto;text-align:center}.croppie-result{position:relative;overflow:hidden}.croppie-result img{position:absolute}.croppie-container .cr-image,.croppie-container .cr-overlay,.croppie-container .cr-viewport{-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0)}.cr-slider{-webkit-appearance:none;width:300px;max-width:100%;padding-top:8px;padding-bottom:8px;background-color:transparent}.cr-slider::-webkit-slider-runnable-track{width:100%;height:3px;background:rgba(0,0,0,.5);border:0;border-radius:3px}.cr-slider::-webkit-slider-thumb{-webkit-appearance:none;border:none;height:16px;width:16px;border-radius:50%;background:#ddd;margin-top:-6px}.cr-slider:focus{outline:0}.cr-slider::-moz-range-track{width:100%;height:3px;background:rgba(0,0,0,.5);border:0;border-radius:3px}.cr-slider::-moz-range-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#ddd;margin-top:-6px}.cr-slider:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.cr-slider::-ms-track{width:100%;height:5px;background:0 0;border-color:transparent;border-width:6px 0;color:transparent}.cr-slider::-ms-fill-lower{background:rgba(0,0,0,.5);border-radius:10px}.cr-slider::-ms-fill-upper{background:rgba(0,0,0,.5);border-radius:10px}.cr-slider::-ms-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#ddd;margin-top:1px}.cr-slider:focus::-ms-fill-lower{background:rgba(0,0,0,.5)}.cr-slider:focus::-ms-fill-upper{background:rgba(0,0,0,.5)}.cr-rotate-controls{position:absolute;bottom:5px;left:5px;z-index:1}.cr-rotate-controls button{border:0;background:0 0}.cr-rotate-controls i:before{display:inline-block;font-style:normal;font-weight:900;font-size:22px}.cr-rotate-l i:before{content:'↺'}.cr-rotate-r i:before{content:'↻'}
|
||||
2206
docker/drawio/webapp/js/diagramly/ElectronApp.js
vendored
Normal file
BIN
docker/drawio/webapp/stencils/clipart/Credit_Card_128x128.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Database_128x128.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Doctor1_128x128.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Earth_globe_128x128.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Email_128x128.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Empty_Folder_128x128.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Firewall_02_128x128.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Full_Folder_128x128.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Gear_128x128.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Graph_128x128.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Laptop_128x128.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Lock_128x128.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
docker/drawio/webapp/stencils/clipart/MacBook_128x128.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Monitor_Tower_128x128.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Piggy_Bank_128x128.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Pilot1_128x128.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Printer_128x128.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
docker/drawio/webapp/stencils/clipart/Router_Icon_128x128.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |