Compare commits

...

606 Commits

Author SHA1 Message Date
kuaifan
9eaa575d1a build 2022-03-04 08:53:42 +08:00
kuaifan
996ed78a0e perf: 优化仪表盘角标数 2022-03-04 08:51:22 +08:00
kuaifan
1759572b2e perf: 优化客户端任务详情按command+s保存 2022-03-04 08:50:22 +08:00
kuaifan
0a9f9eea90 build 2022-03-04 00:00:26 +08:00
kuaifan
223fb540b1 perf: 优化文件重命名,支持按esc取消编辑 2022-03-03 23:57:58 +08:00
kuaifan
c5f1c95f7b perf: 任务详情打开操作菜单时按esc任务窗口隐藏了但是菜单还看见 2022-03-03 23:06:54 +08:00
kuaifan
3bbcbca926 build 2022-03-03 16:21:08 +08:00
kuaifan
63c1deb630 fix: md编辑器出现toc混乱的情况 2022-03-03 16:17:49 +08:00
kuaifan
424e2428fe pref: 上传文件名称过程显示错位的问题 2022-03-03 15:11:50 +08:00
kuaifan
2fdef45156 pref: 退出登录返回登录页而不是注册页 2022-03-03 15:11:34 +08:00
kuaifan
4cd4550a36 pref: 网络异常的情况下需提示网络异常而不是系统出错 2022-03-03 14:53:01 +08:00
kuaifan
16af625aef no msg 2022-03-03 14:35:21 +08:00
kuaifan
8e72794c07 pref: 任务详情当任务倒计时结束时显示"超期未完成"标签 2022-03-03 14:35:12 +08:00
kuaifan
d9cf6d7e1b pref: 优化脚本,支持部分服务器是docker compose命令 2022-03-03 14:22:25 +08:00
kuaifan
f4e4252227 perf: 优化已读回执 2022-03-03 11:41:54 +08:00
kuaifan
7107409b1b no message 2022-03-02 08:46:29 +08:00
kuaifan
6c086fab6f no message 2022-03-02 08:32:38 +08:00
kuaifan
d027d67e08 pref: 倒计时刚到到达0时会显示自定义才继续显示计时,且未显示超时标签 2022-03-02 08:25:38 +08:00
kuaifan
a7d9e635eb pref: 鼠标滑动至仪表盘中的待完成任务卡片时,卡片周围未显示光晕,且未显示为手指样式 2022-03-02 08:19:09 +08:00
kuaifan
e7196efaea pref: 优化任务详细描述显示 2022-03-02 08:17:30 +08:00
kuaifan
0a2a903c74 fix: 修复登录页设置下拉显示不全的情况 2022-03-02 07:59:53 +08:00
kuaifan
8104c26b19 处理任务简介出现"的情况 2022-03-02 07:48:29 +08:00
kuaifan
101d5c7eb0 build 2022-02-28 00:23:20 +08:00
kuaifan
cad253b85f feat: 导出任务功能 2022-02-28 00:21:48 +08:00
kuaifan
f17009a988 优化左上角菜单集合 2022-02-27 22:54:48 +08:00
kuaifan
1f76278d2b 优化客户端升级提示 2022-02-27 22:53:54 +08:00
kuaifan
e032d29c91 修复数据库字段填写错误 2022-02-27 21:12:11 +08:00
kuaifan
31d1b0c994 nomsg 2022-02-27 15:03:39 +08:00
kuaifan
611c6d415c perf: 记录任务工作流变化 2022-02-27 14:12:15 +08:00
kuaifan
e3b7ac00fd perf: 优化修改工作流的过程 2022-02-27 14:11:50 +08:00
kuaifan
7c952822db perf: 优化任务排序 2022-02-27 11:06:18 +08:00
kuaifan
b9e435c0e2 perf: 支持nodejs16+ 2022-02-27 10:59:38 +08:00
kuaifan
356d40e640 feat: 文件支持拖动到列表上传 2022-02-25 22:49:56 +08:00
kuaifan
123ffd4e66 no message 2022-02-25 22:47:56 +08:00
kuaifan
c952659620 fix: 修复无法预览pdf文件 2022-02-25 22:15:25 +08:00
kuaifan
ea8e1e9c57 优化样式 2022-02-25 15:14:20 +08:00
kuaifan
478d63893b fix: 无法浏览聊天图片的问题 2022-02-25 14:38:40 +08:00
kuaifan
b5ccba552f build 2022-02-25 09:12:28 +08:00
kuaifan
8d2ee364ba perf: 文件、聊天文件、任务文件预览优化(支持预览drawio、mind等) 2022-02-25 09:11:58 +08:00
kuaifan
14006068c8 优化样式 2022-02-25 00:13:15 +08:00
kuaifan
b4358ffc66 fix: 登录页重复填写sso地址无法保存的问题 2022-02-25 00:09:57 +08:00
kuaifan
33135b1df1 fix: 无法移动共享文件夹内创建的文件 2022-02-25 00:08:32 +08:00
kuaifan
9a66c38e01 fix: 客户端无法编辑office文件 2022-02-25 00:05:35 +08:00
kuaifan
81132ecab0 fix: 客户端无法下载文件 2022-02-25 00:05:12 +08:00
kuaifan
9d89af37be build 2022-02-24 09:04:03 +08:00
kuaifan
fbc8a36232 perf: 消息会话右键时隐藏滚动条 2022-02-24 09:03:15 +08:00
kuaifan
ea028ea1a1 perf: 页面高度足够时只滚动项目部分 2022-02-24 09:02:42 +08:00
kuaifan
35b1c12bb5 Merge branch 'develop' of ssh://git.gezi.vip:6006/gx/dootask
# Conflicts:
#	electron/package.json
#	package.json
#	public/js/app.js
2022-02-24 08:07:40 +08:00
kuaifan
fc89d96635 优化drawio不显示添加模板 2022-02-24 00:45:53 +08:00
kuaifan
27129652f2 no message 2022-02-23 23:31:59 +08:00
kuaifan
23ef992a7f 优化创建者和协助人机制 2022-02-23 23:30:33 +08:00
kuaifan
04533e17ec Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	app/Models/ProjectTask.php
#	public/css/app.css
#	public/js/app.js
#	public/js/build/361.js
#	public/js/build/412.js
#	public/js/build/499.js.LICENSE.txt
#	public/js/build/659.js
#	public/js/build/659.js.LICENSE.txt
#	public/js/build/726.js.LICENSE.txt
#	public/js/build/747.js
2022-02-23 23:18:50 +08:00
kuaifan
7f2fcba542 no message 2022-02-23 23:03:35 +08:00
kuaifan
c115e2f985 build 2022-02-23 22:57:33 +08:00
kuaifan
88642c2003 perf: 客户端版本更新提示关闭 2022-02-23 22:53:04 +08:00
kuaifan
2c678b5363 no message 2022-02-23 22:52:36 +08:00
kuaifan
bc3b72fafe no message 2022-02-23 20:58:04 +08:00
韦荣超
df3b8cf09c perf: 除了任务状态,任务创建人和协助人权限与负责人的保持一致 2022-02-23 17:25:54 +08:00
kuaifan
65393b7809 优化客户端升级提示 2022-02-23 16:45:38 +08:00
韦荣超
9782c849ad build: e7efaed08a250e1d885377e18742f6e948f481a0 2022-02-23 16:27:57 +08:00
韦荣超
e7efaed08a perf: 议仪表盘添加'待完成任务'选项 2022-02-23 16:26:07 +08:00
kuaifan
337b3e5b5d 优化终端命令 2022-02-23 16:03:54 +08:00
kuaifan
7b1b7d1372 perf: 创建者及协助人可以修改任务但不能修改任务状态 2022-02-23 16:00:54 +08:00
韦荣超
e5b838a2b3 build: f9cc2ceb11efa4b280d4b6344f69e8a2be19bd59 2022-02-23 15:55:41 +08:00
韦荣超
f9cc2ceb11 perf: 优化点击右键时选中框缺少右侧线条 2022-02-23 15:51:24 +08:00
kuaifan
4f107c5618 perf: 客户端修改文件未保存关闭窗口前提示 2022-02-23 15:16:36 +08:00
韦荣超
b1c5aaff43 build: d0a4473e2bb057050959300e333eaea7cdc7a6ff 2022-02-23 15:04:21 +08:00
韦荣超
d0a4473e2b fix: 项目、消息置顶样式修改 2022-02-23 15:02:05 +08:00
kuaifan
1c79361094 fix: 修复客户端任务新窗口无法修改任务等级 2022-02-23 14:53:15 +08:00
kuaifan
f72114c223 优化electron通信 2022-02-23 14:52:39 +08:00
kuaifan
dbd59cd958 no message 2022-02-23 12:51:27 +08:00
韦荣超
d65f8a3c82 build: 96587a4e45ed4c89c12da4a58809f9c3ec88cb0f 2022-02-23 12:09:41 +08:00
韦荣超
96587a4e45 perf: 修改项目及消息置顶样式 2022-02-23 12:06:43 +08:00
韦荣超
76ab47c82e perf: 任务创建人和协助人可修改任务内容和详情,但不可修改任务状态 2022-02-23 11:09:12 +08:00
kuaifan
af8344c555 fix: 修复邮箱大写报错的问题 2022-02-23 10:03:15 +08:00
kuaifan
10ff02b8a0 perf: 修改任务时间日志 2022-02-23 09:58:55 +08:00
kuaifan
cb6cf1e34b 优化drawio 2022-02-23 09:41:19 +08:00
kuaifan
953e924aa2 build 2022-02-23 00:26:44 +08:00
kuaifan
2e0c262d32 no message 2022-02-23 00:19:32 +08:00
kuaifan
cbf2e6a140 no message 2022-02-23 00:07:27 +08:00
kuaifan
49c5a9f621 取消 electron-renderer 2022-02-22 21:02:27 +08:00
kuaifan
fb8f63f305 取消drawio网页版未保存提示 2022-02-22 20:51:07 +08:00
韦荣超
49ff61ad65 build: 695fb60aa4f4e1fcebc82099d3ec6b38ff6bc284 2022-02-22 17:43:08 +08:00
韦荣超
695fb60aa4 perf: 任务聊天中发送图片时,回车可确定发送 2022-02-22 17:39:22 +08:00
韦荣超
da20fafa39 build: d6a9ecd91287555b701ba9f92f64bd430b9574e7 2022-02-22 17:20:47 +08:00
韦荣超
d6a9ecd912 fix: 项目列表滚动'置顶'框隐藏 2022-02-22 17:19:08 +08:00
韦荣超
f95d721c9c build: 69d6417985a0f17bc053ac5786711ac6f3bceca5 2022-02-22 16:51:50 +08:00
韦荣超
69d6417985 feat: 项目列表添加置顶功能 2022-02-22 16:49:53 +08:00
kuaifan
ab2bbc28c8 no message 2022-02-22 14:24:27 +08:00
kuaifan
b0b39429ed build 2022-02-22 12:46:38 +08:00
kuaifan
ff14cbc752 feat: 支持文本、图表、思维导图下载上传 2022-02-22 12:24:16 +08:00
kuaifan
dd013aaaa3 优化drawio 2022-02-21 20:23:15 +08:00
kuaifan
119f61ef67 build 2022-02-21 18:49:38 +08:00
kuaifan
a99588c766 优化drawio路径 2022-02-21 18:11:41 +08:00
kuaifan
6f35fe9936 客户端drawio本地化 2022-02-21 17:46:00 +08:00
kuaifan
3112425e43 build 2022-02-20 21:27:52 +08:00
kuaifan
1ab3aefaa5 perf: 更新流程图表 2022-02-20 17:15:01 +08:00
kuaifan
be08732e6b 修复暗黑模式流程图无法正常浏览的问题 2022-02-19 14:43:11 +08:00
kuaifan
97b58b5f9a build 2022-02-19 14:31:10 +08:00
kuaifan
e4855875cf 优化文件多选部分 2022-02-19 14:29:22 +08:00
kuaifan
7a267cc07b 整理代码 2022-02-19 11:44:03 +08:00
kuaifan
f4f351cf9d Merge branch 'develop' of ssh://git.gezi.vip:6006/gx/dootask
# Conflicts:
#	public/css/app.css
#	public/js/app.js
#	public/js/build/208.js
#	public/js/build/389.js
#	public/js/build/406.js
#	public/js/build/406.js.LICENSE.txt
#	public/js/build/423.js
#	public/js/build/459.js
#	public/js/build/459.js.LICENSE.txt
#	public/js/build/688.js
#	public/js/build/688.js.LICENSE.txt
#	public/js/build/726.js
#	public/js/build/755.js
#	public/js/build/93.js
#	public/js/build/954.js
2022-02-19 09:30:13 +08:00
kuaifan
970b811ab9 no msg 2022-02-19 09:27:30 +08:00
kuaifan
99604dbe35 升级onlyoffice 2022-02-18 19:58:21 +08:00
韦荣超
09b1d89718 build: ccb889233c31d2d180cba378faa009052c24db70 2022-02-18 19:20:42 +08:00
韦荣超
9d1a9f3134 build: ccb889233c31d2d180cba378faa009052c24db70 2022-02-18 19:20:35 +08:00
韦荣超
ccb889233c fix: 【文件】右键多选所有文件复选框显示,取消所有选中消失 2022-02-18 19:19:05 +08:00
韦荣超
939f2cbf97 build: 4bd8835f239ca49a7b2cd11bd3dd4128611b14f5 2022-02-18 18:50:38 +08:00
韦荣超
580e0cb36a build: 4bd8835f239ca49a7b2cd11bd3dd4128611b14f5 2022-02-18 18:50:29 +08:00
韦荣超
4bd8835f23 fix: 消息:列表滚动隐藏'置顶'文案 2022-02-18 18:48:43 +08:00
韦荣超
22f32da0c5 build: aa8a094383bb60ea8313b4f50f80b48df15cff3e 2022-02-18 18:42:49 +08:00
韦荣超
aa8a094383 fix: 文件:列表模式右键后已选内容会错乱修复 2022-02-18 18:40:39 +08:00
韦荣超
4a72b7f089 build: a33e4905cf890fe713a368c9dee97e90c727150d 2022-02-18 17:03:18 +08:00
韦荣超
a33e4905cf fix: 消息:列表滚动右键Y轴值判断错误修复 2022-02-18 17:01:33 +08:00
韦荣超
40bd2f0742 build: 1fd73fe79e315e3fccd9f35a8ecf668c396d707a 2022-02-18 16:45:55 +08:00
韦荣超
1fd73fe79e fix: 消息:列表滚动在任意位置右键菜单错位问题修复 2022-02-18 16:43:27 +08:00
韦荣超
fd7d3e06f4 build: 7a4d27da69e9afaad5dc4b11441a3e8726e785a2 2022-02-18 16:17:41 +08:00
韦荣超
7a4d27da69 perf: 【文件】多个选择剪切功能与右键剪切重复,数据处理应该合拼;方格列表默认不显示复选框,右键菜单新增一个多选菜单 2022-02-18 16:14:54 +08:00
韦荣超
afbadf7d81 build: 4f2c0e94d94e91ab58238cca0d2df0bf978f5d0d 2022-02-18 14:02:22 +08:00
韦荣超
7b65c64431 build: 4f2c0e94d94e91ab58238cca0d2df0bf978f5d0d 2022-02-18 14:02:00 +08:00
韦荣超
4f2c0e94d9 perf: 优化注册提示 2022-02-18 13:59:40 +08:00
韦荣超
7593d7a3e9 build: 3400d1e8033d7da2067e1c9a9b6a8fc3a25cd7cf 2022-02-18 11:54:29 +08:00
韦荣超
35ddc4a472 Merge remote-tracking branch 'origin/chao-flow' into develop 2022-02-18 11:52:22 +08:00
韦荣超
3400d1e803 fix: 为引入组件报错 2022-02-18 11:51:11 +08:00
韦荣超
f672428fde build: 1521d1e8831a60f5f737a9480d2e8726ac98c4a5 2022-02-18 11:29:33 +08:00
韦荣超
4171d993d0 Merge remote-tracking branch 'origin/chao-flow' into develop
# Conflicts:
#	public/js/app.js
2022-02-18 11:25:49 +08:00
韦荣超
d22310ea31 build: 1521d1e8831a60f5f737a9480d2e8726ac98c4a5 2022-02-18 11:23:38 +08:00
韦荣超
1521d1e883 fix: 【文件】流程图只读接入新组件及删除旧组件引入代码 2022-02-18 11:21:56 +08:00
韦荣超
0412615f6e build: 2bd666efe7b4f2dc2bd250bcaf0b9abbf23b2584 2022-02-18 10:11:48 +08:00
韦荣超
2bd666efe7 perf: 消息列表需支持多个置顶 2022-02-18 10:10:17 +08:00
kuaifan
541e1f760b build 2022-02-17 20:39:47 +08:00
kuaifan
f176bed436 隐藏浏览流程图的滚动条(流程图使用拖动浏览) 2022-02-17 19:12:29 +08:00
kuaifan
403545cd9b perf: 没有时间还显示时间倒计时的问题 2022-02-17 19:11:52 +08:00
kuaifan
54e4ed27ae perf: 优化修改任务时间记录 2022-02-17 19:11:23 +08:00
kuaifan
e5ddf5616a perf: 优化文件重命名 2022-02-17 18:48:18 +08:00
kuaifan
967c4f04d9 fix: 修复复制文件内容为空的问题 2022-02-17 18:34:27 +08:00
kuaifan
36d4d445a6 fix: 流程图预览暗黑模式下看不见文字的问题 2022-02-17 17:47:51 +08:00
kuaifan
5ed0ae2fa9 perf: 主任务归档时同步子任务归档 2022-02-17 17:27:11 +08:00
韦荣超
503a719609 build: 2dcbba63cbf2f77a326dd4ab51acba1ad090ddcb 2022-02-17 15:22:31 +08:00
韦荣超
2dcbba63cb fix: 【消息】置顶会话在子类tab中排序错误修改 2022-02-17 15:20:47 +08:00
韦荣超
5e86bdfda7 build: 4f74a0440c4a3a8b214ff6b7c058dcb50ac0a99a 2022-02-17 14:43:21 +08:00
韦荣超
4f74a0440c perf: 【注册】校验参数是否合法顺序优化 2022-02-17 14:41:49 +08:00
韦荣超
858709f610 build: 6689d48fadfc1f1c5ddfe0ecaa05f1d75b70161e 2022-02-17 10:44:23 +08:00
韦荣超
6689d48fad perf: 【消息】列表置顶会话加背景颜色 2022-02-17 10:42:28 +08:00
韦荣超
8581a7c308 build: 9878efb198c5efe93eaa290c588e6e033029c8c3 2022-02-17 09:37:08 +08:00
韦荣超
9878efb198 perf: 【消息】列表取消置顶 2022-02-17 09:35:32 +08:00
韦荣超
9855c50367 build: 04c59041e0a135e835530baad95e2402d7b02a37 2022-02-17 09:19:35 +08:00
韦荣超
04c59041e0 feat: 【消息】列表增加点击右键置顶该聊天功能 2022-02-17 09:17:19 +08:00
kuaifan
7fb1ecc9b0 build 2022-02-15 15:44:43 +08:00
kuaifan
ecd2cdd28e perf: 会员选择输入框不刷新的情况 2022-02-15 15:41:45 +08:00
kuaifan
4b0ad22f8d fix: 取消(完成状态)变为待测试(改变状态),如果有状态负责人应该把状态负责人加上 2022-02-15 15:30:35 +08:00
kuaifan
789558da6c perf: 未完成状态禁止归档 2022-02-15 15:21:11 +08:00
kuaifan
3f96117a57 no message 2022-02-15 15:11:29 +08:00
kuaifan
282d6b5746 升级electron 2022-02-15 15:11:20 +08:00
kuaifan
86d2bb9e2a 升级tinymce 2022-02-15 15:11:00 +08:00
韦荣超
ab4fbf0437 Merge remote-tracking branch 'origin/chao-flow' into develop 2022-02-15 15:09:49 +08:00
韦荣超
ae527a78ef perf: 【文件】流程图新组件添加切换皮肤主题功能 2022-02-15 15:02:32 +08:00
韦荣超
8218868681 Merge remote-tracking branch 'origin/chao-flow' into develop 2022-02-15 14:15:00 +08:00
韦荣超
251f4ca4ac fix: 【文件】流程图新组件添加缺失图片 2022-02-15 14:13:42 +08:00
韦荣超
5d5c8ced24 Merge remote-tracking branch 'origin/chao-flow' into develop 2022-02-15 11:12:20 +08:00
韦荣超
f0ec9b7826 fix: 【文件】流程图新组件复选框在360浏览器中显示异常修改 2022-02-15 11:05:48 +08:00
kuaifan
90b8e785a4 fix: 已完成子任务还出现时间跳动的情况 2022-02-15 10:39:19 +08:00
韦荣超
cb11a71ff5 build: 重新编译 2022-02-15 09:11:14 +08:00
韦荣超
54dd90fc4a Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	public/js/app.js
#	public/js/build/423.js
#	public/js/build/726.js
#	public/js/build/93.js
2022-02-15 09:05:20 +08:00
kuaifan
4bb080bd58 vue data内支持this.$L 2022-02-14 18:59:53 +08:00
韦荣超
5115a048a3 Merge remote-tracking branch 'origin/chao-flow' into develop
# Conflicts:
#	public/css/app.css
#	public/js/app.js
#	public/js/build/423.js
#	public/js/build/93.js
2022-02-14 18:17:09 +08:00
韦荣超
259351cdd2 fix: 【文件】流程图去掉ctr+s提示框 2022-02-14 18:13:16 +08:00
kuaifan
972b8f83bc 添加任务详细描述取消文件上传 2022-02-14 17:37:11 +08:00
kuaifan
9f6b1c1e25 perf: 优化iPad兼容 2022-02-14 17:34:07 +08:00
kuaifan
6229a103aa perf: 优化右下角、登录页主题设置 2022-02-14 17:02:51 +08:00
韦荣超
84116e531b build: 2c1b944b7ced9b1401be1d263b38c8c546bb33cf 2022-02-14 13:42:45 +08:00
韦荣超
2c1b944b7c fix: 【文件】新版流程图右侧及底部被隐藏问题修改 2022-02-14 13:40:37 +08:00
kuaifan
0e66ca148d build 2022-02-13 14:10:06 +08:00
kuaifan
ce70c1ca3a perf: 单条消息最长2000个字符,超过自动分割发送,总最长20000 2022-02-12 17:01:12 +08:00
韦荣超
56b91a59a2 Merge remote-tracking branch 'origin/develop' into develop 2022-02-12 16:02:04 +08:00
kuaifan
8f1fbcb19e no message 2022-02-12 15:10:30 +08:00
韦荣超
a4799a1bc0 “新版本流程图文件” 2022-02-12 15:04:11 +08:00
韦荣超
cc24ff22e9 perf: 去掉刷新提示及前端报错 2022-02-12 15:02:10 +08:00
kuaifan
72ca335c4c perf: 下载、查看任务文件权限改为所有项目成员 2022-02-12 14:24:36 +08:00
kuaifan
0ef6476e58 perf: 任务详细描述取消文件上传 2022-02-12 14:15:23 +08:00
kuaifan
6ba63d1466 fix: 修复聊天mp4文件无法预览的问题 2022-02-12 14:14:18 +08:00
kuaifan
f9d28e1b6b perf: 优化任务详情拖动发送文件 2022-02-12 13:49:30 +08:00
韦荣超
63534d3eb5 feat: 更新流程图组件 2022-02-12 11:54:56 +08:00
韦荣超
2ab2cf01db “新版本流程图文件” 2022-02-12 11:51:37 +08:00
kuaifan
2fc039dd70 perf: 优化通知 2022-02-12 09:50:02 +08:00
韦荣超
230d1a1f86 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	app/Http/Controllers/Api/FileController.php
#	public/js/build/208.js
#	public/js/build/252.js
#	public/js/build/400.js
#	public/js/build/76.js
2022-02-11 11:01:05 +08:00
kuaifan
f7921bf388 build 2022-02-10 21:18:18 +08:00
kuaifan
feed984ba8 perf: 兼容iPad端 2022-02-10 21:03:36 +08:00
kuaifan
5324861f16 perf: 手动切换账号提示“项目不存在或不在成员列表内”的情况 2022-02-10 20:34:13 +08:00
kuaifan
ae80939d2e perf: 任务详情 2022-02-10 20:33:35 +08:00
kuaifan
c117e4b087 fix: 修复文件上传一直出现loading的情况 2022-02-10 20:33:14 +08:00
kuaifan
96580e2284 perf: 该文件版本已经改变了。该页面将被重新加载 2022-02-10 20:12:17 +08:00
kuaifan
bc3932c8b8 perf: 点击切换语言一级菜单出现的兼容问题 2022-02-10 17:10:18 +08:00
kuaifan
f7cd4f34d3 fix: 已存在的任务新添加负责人不出现在任务群聊里 2022-02-10 17:09:44 +08:00
kuaifan
3a76f51707 fix: 初次安装失败的情况 2022-02-10 16:41:07 +08:00
kuaifan
041ba8f2ed 优化onlyoffice样式 2022-02-09 21:52:04 +08:00
kuaifan
17e5d15b1b build 2022-02-09 21:50:12 +08:00
kuaifan
a6ba59eac0 perf: 上传文件夹 2022-02-09 21:04:18 +08:00
kuaifan
e89ff02b59 fix: 客户端偶尔出现无法打开文件的情况 2022-02-09 21:04:03 +08:00
kuaifan
b28be29dc8 build 2022-02-09 17:29:36 +08:00
kuaifan
9ad85e01de perf: 该文件版本已经改变了。该页面将被重新加载 2022-02-09 17:27:59 +08:00
kuaifan
d49790ba78 perf: 团队管理新增身份筛选项 2022-02-09 17:09:40 +08:00
kuaifan
598f01de95 no message 2022-02-09 16:59:48 +08:00
kuaifan
992e137339 perf: 任务文件支持更多格式上传 2022-02-09 16:59:42 +08:00
kuaifan
124666cca6 优化系统设置路由菜单 2022-02-09 16:13:13 +08:00
kuaifan
848359bf7b feat: 添加项目支持默认工作流 2022-02-09 16:05:25 +08:00
kuaifan
fb24af1900 feat: 新增自定义添加项目时的项目模板 2022-02-09 16:05:02 +08:00
韦荣超
3d6df3cc09 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	app/Http/Controllers/Api/DialogController.php
#	app/Http/Controllers/Api/ProjectController.php
#	app/Models/Project.php
#	electron/package.json
#	package.json
#	public/css/app.css
#	public/js/app.js
#	public/js/build/146.js
#	public/js/build/188.js
#	public/js/build/188.js.LICENSE.txt
#	public/js/build/244.js
#	public/js/build/244.js.LICENSE.txt
#	public/js/build/278.js
#	public/js/build/278.js.LICENSE.txt
#	public/js/build/30.js
#	public/js/build/423.js
#	public/js/build/43.js
#	public/js/build/525.js
#	public/js/build/644.js.LICENSE.txt
#	public/js/build/660.js
#	public/js/build/766.js
#	public/js/build/831.js
#	public/js/build/893.js
#	public/js/build/919.js
#	public/js/build/919.js.LICENSE.txt
#	public/js/build/934.js
#	public/js/build/934.js.LICENSE.txt
#	resources/assets/js/pages/manage/components/DialogList.vue
#	resources/assets/js/pages/manage/components/DialogWrapper.vue
#	resources/assets/js/pages/manage/components/TaskAdd.vue
#	resources/assets/sass/components/report.scss
2022-02-07 10:50:38 +08:00
kuaifan
ea58ed46f2 doc 2022-01-29 16:10:31 +08:00
kuaifan
32817a4275 build 2022-01-29 16:04:28 +08:00
kuaifan
95033cd5b7 perf: 优化项目筛选工作流 2022-01-29 16:02:49 +08:00
kuaifan
9999548bc2 perf: 优化撤回消息 2022-01-29 13:39:16 +08:00
kuaifan
3d04bd4444 build 2022-01-28 18:29:15 +08:00
kuaifan
21e618cca2 优化了一些代码 2022-01-28 18:27:47 +08:00
kuaifan
02eb386155 撤回消息优化 2022-01-28 15:09:57 +08:00
kuaifan
35bd038802 Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	app/Http/Controllers/Api/DialogController.php
#	app/Http/Controllers/Api/ProjectController.php
#	electron/package.json
#	package.json
#	public/css/app.css
#	public/docs/assets/main.bundle.js
#	public/js/app.js
#	public/js/build/146.js.LICENSE.txt
#	public/js/build/161.js
#	public/js/build/199.js
#	public/js/build/199.js.LICENSE.txt
#	public/js/build/218.js
#	public/js/build/218.js.LICENSE.txt
#	public/js/build/244.js
#	public/js/build/244.js.LICENSE.txt
#	public/js/build/309.js
#	public/js/build/423.js
#	public/js/build/43.js
#	public/js/build/693.js
#	public/js/build/717.js
#	public/js/build/717.js.LICENSE.txt
#	public/js/build/79.js.LICENSE.txt
#	resources/assets/js/pages/manage/components/TaskAdd.vue
2022-01-28 14:39:43 +08:00
kuaifan
1ff59aee56 perf: 优化修改工作流未保存关闭提示 2022-01-28 13:25:51 +08:00
kuaifan
d64fae1832 perf: 查看工作报告图片显示不全的问题 2022-01-28 13:12:11 +08:00
kuaifan
ae169810d0 perf: 优化UserInput组件 2022-01-28 13:04:54 +08:00
kuaifan
3c7619098a build 2022-01-28 01:20:36 +08:00
kuaifan
a881bfd63b fix: 客户端编辑文件不显示协助成员 2022-01-28 01:12:22 +08:00
kuaifan
09d3131d46 fix: 添加任务时选择任务组无效 2022-01-28 00:12:51 +08:00
kuaifan
53550b8975 fix: 工作报告弹窗被遮挡的问题 2022-01-27 23:55:13 +08:00
kuaifan
569164ed56 fix: 客户端任务窗口首次聊天失败的问题 2022-01-27 23:44:06 +08:00
kuaifan
f61d79d53e 客户端office查看修改窗口隐藏自带的标题 2022-01-27 23:28:38 +08:00
kuaifan
bba28d6b57 fix: 文件右键打开失效 2022-01-27 23:17:41 +08:00
Mr.Huan
469f30044a build: 0993d877990fcb77b3f865268370cdb04de18afd 2022-01-27 17:12:58 +08:00
Mr.Huan
0993d87799 fix: 修复工作汇报正文被图片撑开页面的问题 2022-01-27 17:11:48 +08:00
Mr.Huan
09fa33236f build: 4e9abd65120687e4bb6c84a037596c4c725f96fc 2022-01-27 16:33:25 +08:00
Mr.Huan
4e9abd6512 perf: 开放文件夹移动功能 2022-01-27 16:31:58 +08:00
Mr.Huan
2996c0b38e build: 36e366abe0f3af6b2693708a5f012b2ef7fcc57a 2022-01-27 15:58:26 +08:00
Mr.Huan
36e366abe0 perf: 优化文件模块用户体验 2022-01-27 15:55:44 +08:00
Mr.Huan
f9c6c6c127 build: 34772ef2bf11535c588e594ac976cd0706fc3e91 2022-01-27 14:35:17 +08:00
Mr.Huan
34772ef2bf perf: 文件增加取消选择按钮 2022-01-27 14:33:08 +08:00
Mr.Huan
305af935a7 feat: 文件支持批量移动 2022-01-27 14:24:01 +08:00
kuaifan
33f3c9acbf build 2022-01-27 12:25:10 +08:00
kuaifan
9e560c79ae 优化聊天消息附件支持预览 2022-01-27 12:17:30 +08:00
kuaifan
705d7f3da0 perf: 聊天消息附件支持预览 2022-01-27 00:44:49 +08:00
kuaifan
08372facd7 fix: 移动端无法上传任务文件的问题 2022-01-26 19:11:16 +08:00
kuaifan
886baa427b perf: 仪表盘鼠标滑过时间显示完整时间 2022-01-26 18:12:21 +08:00
kuaifan
8ff94bc138 fix: 任务中没有聊天记录时,发送图片无法成功 2022-01-26 18:05:53 +08:00
Mr.Huan
d0438390cc build: d22266a94725640f85bceb9978c13449dfa5c35c 2022-01-26 17:27:42 +08:00
Mr.Huan
d22266a947 perf: 聊天页面图片尺寸缩小至180px 2022-01-26 17:26:29 +08:00
Mr.Huan
e34fb25759 fix: 修复消息撤回文字提示在第一条时会被顶部遮住的问题 2022-01-26 17:23:20 +08:00
Mr.Huan
675955b2e6 build: 4516bce0eecf7f6224fde7cbd0c1de74be3f05af 2022-01-26 17:14:58 +08:00
Mr.Huan
4516bce0ee fix: 修复个人对话为空时无法重复打开该对话的问题 2022-01-26 17:13:20 +08:00
Mr.Huan
4853fbcec3 build: 72e5f9a83ed00e9514c3d6e9991d7ddf3193659c 2022-01-26 15:04:03 +08:00
Mr.Huan
72e5f9a83e fix: 修复消息撤回文字提示在第一条时会被顶部遮住的问题 2022-01-26 15:02:56 +08:00
Mr.Huan
1a1ddc34a2 build: 40ebfb676c044b8f02d3d43105de34b2762fab9c 2022-01-26 14:46:37 +08:00
Mr.Huan
40ebfb676c perf: 头像上传图片浏览组件增加空提醒 2022-01-26 14:43:29 +08:00
kuaifan
3bb7e958dc perf: macOS角标首次不显示的问题 2022-01-26 11:46:11 +08:00
Mr.Huan
19dd16fcf0 docs: 补充消息撤回功能API文档 2022-01-26 09:48:35 +08:00
kuaifan
f596749645 Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	electron/package.json
#	package.json
#	public/css/app.css
#	public/js/app.js
#	public/js/build/120.js
#	public/js/build/120.js.LICENSE.txt
#	public/js/build/146.js.LICENSE.txt
#	public/js/build/161.js
#	public/js/build/284.js
#	public/js/build/309.js
#	public/js/build/400.js
#	public/js/build/644.js
#	public/js/build/644.js.LICENSE.txt
#	public/js/build/79.js.LICENSE.txt
#	resources/assets/js/pages/manage/components/TaskAdd.vue
2022-01-26 09:35:16 +08:00
kuaifan
482813ea88 build 2022-01-26 01:33:53 +08:00
kuaifan
60e47e85a3 perf: 添加任务时如果自己不是任务负责人可选择加入协助人员列表 2022-01-26 01:12:00 +08:00
kuaifan
22c06fee5e 剔除模式代码优化 2022-01-26 00:02:15 +08:00
kuaifan
4a42ce87a2 feat: 工作流程负责人新增剔除模式(改变任务负责人并保留操作状态的人员) 2022-01-25 23:46:47 +08:00
kuaifan
a98e4dbcd4 perf: 文件列表模式右键重命名无效 2022-01-25 23:12:42 +08:00
kuaifan
7baa37ccd1 fix: macOS客户端首次不加载角标的问题 2022-01-25 23:06:08 +08:00
kuaifan
417017add9 优化编辑器皮肤 2022-01-25 23:04:51 +08:00
Mr.Huan
b5e3cc2503 build: 2e11fe2b5852ddaa7e14bf32f4e4e246ce539084 2022-01-25 16:51:32 +08:00
Mr.Huan
2e11fe2b58 feat: 文件网格模式支持批量删除文件 2022-01-25 16:49:54 +08:00
Mr.Huan
70f1258bab feat: 文件表格支持批量删除文件 2022-01-25 16:49:54 +08:00
韦荣超
d91fa33330 fix: 工作流列表接口用作筛选时不用传多余参数 2022-01-25 16:28:15 +08:00
kuaifan
8f0b5dc049 Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	app/Http/Controllers/Api/DialogController.php
#	electron/package.json
#	package.json
#	public/css/app.css
#	public/js/app.js
#	public/js/build/146.js
#	public/js/build/146.js.LICENSE.txt
#	public/js/build/178.js
#	public/js/build/178.js.LICENSE.txt
#	public/js/build/199.js
#	public/js/build/199.js.LICENSE.txt
#	public/js/build/309.js
#	public/js/build/328.js.LICENSE.txt
#	public/js/build/388.js
#	public/js/build/43.js
#	public/js/build/46.js.LICENSE.txt
#	public/js/build/857.js
#	public/js/build/857.js.LICENSE.txt
#	public/js/build/893.js
2022-01-25 16:12:53 +08:00
kuaifan
7f14a82053 perf: 任务群聊中拖拽文件或者照片时并未有确认窗口 2022-01-25 16:06:26 +08:00
kuaifan
b05db6d458 fix: 主任务负责人无法修改子任务负责人的问题 2022-01-25 15:49:22 +08:00
kuaifan
ea87092d73 fix: 设置主任务时间跟子任务没有交集时子任务时间改为跟主任务一致 2022-01-25 15:28:42 +08:00
kuaifan
f468f7ae27 build 2022-01-25 15:05:46 +08:00
kuaifan
f12c79d292 pref: 待处理时进入动态中点击重置,重置提示中的参数为空,点击确定后,提示流程不存在或者删除 2022-01-25 15:04:03 +08:00
kuaifan
6e76514a24 pref: 添加子任务时输入框为空,回车时提示的图标错误 2022-01-25 15:03:43 +08:00
kuaifan
88f3d3821e fix: 搜索后,点击查看已归档的任务,仍然显示这个搜索框 2022-01-25 15:03:24 +08:00
kuaifan
bed2e20a91 fix: 修复归档任务无法查看日志的问题 2022-01-25 14:50:39 +08:00
kuaifan
c7700bdfef perf: 上传文件夹应该保持目录结构 2022-01-25 14:46:52 +08:00
Mr.Huan
778f6367f9 fix: 动态中点击重置按钮后流程的字体颜色和背景颜色并未正确显示 2022-01-25 11:00:59 +08:00
Mr.Huan
6115eea401 build: ff62ef729a15d819970f155d657bbac2e85e2760 2022-01-25 09:39:03 +08:00
Mr.Huan
ff62ef729a perf: 支持xmind,rp格式文件上传 2022-01-25 09:37:45 +08:00
kuaifan
d0a432164d build 2022-01-25 02:06:27 +08:00
kuaifan
0f8d9e64ef no message 2022-01-25 01:30:55 +08:00
kuaifan
d1f5096a16 删除luckysheet 2022-01-25 00:32:51 +08:00
kuaifan
003de399dc 支持在线编辑代码 2022-01-25 00:24:47 +08:00
kuaifan
36989ce5ff feat: 新增文本文件编辑功能 2022-01-24 20:33:27 +08:00
Mr.Huan
fa987b4e30 build: b47c49424051a2233e0e826dbf9b4ce3447e4b89 2022-01-24 15:50:32 +08:00
Mr.Huan
b47c494240 fix: 修复任务负责人选择弹窗不随页面滚动的问题 2022-01-24 15:35:04 +08:00
kuaifan
1ab8a19f8e fix: 客户端无法下载聊天文件的问题 2022-01-24 15:18:35 +08:00
Mr.Huan
a31134195b build: 5580a4ee3db922ec516178231a00d121b29fdc88 2022-01-24 15:08:38 +08:00
Mr.Huan
5580a4ee3d fix: 修复添加任务变更栏目后无效果的问题 2022-01-24 15:07:01 +08:00
kuaifan
870276fa48 feat: 支持文件下载 2022-01-24 14:42:29 +08:00
Mr.Huan
608660a101 build: 25cb8015d0d46f8c0acf48d114df7d722cb90673 2022-01-24 11:56:07 +08:00
Mr.Huan
25cb8015d0 perf: 注册页面增加密码格式提醒 2022-01-24 11:54:47 +08:00
Mr.Huan
e686e8f58c build: 14b699b5b199f4568ab006b7657f811069f8420d 2022-01-24 10:39:13 +08:00
Mr.Huan
14b699b5b1 perf: 消息撤回后直接删除消息 2022-01-24 10:37:18 +08:00
韦荣超
ce0c86c8e5 build: 11d1f26724ebd740abf0c2f6f1c9fa4c51ad1802 2022-01-24 10:22:45 +08:00
韦荣超
11d1f26724 feat: 加入【项目管理】任务列表流程筛选被回滚功能 2022-01-24 10:20:59 +08:00
Mr.Huan
a8c0978f0d build: 0a0227cca43d3b211cd38fd23d4426d4a104036a 2022-01-24 09:31:52 +08:00
Mr.Huan
f643911014 feat: 聊天消息撤回-后端实现 2022-01-24 09:30:10 +08:00
Mr.Huan
0a0227cca4 feat: 聊天消息撤回-前端实现 2022-01-24 09:30:10 +08:00
kuaifan
8bacc3b6ba Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	electron/package.json
#	package.json
#	public/js/app.js
#	public/js/build/161.js
#	public/js/build/400.js
#	public/js/build/616.js
#	public/js/build/845.js
2022-01-24 01:45:08 +08:00
kuaifan
d516330a41 feat: 聊天粘贴发送文件、图片时预览确认 2022-01-24 01:30:10 +08:00
kuaifan
2d83faf144 build 2022-01-24 00:42:58 +08:00
kuaifan
c3fd6bf88f 优化日志显示 2022-01-24 00:40:14 +08:00
kuaifan
2a646becfd 优化项目简介样式 2022-01-24 00:33:31 +08:00
kuaifan
a825657516 fix: 修改主任务时间时未设置时间的子任务没有同步修改 2022-01-24 00:10:47 +08:00
kuaifan
b71dbe9832 perf: 任务详情窗口截止时间24小时倒计时 2022-01-24 00:05:36 +08:00
kuaifan
f06b4040bc 支持查看已归档任务 2022-01-23 23:55:56 +08:00
kuaifan
563cd4b843 优化已归档任务列表 2022-01-23 23:44:39 +08:00
kuaifan
834dc9bec9 支持查看已归档任务 2022-01-23 23:33:16 +08:00
kuaifan
e6e58a03a6 perf: 修改任务或修改项目后同步对话信息 2022-01-23 17:58:32 +08:00
kuaifan
d9f4adbe26 支持查看归档任务 2022-01-23 17:43:15 +08:00
kuaifan
1777153411 perf: 支持查看已归档任务详情 2022-01-23 17:36:19 +08:00
kuaifan
9b0ca581f1 perf: 消息列表显示任务基本状态 2022-01-23 16:17:11 +08:00
kuaifan
493cf7d46a perf: 优化消息对话排序 2022-01-23 15:01:58 +08:00
kuaifan
004bf36dc1 perf: 隐藏无聊天内容的对话 2022-01-23 14:51:32 +08:00
kuaifan
fcf7fb4b9f perf: 消息页再次点击类型定位到未读消息 2022-01-23 14:48:10 +08:00
kuaifan
130a85a5fc fix: flow文件路径引用错误导致无法编译的问题 2022-01-23 13:16:25 +08:00
Mr.Huan
d46eec568c build: 6529e4586820ae388273295f956e2f142275a494 2022-01-22 15:39:25 +08:00
Mr.Huan
7dc6d0ffb2 build: 6529e4586820ae388273295f956e2f142275a494 2022-01-22 15:28:37 +08:00
Mr.Huan
6529e45868 fix: 修复工作汇报选择接收人组件在调整窗体大小时发生位移的问题 2022-01-22 15:26:42 +08:00
Mr.Huan
3ba6ea9c7e perf: 调整周报签名的生成方法 2022-01-22 15:07:37 +08:00
kuaifan
b6ff6f5453 Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	public/js/app.js
#	public/js/build/657.js
#	public/js/build/782.js
#	public/js/build/845.js
2022-01-22 14:18:32 +08:00
Mr.Huan
cfa529b742 build: 2eabc76c1a7da69f9e5efd9cda024eb51daa19f7 2022-01-22 13:53:13 +08:00
Mr.Huan
2eabc76c1a fix: 修复周报点击编辑关闭后再点击"新建汇报"按钮导致用户列表无法重新加载的问题 2022-01-22 13:51:03 +08:00
韦荣超
463e67d64c fix:【工作汇报】修复新增今天的周报,提示要覆盖昨天日报的问题 2022-01-22 12:06:21 +08:00
韦荣超
1f96af1024 build: 563aa9295850392b13d1a1ef7600ebb80e2d3b87 2022-01-22 10:37:27 +08:00
韦荣超
563aa92958 fix: 【工作汇报】收到的汇报中,未读时发送人删除掉汇报对象,汇报对象的未读数量仍然未减少 2022-01-22 10:35:45 +08:00
kuaifan
f09b864e30 perf: 仅客户端或Chrome浏览器支持主题功能 2022-01-22 09:07:13 +08:00
kuaifan
7ce15d5a71 优化代码 2022-01-21 23:31:01 +08:00
kuaifan
cc767b164e 优化工作报告 2022-01-21 23:16:50 +08:00
kuaifan
230ff75c0b 优化搜索栏前端 2022-01-21 21:56:27 +08:00
kuaifan
da39739fb5 优化工作报告前端 2022-01-21 21:49:27 +08:00
kuaifan
c33be22057 Merge branch 'develop' of ssh://git.gezi.vip:6006/gx/dootask into develop
# Conflicts:
#	resources/assets/js/pages/manage/components/ReportDetail.vue
#	resources/assets/js/pages/manage/components/ReportEdit.vue
2022-01-21 21:04:28 +08:00
kuaifan
902f35d21b 优化前端代码 2022-01-21 21:02:17 +08:00
韦荣超
d8426780d2 build: a1aaf90d2ef6af3e46e22bf73016ae80f97a6aa2 2022-01-21 21:01:08 +08:00
韦荣超
a1aaf90d2e fix: 【工作报告】新增报告初始化数据不导入已有时间点数据 2022-01-21 20:59:28 +08:00
韦荣超
53ed4d4072 build: da75554447c8deb7167390d56bfbe9927266a1ee 2022-01-21 19:59:44 +08:00
韦荣超
da75554447 fix: 【工作报告】详情页面前端报错修改 2022-01-21 19:58:03 +08:00
韦荣超
04998d6a60 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	public/js/app.js
2022-01-21 18:10:53 +08:00
韦荣超
3d88a28465 build: 2dd8f75d526514a0adec9d3c8eac5f026b41eed0 2022-01-21 18:10:24 +08:00
韦荣超
2dd8f75d52 fix: 【工作报告】填写内容--汇报类型切换提交提示错误问题 2022-01-21 18:08:48 +08:00
kuaifan
7a4bdd0ada no message 2022-01-21 17:59:08 +08:00
韦荣超
0ed0b5ee43 build: 2475ee90ee493c5e88686a2dd618782f6c6a0850 2022-01-21 17:41:09 +08:00
韦荣超
2475ee90ee perf: 工作报告优化 2022-01-21 17:39:10 +08:00
kuaifan
511c19d5aa 优化前端代码 2022-01-21 14:49:20 +08:00
韦荣超
c4fed86f1e Merge remote-tracking branch 'origin/develop' into develop 2022-01-21 14:26:32 +08:00
韦荣超
18904ebbaa perf: 规范代码 2022-01-21 14:26:17 +08:00
kuaifan
da3672e6be 优化前端代码 2022-01-21 14:24:31 +08:00
kuaifan
52e9836bbf build 2022-01-21 14:07:14 +08:00
kuaifan
3201d90a53 格式化代码 2022-01-21 14:07:02 +08:00
kuaifan
cd9c1d9660 Merge branch 'master' of github.com:kuaifan/dootask into develop 2022-01-21 13:54:58 +08:00
kuaifan
0f54a1f638 no message 2022-01-21 13:50:41 +08:00
kuaifan
713316f87c 优化暗黑皮肤 2022-01-21 13:50:23 +08:00
kuaifan
88d38ba8d1 fix: 发送图片显示错误 2022-01-21 13:45:33 +08:00
kuaifan
164f3275f4 优化暗黑皮肤 2022-01-21 13:13:07 +08:00
韦荣超
a44d6b8b79 build: 942cf57c36c5fb2b636047edbebd75fc6c25c777 2022-01-21 12:10:48 +08:00
韦荣超
942cf57c36 perf: 工作报告优化 2022-01-21 12:06:26 +08:00
kuaifan
18bc75242b fix: 项目负责人还原已经回档的自己不是任务负责人的任务时,无法还原,且会提示错误 2022-01-21 11:10:29 +08:00
kuaifan
3b38c8b408 perf: 共享文件删除、移动改为仅限所有者或创建者操作 2022-01-21 10:00:31 +08:00
kuaifan
22e718423e 优化暗黑模式 2022-01-21 01:38:05 +08:00
kuaifan
0dc0e7226e 调整主题色 2022-01-20 22:59:51 +08:00
kuaifan
8069784198 no message 2022-01-20 00:38:30 +08:00
kuaifan
a9e71567fe Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	electron/package.json
#	package.json
#	public/css/app.css
#	public/js/app.js
#	public/js/build/146.js
#	public/js/build/169.js
#	public/js/build/631.js
#	public/js/build/679.js
#	public/js/build/700.js
#	public/js/build/728.js
#	public/js/build/802.js
#	public/js/build/823.js
#	public/js/build/954.js
#	resources/assets/js/pages/manage.vue
2022-01-20 00:33:05 +08:00
kuaifan
0fae151731 build 2022-01-20 00:20:06 +08:00
kuaifan
3bb0b55955 feat: 添加暗黑模式 2022-01-20 00:19:00 +08:00
kuaifan
071ca80bae fix: 无法读取 /www/.env:没有那个文件或目录 2022-01-18 22:52:43 +08:00
kuaifan
1abd78305f Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	public/css/app.css
#	public/js/app.js
#	public/js/build/120.js
#	public/js/build/120.js.LICENSE.txt
#	public/js/build/198.js
#	public/js/build/494.js
#	public/js/build/79.js
#	public/js/build/793.js
#	public/js/build/875.js
#	public/js/build/890.js
#	resources/assets/js/components/DrawerOverlay.vue
2022-01-17 17:13:12 +08:00
kuaifan
e1ef9a94af no message 2022-01-17 00:36:19 +08:00
kuaifan
9f62023175 build 2022-01-17 00:12:34 +08:00
kuaifan
c9a6d2f5a8 perf: 优化安装脚本 2022-01-16 22:03:50 +08:00
kuaifan
07fca8b895 fix: 最大只能上传64M的问题 2022-01-16 14:53:35 +08:00
kuaifan
1a65c4a579 修复客户端 2022-01-16 01:11:19 +08:00
kuaifan
7a28a8950c 修复前端编译 2022-01-16 00:43:05 +08:00
kuaifan
89e8b0d8b9 perf: 新增工作流初始化数据 2022-01-16 00:05:49 +08:00
kuaifan
74803df5bd no message 2022-01-15 23:37:29 +08:00
kuaifan
2a8e030fb4 perf: 优化客户端自动更新 2022-01-15 23:36:21 +08:00
kuaifan
c6ebe994cc build 2022-01-15 12:53:21 +08:00
kuaifan
be3677cfa8 优化DrawerOverlay组件支持底部显示动态改变尺寸 2022-01-15 12:16:50 +08:00
kuaifan
0c2e56271b 优化DrawerOverlay组件 2022-01-15 00:06:40 +08:00
kuaifan
b019d40009 fix: websocket获取链接失败 2022-01-14 23:15:59 +08:00
kuaifan
361484be95 build 2022-01-14 18:12:33 +08:00
kuaifan
f0ce6cc28f build 2022-01-14 17:59:07 +08:00
kuaifan
ede298a142 perf: 工作汇报样式 2022-01-14 17:57:51 +08:00
kuaifan
aca1a4d34c 优化ws返回的地址 2022-01-14 16:56:45 +08:00
韦荣超
e63c5c074c build: 0d0b5dd552aa634cf972bbc16addab5c242be14d 2022-01-14 16:46:30 +08:00
韦荣超
0d0b5dd552 fix: 【工作报告】填写'提交'按钮位置被挤压修改 2022-01-14 16:43:13 +08:00
kuaifan
08704e7f60 优化报错 2022-01-14 16:35:00 +08:00
韦荣超
d48ed18102 feat: 【工作报告】功能 2022-01-14 15:55:26 +08:00
kuaifan
3602acd187 build 2022-01-14 14:05:09 +08:00
kuaifan
ad477eb608 优化文案 2022-01-14 14:01:56 +08:00
kuaifan
0a393845e9 优化任务详情窗口聊天 2022-01-14 13:52:10 +08:00
kuaifan
59b98209ac 优化websocket重连后的逻辑 2022-01-14 13:46:22 +08:00
kuaifan
ac26713f86 优化查询代码 2022-01-14 12:19:50 +08:00
kuaifan
9250ef6f65 fix: 查看日历页面部分任务会不见 2022-01-14 10:23:43 +08:00
kuaifan
cabbeb07d0 优化websocket重连后的逻辑 2022-01-14 10:23:23 +08:00
kuaifan
43171645c0 build 2022-01-14 00:04:28 +08:00
kuaifan
79c1484288 新增优化websocket重连后刷新项目、对话、仪表盘数据 2022-01-13 23:59:38 +08:00
kuaifan
e730875db3 perf: 客户端更新提示 2022-01-13 23:55:07 +08:00
kuaifan
18e9fb99b5 更新nginx持续握手时间 2022-01-13 23:53:29 +08:00
kuaifan
fd82b9b555 build 2022-01-13 19:42:41 +08:00
kuaifan
5c3bf0067e 优化日志显示 2022-01-13 19:19:58 +08:00
kuaifan
ac6b90c986 fix: 任务流转会出错 2022-01-13 18:39:57 +08:00
kuaifan
7dcd8b29fc build 2022-01-13 11:50:54 +08:00
kuaifan
d951da2c02 fix: 修复客户端旧版本更新任务出现已完成列表中有状态为“进行中”的情况 2022-01-13 11:48:33 +08:00
kuaifan
605aee35c4 perf: 发送文件ws返回错误地址 2022-01-13 11:42:01 +08:00
kuaifan
dc1f1985e8 访问接口header加上version、platform 2022-01-13 11:22:46 +08:00
kuaifan
cebcc26baf 优化项目日志 2022-01-13 11:11:29 +08:00
kuaifan
75a12fc6f5 优化日志 2022-01-13 09:00:39 +08:00
kuaifan
a3e0d89eb0 修改任务状态判断是否项目负责人直接查表 2022-01-13 00:32:35 +08:00
kuaifan
9594cc674f no message 2022-01-13 00:30:36 +08:00
kuaifan
87cdee4fe8 pref: 优化任务日志的显示 2022-01-13 00:30:36 +08:00
kuaifan
1df55b78c2 perf: 项目页面点击搜索按钮再点击弹出的搜索框窗会隐藏 2022-01-13 00:30:36 +08:00
kuaifan
f7820a23be no message 2022-01-12 16:34:16 +08:00
kuaifan
f80b9cdca5 feat: 工作流程状态支持仅限状态负责人修改 2022-01-12 16:16:04 +08:00
kuaifan
f2180b22c7 build 2022-01-11 23:23:44 +08:00
kuaifan
a745088213 perf: 任务日志刷新 2022-01-11 23:17:59 +08:00
kuaifan
14be7bd2b1 no msg 2022-01-11 22:58:30 +08:00
kuaifan
d453ea66da 优化登录页按钮 2022-01-11 21:31:23 +08:00
kuaifan
e7ae86e261 feat: 任务流转自动负责人支持转让模式 2022-01-11 20:07:58 +08:00
kuaifan
b7417f41c5 perf: action中错误提醒弱化 2022-01-11 17:54:42 +08:00
kuaifan
9e7aa381ed perf: 手机版无法选择日期范围的问题 2022-01-11 17:47:34 +08:00
kuaifan
b02453e9f4 perf: 手机版无法聊天,输入页面跳动 2022-01-11 17:35:53 +08:00
kuaifan
6b7f8fd31c 优化完成任务标记 2022-01-11 14:18:49 +08:00
kuaifan
4652242d6b build 2022-01-11 12:16:01 +08:00
kuaifan
a47ca2f357 perf: 个人设置显示版本号 2022-01-11 12:13:26 +08:00
kuaifan
e5a1e58159 perf: 完成任务暂时继续显示在我的列表 2022-01-11 12:13:06 +08:00
kuaifan
01a1e34e99 no message 2022-01-11 10:59:47 +08:00
kuaifan
e711220a66 优化自定义服务器 2022-01-11 10:31:37 +08:00
kuaifan
1d20f529a0 no message 2022-01-11 00:28:58 +08:00
kuaifan
79a94d25bd perf: 客户端自动下载新版本更新 2022-01-10 19:27:43 +08:00
kuaifan
92793b8ff8 no message 2022-01-10 00:25:36 +08:00
kuaifan
982024f359 子任务时间在主任务时间内 2022-01-09 20:13:59 +08:00
kuaifan
e851bd4d61 子任务时间大于主任务时主任务自动修改 2022-01-09 18:56:30 +08:00
kuaifan
e4b9383e96 perf: 设置子任务时间时,如果主任务没有时间则自动设置 2022-01-09 18:20:36 +08:00
kuaifan
95ac9aac14 添加任务自动上工作流状态 2022-01-09 17:59:08 +08:00
kuaifan
b895eec69c 初步完成工作流 2022-01-09 17:52:46 +08:00
kuaifan
1fe4e80f82 no msg 2022-01-09 12:56:55 +08:00
kuaifan
eb1f5f2632 perf: 任务操作菜单组件化 2022-01-09 00:09:43 +08:00
kuaifan
114b792300 no message 2022-01-08 21:23:47 +08:00
kuaifan
56a352a9d2 perf: 调整仅项目负责人可以删除任务列表 2022-01-08 17:35:25 +08:00
kuaifan
4c4d60dd83 no message 2022-01-08 17:32:03 +08:00
kuaifan
134ab0fe98 no message 2022-01-08 17:27:30 +08:00
kuaifan
bbd394272f feat: 添加工作流 2022-01-08 17:24:48 +08:00
kuaifan
8a2571f514 no msg 2022-01-08 08:56:29 +08:00
kuaifan
bf15be8144 perf: 领取任务需要设置计划时间 2022-01-08 08:51:34 +08:00
kuaifan
bf6f8de7fa no msg 2022-01-08 00:35:01 +08:00
kuaifan
2e75e6ffb6 后台限制任务负责人、协助人员每项最多不能超过10个 2022-01-07 23:36:05 +08:00
kuaifan
b987be54bf 优化前端变量 2022-01-07 23:28:07 +08:00
kuaifan
533e8a3742 build 2022-01-07 19:12:38 +08:00
kuaifan
9d007e64f6 优化部分相同代码 2022-01-07 18:42:04 +08:00
kuaifan
8ec1578f50 左边底部新增添加任务选项 2022-01-07 18:23:30 +08:00
kuaifan
4e8cc36d3a 显示完成时间 2022-01-07 18:09:22 +08:00
kuaifan
5a64cb2323 替换Tooltip改成ETooltip 2022-01-07 18:09:05 +08:00
kuaifan
b2aba82a1b perf: 任务版本如果只有一个负责人时显示负责人的名字 2022-01-07 17:46:03 +08:00
kuaifan
9d9500ba1b fix: 其他人员添加任务会临时出现在自己的列表中 2022-01-07 17:26:22 +08:00
kuaifan
674c5a11c1 客户端pkg包安装前必须关闭已打开的应用 2022-01-07 10:56:07 +08:00
kuaifan
8e2b2947ae 版本号基于master分支生成 2022-01-07 10:16:35 +08:00
kuaifan
0413ca7cba 新增pkg安装包 2022-01-07 10:14:49 +08:00
kuaifan
15cf7800a4 日历选时间快速添加任务 2022-01-07 01:07:41 +08:00
kuaifan
db6114a4ee pref: 日历任务缓存 2022-01-07 00:56:03 +08:00
kuaifan
818495a697 build 2022-01-06 23:25:04 +08:00
kuaifan
7aa41c4050 no msg 2022-01-06 23:22:03 +08:00
kuaifan
1975b8af1d perf: 日历选择时间添加任务 2022-01-06 23:15:04 +08:00
kuaifan
b870f5f4d1 no msg 2022-01-06 22:24:45 +08:00
kuaifan
3ebc720934 no message 2022-01-06 20:24:31 +08:00
kuaifan
748976f393 feat: 创建任务窗口添加小时钟 2022-01-06 20:12:13 +08:00
kuaifan
83e5c7fadb 优化注释 2022-01-06 19:22:29 +08:00
kuaifan
f919a34166 优化任务、列表数量限制 2022-01-06 19:12:49 +08:00
kuaifan
77a9eca634 no msg 2022-01-06 18:56:50 +08:00
kuaifan
72b732a55d perf: 归档任务可以搜索 2022-01-06 18:53:34 +08:00
kuaifan
0236897d1f no msg 2022-01-06 17:17:23 +08:00
kuaifan
46f95e5e13 no msg 2022-01-06 16:57:13 +08:00
kuaifan
24287c0857 fix: 主任务被删除或归档时子任务应该也同步 2022-01-06 16:51:32 +08:00
kuaifan
c930e4dd92 perf: 归档任务列表显示完成时间 2022-01-06 16:30:45 +08:00
kuaifan
7d8856e4bc no msg 2022-01-06 15:41:58 +08:00
kuaifan
6d026bbf42 no message 2022-01-06 15:24:54 +08:00
kuaifan
713770a448 no message 2022-01-06 15:09:41 +08:00
kuaifan
ac873fa757 no msg 2022-01-06 14:40:25 +08:00
kuaifan
6b3513c1c4 feat: 限制个人最多500个未完成任务 2022-01-06 14:40:19 +08:00
kuaifan
840374c48c feat: 限制项目最多100个人参与 2022-01-06 14:39:56 +08:00
kuaifan
7f454f279c perf: 项目版面成员显示 2022-01-06 14:28:23 +08:00
kuaifan
267c09f20c no msg 2022-01-06 13:38:49 +08:00
kuaifan
e44e77a3a6 pref: 子任务默认起始时间与主任务一致 2022-01-06 13:28:47 +08:00
kuaifan
4d9dd13ffb pref: 禁用会员记录禁用时间 2022-01-06 13:28:10 +08:00
kuaifan
369e75cb7e perf: 安装项目时支持自定义端口 2022-01-06 11:33:51 +08:00
kuaifan
0a6fa2431e perf: 任务详情窗口光标在任务描述可使用ctrl+s(command+s)保存 2022-01-06 08:27:32 +08:00
kuaifan
0bc40d1748 perf: 文件快捷键保存 2022-01-06 08:17:37 +08:00
kuaifan
919dc3cdea feat: 思维导图支持无极缩放 2022-01-06 08:08:40 +08:00
kuaifan
7c64b27ef4 feat: 自动归档已完成任务 2022-01-06 01:19:44 +08:00
kuaifan
369577a2c8 no message 2022-01-06 00:32:27 +08:00
kuaifan
fbc25e5134 no message 2022-01-05 23:50:29 +08:00
kuaifan
aba1628d36 no message 2022-01-05 22:51:02 +08:00
kuaifan
90336e1edf 整理$A 2022-01-05 20:19:27 +08:00
kuaifan
dde32fcaee no message 2022-01-05 19:49:47 +08:00
kuaifan
6324e79aba no message 2022-01-05 15:04:55 +08:00
kuaifan
7a22f4b20f no message 2022-01-05 14:56:11 +08:00
kuaifan
aad621bd84 no message 2022-01-05 14:17:45 +08:00
kuaifan
0b4e1f3dee no message 2022-01-05 13:41:05 +08:00
kuaifan
78f88db560 将所有任务都取出全部显示,子任务判断显示主任务 2022-01-05 09:30:00 +08:00
kuaifan
cc1c425ecf no message 2022-01-04 23:42:29 +08:00
kuaifan
186290e355 未完成项目概况 2022-01-04 20:23:01 +08:00
kuaifan
f1f1d784ff 修复我的任务数据 2022-01-01 15:03:33 +08:00
kuaifan
a1c7efeb85 任务数据逻辑 2021-12-31 14:53:57 +08:00
kuaifan
ba5f635687 子任务默认时间与主任务相同 2021-12-31 11:07:44 +08:00
kuaifan
bae5afc0da no message 2021-12-31 09:59:11 +08:00
kuaifan
befd5c3b08 去除taskSubs 2021-12-31 07:48:55 +08:00
kuaifan
0b4e96a90f build 2021-12-30 15:35:16 +08:00
kuaifan
57edf38c1a fix: 任务有负责人后仅限项目或任务负责人修改 2021-12-30 15:32:36 +08:00
kuaifan
502dd1ec1f perf: 未被领取的任务标记完成之前先领取 2021-12-30 14:39:12 +08:00
kuaifan
02361ddfb2 perf: 快速添加任务0天的描述 2021-12-30 14:31:23 +08:00
kuaifan
bcf6cc1019 perf: 客户端标题优化 2021-12-30 14:18:25 +08:00
kuaifan
b97a2e7cf3 build 2021-12-30 11:04:16 +08:00
kuaifan
27158e1ee7 perf: office文件预览 2021-12-30 10:58:51 +08:00
kuaifan
28abad0276 no msg 2021-12-30 10:36:46 +08:00
kuaifan
b59549ebe9 perf: 文件预览不能滚动的问题 2021-12-30 10:34:52 +08:00
kuaifan
b9f788fbe8 build 2021-12-30 00:58:23 +08:00
kuaifan
9d89334cc5 perf: 优化文件权限 2021-12-30 00:52:34 +08:00
kuaifan
6c67ff3fe8 fix: 只读文件也能修改文件 2021-12-29 22:11:18 +08:00
kuaifan
6ef59f703a perf: 初始化数据 2021-12-29 21:40:13 +08:00
kuaifan
bbd055c798 build 2021-12-29 17:36:33 +08:00
kuaifan
53879fcefb feat: 文件分享查看链接 2021-12-29 17:23:53 +08:00
kuaifan
fd6e7f3096 feat: 新增主动退出共享文件 2021-12-29 14:11:33 +08:00
kuaifan
4d8cf41b7a feat: 共享给所有人 2021-12-29 13:08:30 +08:00
kuaifan
2b0467e00f feat: 文件支持只读、读/写细化设置 2021-12-29 08:38:45 +08:00
kuaifan
cef6646f50 perf: Windows客户端无法关闭的情况 2021-12-28 16:55:29 +08:00
kuaifan
b695f90ded build 2021-12-28 14:40:59 +08:00
kuaifan
d7f1246c32 perf: 任务等级支持设置0天表示默认不限时 2021-12-28 14:37:02 +08:00
kuaifan
739d1f2455 perf: 客户端子窗口 2021-12-28 14:12:24 +08:00
kuaifan
be7c6e700b fix: 聊天窗口不自动滚动的问题 2021-12-28 12:24:27 +08:00
kuaifan
320dd49a87 perf: 任务窗口 2021-12-28 11:42:59 +08:00
kuaifan
9bfa680fa4 feat: 客户端子窗口数据同步到主窗口 2021-12-28 09:00:44 +08:00
kuaifan
ed32f9994d feat: 任务新窗口打开 2021-12-27 23:20:37 +08:00
kuaifan
c6eb850abe no message 2021-12-27 21:04:35 +08:00
kuaifan
6c458b81b2 fix: 清除缓存导致自定义服务器失败的问题 2021-12-27 14:49:08 +08:00
kuaifan
6741f59aef fix: office无法修改的问题 2021-12-27 14:44:45 +08:00
kuaifan
6a0fd46fc4 perf: 链接说明 2021-12-27 14:37:21 +08:00
kuaifan
b2c8beae71 build 2021-12-27 14:21:05 +08:00
kuaifan
b376327438 style: 任务描述溢出的问题 2021-12-27 13:57:42 +08:00
kuaifan
9eb4fecbe8 feat: 客户端支持自定义服务器 2021-12-27 13:49:04 +08:00
kuaifan
f672f4d1bb style: 优化客户端更新窗口滚动 2021-12-27 12:26:43 +08:00
kuaifan
c8b085e963 perf: 发布窗口小屏幕不支持右键(隐藏右键提示) 2021-12-27 12:20:10 +08:00
kuaifan
8cd3daee9d style: 发布任务出口小屏幕下方按钮不居中 2021-12-27 12:20:10 +08:00
kuaifan
0b03aec038 fix: 邀请加入项目数据库迁移文件 2021-12-27 12:19:03 +08:00
kuaifan
b04fd1a937 fix: 系统设置保存后邀请码消失 2021-12-27 00:07:34 +08:00
kuaifan
bdf4222d70 build 2021-12-26 23:48:14 +08:00
kuaifan
261f7ebbc2 fix: 任务详情窗口不显示子任务 2021-12-26 23:42:38 +08:00
kuaifan
91f25e9ec3 feat: 注册邀请码模式 2021-12-26 23:38:47 +08:00
kuaifan
dd86bb88c6 feat: 通过链接邀请加入项目 2021-12-26 23:38:25 +08:00
kuaifan
0665f2de5f feat: 文件上传进度 2021-12-26 11:51:12 +08:00
kuaifan
364e5df974 fix: 全局loading 2021-12-26 02:22:26 +08:00
kuaifan
d3cdaccbc5 build 2021-12-26 01:52:43 +08:00
kuaifan
75dbc990e4 perf: 安装composer install失败时尝试使用国内源再试一次 2021-12-26 01:44:04 +08:00
kuaifan
5bdd6e15e4 fix: 图片媒体文件无法预览 2021-12-26 01:20:37 +08:00
kuaifan
f7b5a2e971 feat: 新注册自动创建个人项目 2021-12-26 01:11:27 +08:00
kuaifan
a486eefd81 perf: 优化office加载提示 2021-12-26 01:00:49 +08:00
kuaifan
1fae364e7d perf: 按需加载富文本静态资源 2021-12-26 00:43:57 +08:00
kuaifan
76db0c41d3 style: 登录成功加载效果 2021-12-26 00:11:44 +08:00
kuaifan
7318b8917d style: 客户端文件窗口样式 2021-12-25 19:12:55 +08:00
kuaifan
fefcd682b8 build 2021-12-25 16:39:03 +08:00
kuaifan
a32c0b028b perf: 客户端新窗口打开文件 2021-12-25 16:35:47 +08:00
kuaifan
ffe62b8f8e fix: 客户端新窗口 2021-12-25 15:19:03 +08:00
kuaifan
5076374b5d doc: Docker Compose v2.0+ 2021-12-25 14:50:04 +08:00
kuaifan
d368e24f75 perf: 客户端窗口标题 2021-12-25 14:49:44 +08:00
kuaifan
343c5eb587 feat: 客户端新窗口打开文件 2021-12-25 14:48:38 +08:00
kuaifan
f7fc379e56 perf: 领取任务流程 2021-12-25 11:43:29 +08:00
kuaifan
ccf4c4bbb3 fix: window客户端弹出错误 2021-12-25 11:11:57 +08:00
kuaifan
a32995abec perf: 到期时间格式化 2021-12-25 02:18:56 +08:00
kuaifan
abd87f3584 feat: 任务创建窗口自动选择上次添加的项目 2021-12-25 02:12:53 +08:00
kuaifan
bbf7277abc style: iview 2021-12-25 01:44:35 +08:00
kuaifan
b38b335fa1 perf: 小屏幕弹窗底部按钮铺全 2021-12-25 01:29:28 +08:00
kuaifan
cf41e71494 perf: 仪表盘完成任务 2021-12-25 01:06:32 +08:00
kuaifan
e75408d20d style: iview 2021-12-25 00:59:26 +08:00
kuaifan
d79f3c6a80 fix: 任务窗口详情描述内容溢出 2021-12-25 00:16:28 +08:00
kuaifan
1a5e196e4e fix: 任务聊天窗口领取任务按钮错位 2021-12-25 00:11:29 +08:00
kuaifan
138bfc8362 fix: 任务级别设置等级描述 2021-12-25 00:06:44 +08:00
kuaifan
464795779f fix: 手机版隐藏客户端下载按钮 2021-12-24 23:49:33 +08:00
kuaifan
e65c80962d perf: 添加任务窗口 2021-12-24 23:49:09 +08:00
kuaifan
7171d1d6b2 perf: 添加任务弹窗 2021-12-24 12:59:05 +08:00
kuaifan
162c6e95d3 style: 排序箭头颜色 2021-12-24 00:36:29 +08:00
kuaifan
63743656d4 fix: 按到期时间排序没有时间应该排到最后 2021-12-24 00:27:01 +08:00
kuaifan
a3a9032af7 build 2021-12-23 23:36:50 +08:00
kuaifan
194ef2b4ca fix: 分页 #31 2021-12-23 23:34:49 +08:00
kuaifan
89b8342ca0 perf: 客户端关闭 2021-12-23 23:26:33 +08:00
kuaifan
ba32df2fb8 perf: 客户端在项目页面支持快捷键添加任务 2021-12-23 23:22:10 +08:00
kuaifan
08234afe4f fix: 任务负责人无法修改子任务的问题 2021-12-23 23:20:04 +08:00
kuaifan
41eb28992e feat: mac客户端角标提示加上仪表盘内的任务数 2021-12-23 21:50:13 +08:00
kuaifan
20a6da4944 feat: 客户端快捷键关闭侧滑窗 2021-12-23 21:49:27 +08:00
kuaifan
544496a09b feat: 客户端快捷键关闭窗口先关网页内的弹窗 2021-12-23 19:35:35 +08:00
kuaifan
26e7d562aa docs: 添加QQ群号 2021-12-23 16:44:11 +08:00
kuaifan
d1814a4e0f feat: 项目任务支持优先级、到期时间排序 2021-12-23 15:41:48 +08:00
kuaifan
a11adad23f perf: 已完成任务的显示 2021-12-23 15:10:09 +08:00
kuaifan
834c8cc7d5 perf: 任务详细描述、添加任务 2021-12-23 14:55:29 +08:00
kuaifan
1801ea7873 perf: 仪表盘 2021-12-23 12:21:23 +08:00
kuaifan
b208634e40 feat: 仪表盘徽标数 2021-12-23 01:33:14 +08:00
kuaifan
7b3d071fd3 feat: 协助的任务 2021-12-23 01:09:05 +08:00
kuaifan
663a8bb06d perf: 时间范围任务 2021-12-23 01:07:47 +08:00
kuaifan
6c34f083e3 perf: 今天任务、我的任务 2021-12-22 22:39:08 +08:00
kuaifan
6d9237c399 perf: 日历日、周视图选择添加任务会进入all day 2021-12-22 21:37:07 +08:00
kuaifan
8a7186c1b1 feat: 记住最后登录账号 2021-12-22 21:19:22 +08:00
kuaifan
c120686fae perf: 清除缓存 2021-12-22 21:13:25 +08:00
kuaifan
75c83e4117 fix 只有一列的时候默认版面为表格模式 2021-12-22 20:28:40 +08:00
kuaifan
d16b846d4e build 2021-12-22 19:51:54 +08:00
kuaifan
fe44c35406 使用element notification 2021-12-22 17:49:44 +08:00
kuaifan
376bcc4a0b 新增客户端提示升级 2021-12-22 15:37:19 +08:00
kuaifan
26d9e63e83 优化electron脚本命令 2021-12-22 12:13:31 +08:00
kuaifan
a7ac2cee13 no message 2021-12-22 01:42:21 +08:00
kuaifan
7a850704e5 no message 2021-12-22 00:37:17 +08:00
kuaifan
d4cc561d90 add workflows 2021-12-21 23:47:08 +08:00
2280 changed files with 163188 additions and 437164 deletions

61
.github/workflows/electron.yml vendored Normal file
View File

@ -0,0 +1,61 @@
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,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:
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 }}

1
.gitignore vendored
View File

@ -19,7 +19,6 @@ 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
View File

@ -0,0 +1,3 @@
[submodule "resources/drawio"]
path = resources/drawio
url = https://github.com/jgraph/drawio.git

View File

@ -5,10 +5,15 @@ English | **[中文文档](./README_CN.md)**
- [Screenshot Preview](README_PREVIEW.md)
- [Demo site](http://www.dootask.com/)
**QQ Group**
Group No.: `546574618`
## Setup
> `Docker` & `Docker Compose` must be installed
- `Docker` & `Docker Compose v2.0+` must be installed
- System: `Centos/Debian/Ubuntu/macOS`
- Hardware suggestion: 2 cores and above 2G memory
### Deployment project
@ -16,14 +21,14 @@ English | **[中文文档](./README_CN.md)**
# 1、Clone the repository
# Clone projects on github
git clone https://github.com/kuaifan/dootask.git
# or you can use gitee
git clone https://gitee.com/aipaw/dootask.git
git clone --depth=1 https://github.com/kuaifan/dootask.git
# Or you can use gitee
git clone --depth=1 https://gitee.com/aipaw/dootask.git
# 2、enter directory
# 2、Enter directory
cd dootask
# 3、Build project
# 3、InstallationCustom port installation: ./cmd install --port 2222
./cmd install
```
@ -37,8 +42,7 @@ cd dootask
### Change port
```bash
./cmd php bin/run --port=2222
./cmd up -d
./cmd port 2222
```
### Stop server
@ -50,18 +54,28 @@ cd dootask
./cmd start
```
### Development compilation
```bash
# Development mode, Mac OS only
./cmd dev
# Production projects, macOS only
./cmd prod
```
### Shortcuts for running command
```bash
# You can do this using the following command
./cmd artisan "your command" // To run a artisan command
./cmd php "your command" // To run a php command
./cmd nginx "your command" // To run a nginx command
./cmd redis "your command" // To run a redis command
./cmd composer "your command" // To run a composer command
./cmd supervisorctl "your command" // To run a supervisorctl command
./cmd test "your command" // To run a phpunit command
./cmd mysql "your command" // To run a mysql command (backup: Backup database, recovery: Restore database)
./cmd artisan "your command" # To run a artisan command
./cmd php "your command" # To run a php command
./cmd nginx "your command" # To run a nginx command
./cmd redis "your command" # To run a redis command
./cmd composer "your command" # To run a composer command
./cmd supervisorctl "your command" # To run a supervisorctl command
./cmd test "your command" # To run a phpunit command
./cmd mysql "your command" # To run a mysql command (backup: Backup database, recovery: Restore database)
```
### NGINX PROXY SSL
@ -90,6 +104,7 @@ git pull
./cmd uninstall
./cmd install
./cmd mysql recovery
./cmd artisan migrate
```
## Uninstall

View File

@ -5,10 +5,15 @@
- [截图预览](README_PREVIEW.md)
- [演示站点](http://www.dootask.com/)
**QQ交流群**
- QQ群号: `546574618`
## 安装程序
> 必须安装 `Docker``Docker Compose`
- 必须安装:`Docker``Docker Compose v2.0+`
- 支持环境:`Centos/Debian/Ubuntu/macOS`
- 硬件建议2核2G以上
### 部署项目
@ -16,14 +21,14 @@
# 1、克隆项目到您的本地或服务器
# 通过github克隆项目
git clone https://github.com/kuaifan/dootask.git
git clone --depth=1 https://github.com/kuaifan/dootask.git
# 或者你也可以使用gitee
git clone https://gitee.com/aipaw/dootask.git
git clone --depth=1 https://gitee.com/aipaw/dootask.git
# 2、进入目录
cd dootask
# 3、一键构建项目
# 3、一键安装项目(自定义端口安装 ./cmd install --port 2222
./cmd install
```
@ -37,8 +42,7 @@ cd dootask
### 更换端口
```bash
./cmd php bin/run --port=2222
./cmd up -d
./cmd port 2222
```
### 停止服务
@ -50,18 +54,29 @@ cd dootask
./cmd start
```
### 开发编译
```bash
# 开发模式仅限macOS
./cmd dev
# 编译项目仅限macOS
./cmd prod
```
### 运行命令的快捷方式
```bash
# 你可以使用以下命令来执行
./cmd artisan "your command" // 运行 artisan 命令
./cmd php "your command" // 运行 php 命令
./cmd nginx "your command" // 运行 nginx 命令
./cmd redis "your command" // 运行 redis 命令
./cmd composer "your command" // 运行 composer 命令
./cmd supervisorctl "your command" // 运行 supervisorctl 命令
./cmd test "your command" // 运行 phpunit 命令
./cmd mysql "your command" // 运行 mysql 命令 (backup: 备份数据库recovery: 还原数据库)
./cmd artisan "your command" # 运行 artisan 命令
./cmd php "your command" # 运行 php 命令
./cmd nginx "your command" # 运行 nginx 命令
./cmd redis "your command" # 运行 redis 命令
./cmd composer "your command" # 运行 composer 命令
./cmd supervisorctl "your command" # 运行 supervisorctl 命令
./cmd test "your command" # 运行 phpunit 命令
./cmd mysql "your command" # 运行 mysql 命令 (backup: 备份数据库recovery: 还原数据库)
```
### NGINX 代理 SSL
@ -90,6 +105,7 @@ git pull
./cmd uninstall
./cmd install
./cmd mysql recovery
./cmd artisan migrate
```
## 卸载项目

View File

@ -10,7 +10,7 @@ if (!function_exists('asset_main')) {
if (!function_exists('seeders_at')) {
function seeders_at($data)
{
$diff = time() - strtotime("2021-07-01");
$diff = time() - strtotime("2021-07-02");
$time = strtotime($data) + $diff;
return date("Y-m-d H:i:s", $time);
}

View File

@ -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,7 +11,9 @@ use App\Models\WebSocketDialogMsg;
use App\Models\WebSocketDialogMsgRead;
use App\Models\WebSocketDialogUser;
use App\Module\Base;
use Carbon\Carbon;
use Request;
use Response;
/**
* @apiDefine dialog
@ -20,18 +23,28 @@ use Request;
class DialogController extends AbstractController
{
/**
* 对话列表
* @api {get} api/dialog/lists 01. 对话列表
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName lists
*
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:100,最大:200
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function lists()
{
$user = User::auth();
//
$list = WebSocketDialog::select(['web_socket_dialogs.*'])
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at'])
->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) {
@ -42,9 +55,18 @@ class DialogController extends AbstractController
}
/**
* 获取单个会话信息
* @api {get} api/dialog/one 02. 获取单个会话信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName one
*
* @apiParam {Number} dialog_id 对话ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function one()
{
@ -65,9 +87,18 @@ class DialogController extends AbstractController
}
/**
* 打开会话
* @api {get} api/dialog/msg/user 03. 打开会话
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName open__user
*
* @apiParam {Number} userid 对话会员ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function open__user()
{
@ -90,12 +121,21 @@ class DialogController extends AbstractController
}
/**
* 获取消息列表
* @api {get} api/dialog/msg/lists 04. 获取消息列表
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__lists
*
* @apiParam {Number} dialog_id 对话ID
*
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:50,最大:100
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__lists()
{
@ -124,24 +164,51 @@ class DialogController extends AbstractController
}
/**
* 未读消息
* @api {get} api/dialog/msg/unread 05. 获取未读消息数量
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @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 {post} api/dialog/msg/sendtext 06. 发送消息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__sendtext
*
* @apiParam {Number} dialog_id 对话ID
* @apiParam {String} text 消息内容
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__sendtext()
{
Base::checkClientVersion('0.8.1');
$user = User::auth();
//
$chat_nickname = Base::settingFind('system', 'chat_nickname');
@ -152,8 +219,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('消息内容不能为空');
@ -163,20 +230,39 @@ 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);
}
/**
* {post}文件上传
* @api {post} api/dialog/msg/sendfile 07. 文件上传
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__sendfile
*
* @apiParam {Number} dialog_id 对话ID
* @apiParam {String} [filename] post-文件名称
* @apiParam {String} [image64] post-base64图片二选一
* @apiParam {File} [files] post-文件对象(二选一)
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__sendfile()
{
@ -186,7 +272,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) {
@ -198,7 +284,7 @@ class DialogController extends AbstractController
} else {
$data = Base::upload([
"file" => Request::file('files'),
"type" => 'file',
"type" => 'more',
"path" => $path,
"fileName" => $fileName,
]);
@ -241,9 +327,18 @@ class DialogController extends AbstractController
}
/**
* 获取消息阅读情况
* @api {get} api/dialog/msg/readlist 08. 获取消息阅读情况
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__readlist
*
* @apiParam {Number} msg_id 消息ID需要是消息的发送人
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__readlist()
{
@ -259,4 +354,137 @@ class DialogController extends AbstractController
$read = WebSocketDialogMsgRead::whereMsgId($msg_id)->get();
return Base::retSuccess('success', $read ?: []);
}
/**
* @api {get} api/dialog/msg/detail 09. 消息详情
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @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 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__detail()
{
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') {
$msg = Base::json2array($dialogMsg->getRawOriginal('msg'));
$msg = File::formatFileData($msg);
$data['content'] = $msg['content'];
$data['file_mode'] = $msg['file_mode'];
}
//
return Base::retSuccess('success', $data);
}
/**
* @api {get} api/dialog/msg/download 10. 文件下载
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__download
*
* @apiParam {Number} msg_id 消息ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__download()
{
User::auth();
//
$msg_id = intval(Request::input('msg_id'));
//
$msg = WebSocketDialogMsg::whereId($msg_id)->first();
if (empty($msg)) {
abort(403, "This file not exist.");
}
if ($msg->type != 'file') {
abort(403, "This file not support download.");
}
$array = Base::json2array($msg->getRawOriginal('msg'));
//
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", $dialogId);
}
}

View File

@ -2,18 +2,18 @@
namespace App\Http\Controllers\Api;
use App\Exceptions\ApiException;
use App\Models\AbstractModel;
use App\Models\File;
use App\Models\FileContent;
use App\Models\FileLink;
use App\Models\FileUser;
use App\Models\User;
use App\Models\WebSocket;
use App\Module\Base;
use App\Module\Ihttp;
use Arr;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Request;
use Response;
/**
* @apiDefine file
@ -23,9 +23,18 @@ use Response;
class FileController extends AbstractController
{
/**
* 获取文件列表
* @api {get} api/file/lists 01. 获取文件列表
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName lists
*
* @apiParam {Number} [pid] 父级ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function lists()
{
@ -34,18 +43,18 @@ class FileController extends AbstractController
$data = Request::all();
$pid = intval($data['pid']);
//
$permission = 1000;
if ($pid > 0) {
$file = File::find($pid);
if (empty($file)) {
return Base::retError('Not exist');
}
$file->exceAllow($user->userid);
//
File::permissionFind($pid, 0, $permission);
$builder = File::wherePid($pid);
} else {
$builder = File::whereUserid($user->userid);
}
//
$array = $builder->take(500)->get()->toArray();
foreach ($array as &$item) {
$item['permission'] = $permission;
}
//
if ($pid > 0) {
// 遍历获取父级
@ -56,18 +65,23 @@ class FileController extends AbstractController
}
$pid = $file->pid;
$temp = $file->toArray();
$temp['allow'] = $file->chackAllow($user->userid);
$temp['permission'] = $file->getPermission($user->userid);
$array[] = $temp;
}
} else {
// 获取共享相关
$list = File::where('userid', '!=', $user->userid)->where(function ($query) use ($user) {
$query->where('share', 1)->orWhere(function ($q2) use ($user) {
$q2->where('share', 2)->whereIn('id', function ($q3) use ($user) {
$q3->select('file_id')->from('file_users')->where('userid', $user->userid);
});
});
})->get();
DB::statement("SET SQL_MODE=''");
$pre = DB::connection()->getTablePrefix();
$list = File::select(["files.*", DB::raw("MAX({$pre}file_users.permission) as permission")])
->join('file_users', 'files.id', '=', 'file_users.file_id')
->where('files.userid', '!=', $user->userid)
->where(function ($query) use ($user) {
$query->where('file_users.userid', 0);
$query->orWhere('file_users.userid', $user->userid);
})
->groupBy('files.id')
->take(100)
->get();
if ($list->isNotEmpty()) {
foreach ($list as $file) {
$temp = $file->toArray();
@ -80,9 +94,57 @@ class FileController extends AbstractController
}
/**
* 搜索文件列表
* @api {get} api/file/one 02. 获取单条数据
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName one
*
* @apiParam {Number|String} id
* - Number 文件ID需要登录
* - String 链接码(不需要登录,用于预览)
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function one()
{
$id = Request::input('id');
//
$permission = 0;
if (Base::isNumber($id)) {
User::auth();
$file = File::permissionFind(intval($id), 0, $permission);
} elseif ($id) {
$fileLink = FileLink::whereCode($id)->first();
$file = $fileLink?->file;
if (empty($file)) {
return Base::retError('链接不存在');
}
} else {
return Base::retError('参数错误');
}
//
$array = $file->toArray();
$array['permission'] = $permission;
return Base::retSuccess('success', $array);
}
/**
* @api {get} api/file/search 03. 搜索文件列表
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName search
*
* @apiParam {String} [key] 关键词
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function search()
{
@ -100,12 +162,21 @@ class FileController extends AbstractController
}
/**
* 添加、修改文件()
* @api {get} api/file/add 04. 添加、修改文件()
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName add
*
* @apiParam {String} name 项目名称
* @apiParam {String} type 文件类型
* @apiParam {Number} [id] 文件ID赋值修改文件名称
* @apiParam {Number} [pid] 父级ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function add()
{
@ -123,7 +194,7 @@ class FileController extends AbstractController
//
if ($id > 0) {
// 修改
$file = File::allowFind($id);
$file = File::permissionFind($id, 1);
//
$file->name = $name;
$file->save();
@ -135,29 +206,37 @@ class FileController extends AbstractController
'folder',
'document',
'mind',
'sheet',
'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) {
if (File::wherePid($pid)->count() >= 300) {
return Base::retError('每个文件夹里最多只能创建300个文件或文件夹');
}
$row = File::allowFind($pid, '主文件不存在');
$row = File::permissionFind($pid, 1);
$userid = $row->userid;
} else {
if (File::whereUserid($user->userid)->wherePid(0)->count() >= 300) {
@ -182,9 +261,18 @@ class FileController extends AbstractController
}
/**
* 复制文件()
* @api {get} api/file/copy 05. 复制文件()
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName copy
*
* @apiParam {Number} id 文件ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function copy()
{
@ -192,7 +280,7 @@ class FileController extends AbstractController
//
$id = intval(Request::input('id'));
//
$row = File::allowFind($id);
$row = File::permissionFind($id);
//
$userid = $user->userid;
if ($row->pid > 0) {
@ -214,38 +302,61 @@ class FileController extends AbstractController
'userid' => $userid,
'created_id' => $user->userid,
]);
$data = AbstractModel::transaction(function() use ($file) {
$content = FileContent::select(['content', 'text', 'size'])->whereFid($file->cid)->orderByDesc('id')->first();
$file->size = $content?->size ?: 0;
$file->save();
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);
}
/**
* 移动文件()
* @api {get} api/file/move 06. 移动文件()
*
* @apiParam {Number} id 文件ID
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName move
*
* @apiParam {Numbers} ids 文件ID格式[id1, id2]
* @apiParam {Number} pid 移动到的文件夹ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function move()
{
$user = User::auth();
//
$id = intval(Request::input('id'));
$ids = Request::input('ids');
$pid = intval(Request::input('pid'));
//
$file = File::whereId($id)->first();
if (empty($file)) {
return Base::retError('文件不存在或已被删除');
if (!is_array($ids) || empty($ids)) {
return Base::retError('请选择移动的文件或文件夹');
}
if ($file->userid != $user->userid) {
return Base::retError('仅限所有者操作');
if (count($ids) > 100) {
return Base::retError('一次最多只能移动100个文件或文件夹');
}
if ($pid > 0) {
File::permissionFind($pid, 1);
}
//
$files = [];
AbstractModel::transaction(function() use ($pid, $ids, &$files) {
foreach ($ids as $id) {
$file = File::permissionFind($id, 1000);
//
if ($pid > 0) {
if (!File::whereUserid($user->userid)->whereId($pid)->exists()) {
return Base::retError('参数错误');
}
$arr = [];
$tid = $pid;
while ($tid > 0) {
@ -253,60 +364,137 @@ class FileController extends AbstractController
$tid = intval(File::whereId($tid)->value('pid'));
}
if (in_array($id, $arr)) {
return Base::retError('位置错误');
throw new ApiException('移动位置错误');
}
}
//
$file->pid = $pid;
$file->save();
$files[] = $file;
}
});
foreach ($files as $file) {
$file->pushMsg('update', $file);
return Base::retSuccess('操作成功', $file);
}
return Base::retSuccess('操作成功', $files);
}
/**
* 删除文件()
* @api {get} api/file/remove 07. 删除文件()
*
* @apiParam {Number} id 文件ID
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName remove
*
* @apiParam {Numbers} ids 文件ID格式[id1, id2]
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function remove()
{
$id = intval(Request::input('id'));
User::auth();
//
$file = File::allowFind($id);
$ids = Request::input('ids');
//
if (!is_array($ids) || empty($ids)) {
return Base::retError('请选择删除的文件或文件夹');
}
if (count($ids) > 100) {
return Base::retError('一次最多只能删除100个文件或文件夹');
}
//
$files = [];
AbstractModel::transaction(function() use ($ids, &$files) {
foreach ($ids as $id) {
$file = File::permissionFind($id, 1000);
$file->deleteFile();
return Base::retSuccess('删除成功', $file);
$files[] = $file;
}
});
//
return Base::retSuccess('删除成功', $files);
}
/**
* 获取文件内容
* @api {get} api/file/content 08. 获取文件内容
*
* @apiParam {Number} id 文件ID
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName content
*
* @apiParam {Number|String} id
* - Number: 文件ID需要登录
* - String: 链接码(不需要登录,用于预览)
* @apiParam {String} only_update_at 仅获取update_at字段
* - no (默认)
* - yes
* @apiParam {String} down 直接下载
* - no: 浏览(默认)
* - yes: 下载office文件直接下载
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function content()
{
$id = intval(Request::input('id'));
$id = Request::input('id');
$down = Request::input('down', 'no');
$only_update_at = Request::input('only_update_at', 'no');
//
$file = File::allowFind($id);
if (Base::isNumber($id)) {
User::auth();
$file = File::permissionFind(intval($id));
} elseif ($id) {
$fileLink = FileLink::whereCode($id)->first();
$file = $fileLink?->file;
if (empty($file)) {
return Base::retError('链接不存在');
}
} else {
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->type, $content ? $content->content : []);
return FileContent::formatContent($file, $content?->content, $down == 'yes');
}
/**
* 保存文件内容
* @api {get} api/file/content/save 09. 保存文件内容
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName content__save
*
* @apiParam {Number} id 文件ID
* @apiParam {Object} [D] Request Payload 提交
* - content: 内容
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function content__save()
{
Base::checkClientVersion('0.9.13');
$user = User::auth();
//
$id = Base::getPostInt('id');
$content = Base::getPostValue('content');
//
$file = File::allowFind($id);
$file = File::permissionFind($id, 1);
//
$text = '';
if ($file->type == 'document') {
@ -314,12 +502,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;
}
}
@ -329,11 +516,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();
@ -346,9 +563,18 @@ class FileController extends AbstractController
}
/**
* 保存文件内容office
* @api {get} api/file/content/office 10. 保存文件内容office
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName content__office
*
* @apiParam {Number} id 文件ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function content__office()
{
@ -359,12 +585,12 @@ class FileController extends AbstractController
$key = Request::input('key');
$url = Request::input('url');
//
$file = File::allowFind($id);
$file = File::permissionFind($id, 1);
//
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);
@ -382,6 +608,7 @@ class FileController extends AbstractController
$content->save();
//
$file->size = $content->size;
$file->updated_at = Carbon::now();
$file->save();
$file->pushMsg('update', $file);
}
@ -390,23 +617,33 @@ class FileController extends AbstractController
}
/**
* 保存文件内容(上传文件)
* @api {get} api/file/content/upload 11. 保存文件内容(上传文件)
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName content__upload
*
* @apiParam {Number} [pid] 父级ID
* @apiParam {String} [files] 文件名
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function content__upload()
{
$user = User::auth();
//
$pid = intval(Request::input('pid'));
$webkitRelativePath = Request::input('webkitRelativePath');
//
$userid = $user->userid;
if ($pid > 0) {
if (File::wherePid($pid)->count() >= 300) {
return Base::retError('每个文件夹里最多只能创建300个文件或文件夹');
}
$row = File::allowFind($pid, '主文件不存在');
$row = File::permissionFind($pid, 1);
$userid = $row->userid;
} else {
if (File::whereUserid($user->userid)->wherePid(0)->count() >= 300) {
@ -414,7 +651,37 @@ class FileController extends AbstractController
}
}
//
$path = 'uploads/office/' . date("Ym") . '/u' . $user->userid . '/';
$dirs = explode("/", $webkitRelativePath);
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([
'pid' => $pid,
'type' => 'folder',
'name' => $dirName,
'userid' => $userid,
'created_id' => $user->userid,
]);
if ($dirRow->save()) {
$pushMsg[] = File::find($dirRow->id);
}
}
if (empty($dirRow)) {
throw new ApiException('创建文件夹失败');
}
$pid = $dirRow->id;
});
foreach ($pushMsg as $tmpRow) {
$tmpRow->pushMsg('add', $tmpRow);
}
}
}
//
$path = 'uploads/tmp/' . date("Ym") . '/';
$data = Base::upload([
"file" => Request::file('files'),
"type" => 'more',
@ -427,6 +694,9 @@ 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",
@ -438,12 +708,22 @@ class FileController extends AbstractController
'ofd' => "ofd",
'pdf' => "pdf",
'txt' => "txt",
'html', 'htm', 'asp', 'jsp', 'xml', 'json', 'properties', 'md', 'gitignore', 'log', 'java', 'py', 'c', 'cpp', 'sql', 'sh', 'bat', 'm', 'bas', 'prg', 'cmd',
'php', 'go', 'python', 'js', 'ftl', 'css', 'lua', 'rb', 'yaml', 'yml', 'h', 'cs', 'aspx' => "code",
'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', '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']),
@ -453,9 +733,11 @@ class FileController extends AbstractController
'created_id' => $user->userid,
]);
// 开始创建
return AbstractModel::transaction(function () use ($type, $user, $data, $file) {
return AbstractModel::transaction(function () use ($webkitRelativePath, $type, $user, $data, $file) {
$file->size = $data['size'] * 1024;
$file->save();
//
$data = Base::uploadMove($data, "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $file->id . "/");
$content = FileContent::createInstance([
'fid' => $file->id,
'content' => [
@ -465,24 +747,33 @@ class FileController extends AbstractController
'url' => $data['path']
],
'text' => '',
'size' => $data['size'] * 1024,
'size' => $file->size,
'userid' => $user->userid,
]);
$content->save();
//
$file->size = $content->size;
$file->save();
$tmpRow = File::find($file->id);
$tmpRow->pushMsg('add', $tmpRow);
//
$data = File::find($file->id);
$data->pushMsg('add', $data);
$data = $tmpRow->toArray();
$data['full_name'] = $webkitRelativePath ?: $data['name'];
return Base::retSuccess($data['name'] . ' 上传成功', $data);
});
}
/**
* 获取共享信息
* @api {get} api/file/share 12. 获取共享信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName share
*
* @apiParam {Number} id 文件ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function share()
{
@ -498,34 +789,44 @@ class FileController extends AbstractController
return Base::retError('仅限所有者操作');
}
//
$userids = FileUser::whereFileId($file->id)->pluck('userid')->toArray();
$list = FileUser::whereFileId($file->id)->get();
//
return Base::retSuccess('success', [
'id' => $file->id,
'userids' => $userids
'list' => $list
]);
}
/**
* 获取共享信息
* @api {get} api/file/share/update 13. 设置共享
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName share__update
*
* @apiParam {Number} id 文件ID
* @apiParam {String} action 动作
* - share: 设置共享
* - unshare: 取消共享
* @apiParam {Number} [share] 共享对象
* - 1: 共享给所有人(限管理员)
* - 2: 共享给指定成员
* @apiParam {Array} [userids] 共享成员,格式: [userid1, userid2, userid3]
* @apiParam {Number} [permission] 共享方式
* - 0:只读
* - 1:读写
* - -1: 删除
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function share__update()
{
$user = User::auth();
//
$id = intval(Request::input('id'));
$action = Request::input('action');
$share = intval(Request::input('share'));
$userids = Request::input('userids');
$permission = intval(Request::input('permission'));
//
if (!in_array($permission, [-1, 0, 1])) {
return Base::retError('参数错误');
}
//
$file = File::whereId($id)->first();
if (empty($file)) {
@ -539,56 +840,131 @@ class FileController extends AbstractController
return Base::retError('已经处于共享文件夹中');
}
//
if ($action == 'unshare') {
// 取消共享
if ($file->share == 1) {
$uids = WebSocket::select(['userid'])->pluck('userid')->toArray();
} else {
$uids = FileUser::whereFileId($file->id)->pluck('userid')->toArray();
if (!is_array($userids) || empty($userids)) {
return Base::retError('请选择共享对象');
}
$uids = array_values(array_diff($uids, [$user->userid]));
//
$file->setShare(0);
$message = '取消成功';
} else {
// 设置共享
switch ($share) {
case 1:
$user->isAdmin();
break;
case 2:
$array = [];
if (is_array($userids)) {
if ($permission === -1) {
// 取消共享
$action = "delete";
foreach ($userids as $userid) {
if (!intval($userid)) continue;
if (!User::whereUserid($userid)->exists()) continue;
FileUser::updateInsert([
if (FileUser::where([
'file_id' => $file->id,
'userid' => $userid,
]);
])->delete()) {
$array[] = $userid;
}
}
if (empty($array)) {
return Base::retError('请选择共享成员');
} else {
// 设置共享
$action = "update";
if (FileUser::whereFileId($file->id)->count() + count($userids) > 100) {
return Base::retError('共享人数上限100个成员');
}
foreach ($userids as $userid) {
if (FileUser::updateInsert([
'file_id' => $file->id,
'userid' => $userid,
], [
'permission' => $permission,
])) {
$array[] = $userid;
}
$builder = FileUser::whereFileId($file->id)->whereNotIn('userid', $array);
$uids = (clone $builder)->pluck('userid')->toArray();
$builder->delete();
break;
default:
return Base::retError('请选择共享对象');
}
$file->setShare($share);
$message = '设置成功';
}
//
$file->pushMsg('update', $file);
if (isset($uids)) {
$file->pushMsg('delete', null, $uids);
$file->setShare();
$file->pushMsg($action, $action == "delete" ? null : $file, $array);
return Base::retSuccess($action == "delete" ? "删除成功" : "设置成功", $file);
}
return Base::retSuccess($message, $file);
/**
* @api {get} api/file/share/out 14. 退出共享
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName share__out
*
* @apiParam {Number} id 文件ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function share__out()
{
$user = User::auth();
//
$id = intval(Request::input('id'));
//
$file = File::permissionFind($id);
//
if ($file->userid == $user->userid) {
return Base::retError('不能退出自己共享的文件');
}
if (FileUser::where([
'file_id' => $file->id,
'userid' => 0,
])->exists()) {
return Base::retError('无法退出共享所有人的文件或文件夹');
}
FileUser::where([
'file_id' => $file->id,
'userid' => $user->userid,
])->delete();
//
$file->setShare();
return Base::retSuccess("退出成功");
}
/**
* @api {get} api/file/link 15. 获取链接
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName link
*
* @apiParam {Number} id 文件ID
* @apiParam {String} refresh 刷新链接
* - no: 只获取(默认)
* - yes: 刷新链接,之前的将失效
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function link()
{
User::auth();
//
$id = intval(Request::input('id'));
$refresh = Request::input('refresh', 'no');
//
$file = File::permissionFind($id, 1000);
if ($file->type == 'folder') {
return Base::retError('文件夹暂不支持此功能');
}
//
$fileLink = FileLink::whereFileId($file->id)->first();
if (empty($fileLink)) {
$fileLink = FileLink::createInstance([
'file_id' => $file->id,
'code' => Base::generatePassword(64),
]);
$fileLink->save();
} else {
if ($refresh == 'yes') {
$fileLink->code = Base::generatePassword(64);
$fileLink->save();
}
}
return Base::retSuccess('success', [
'id' => $file->id,
'url' => Base::fillUrl('single/file/' . $fileLink->code),
'num' => $fileLink->num
]);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,466 @@
<?php
namespace App\Http\Controllers\Api;
use App\Exceptions\ApiException;
use App\Models\ProjectTask;
use App\Models\Report;
use App\Models\ReportReceive;
use App\Models\User;
use App\Module\Base;
use App\Tasks\PushTask;
use Carbon\Carbon;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Validation\Rule;
use Request;
use Illuminate\Support\Facades\Validator;
/**
* @apiDefine report
*
* 汇报
*/
class ReportController extends AbstractController
{
/**
* @api {get} api/report/my 01. 我发送的汇报
*
* @apiVersion 1.0.0
* @apiGroup report
* @apiName my
*
* @apiParam {String} [type] 汇报类型weekly:周报daily:日报
* @apiParam {Array} [created_at] 汇报时间
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:20,最大:50
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
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]))];
}
$list = $builder->where($whereArray)->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
return Base::retSuccess('success', $list);
}
/**
* @api {get} api/report/receive 02. 我接收的汇报
*
* @apiVersion 1.0.0
* @apiGroup report
* @apiName receive
*
* @apiParam {String} [username] 会员名
* @apiParam {String} [type] 汇报类型weekly:周报daily:日报
* @apiParam {Array} [created_at] 汇报时间
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:20,最大:50
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function receive(): array
{
$user = User::auth();
$builder = Report::with(['receivesUser']);
$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 . '%');
}
});
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));
if ($list->items()) {
foreach ($list->items() as $item) {
$item->receive_time = ReportReceive::query()->whereRid($item["id"])->whereUserid($user->userid)->value("receive_time");
}
}
return Base::retSuccess('success', $list);
}
/**
* @api {get} api/report/store 03. 保存并发送工作汇报
*
* @apiVersion 1.0.0
* @apiGroup report
* @apiName store
*
* @apiParam {Number} [id] 汇报ID
* @apiParam {String} [title] 汇报标题
* @apiParam {Array} [type] 汇报类型weekly:周报daily:日报
* @apiParam {Number} [content] 内容
* @apiParam {Number} [receive] 汇报对象
* @apiParam {Number} [offset] 偏移量
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function store(): array
{
$input = [
"id" => Base::getPostValue("id", 0),
"title" => Base::getPostValue("title"),
"type" => Base::getPostValue("type"),
"content" => Base::getPostValue("content"),
"receive" => Base::getPostValue("receive"),
// 以当前日期为基础的周期偏移量。例如选择了上一周那么就是 -1上一天同理。
"offset" => Base::getPostValue("offset", 0),
];
$validator = Validator::make($input, [
'id' => 'numeric',
'title' => 'required',
'type' => ['required', Rule::in([Report::WEEKLY, Report::DAILY])],
'content' => 'required',
'receive' => 'required',
'offset' => ['numeric', 'max:0'],
], [
'id.numeric' => 'ID只能是数字',
'title.required' => '请填写标题',
'type.required' => '请选择汇报类型',
'type.in' => '汇报类型错误',
'content.required' => '请填写汇报内容',
'receive.required' => '请选择接收人',
'offset.numeric' => '工作汇报周期格式错误,只能是数字',
'offset.max' => '只能提交当天/本周或者之前的的工作汇报',
]);
if ($validator->fails())
return Base::retError($validator->errors()->first());
$user = User::auth();
// 接收人
if (is_array($input["receive"])) {
// 删除当前登录人
$input["receive"] = array_diff($input["receive"], [$user->userid]);
// 查询用户是否存在
if (count($input["receive"]) !== User::whereIn("userid", $input["receive"])->count())
return Base::retError("用户不存在");
foreach ($input["receive"] as $userid) {
$input["receive_content"][] = [
"receive_time" => Carbon::now()->toDateTimeString(),
"userid" => $userid,
"read" => 0,
];
}
}
// 在事务中运行
Report::transaction(function () use ($input, $user) {
$id = $input["id"];
if ($id) {
// 编辑
$report = Report::getOne($id);
$report->updateInstance([
"title" => $input["title"],
"type" => $input["type"],
"content" => htmlspecialchars($input["content"]),
]);
} else {
// 生成唯一标识
$sign = Report::generateSign($input["type"], $input["offset"]);
// 检查唯一标识是否存在
if (empty($input["id"])) {
if (Report::query()->whereSign($sign)->whereType($input["type"])->count() > 0)
throw new ApiException("请勿重复提交工作汇报");
}
$report = Report::createInstance([
"title" => $input["title"],
"type" => $input["type"],
"content" => htmlspecialchars($input["content"]),
"userid" => $user->userid,
"sign" => $sign,
]);
}
$report->save();
if (!empty($input["receive_content"])) {
// 删除关联
$report->Receives()->delete();
// 保存接收人
$report->Receives()->createMany($input["receive_content"]);
}
// 推送消息
$userids = [];
foreach ($input["receive_content"] as $item) {
$userids[] = $item['userid'];
}
if ($userids) {
$params = [
'ignoreFd' => Request::header('fd'),
'userid' => $userids,
'msg' => [
'type' => 'report',
'action' => 'unreadUpdate',
]
];
Task::deliver(new PushTask($params, false));
}
});
return Base::retSuccess('保存成功');
}
/**
* @api {get} api/report/template 04. 生成汇报模板
*
* @apiVersion 1.0.0
* @apiGroup report
* @apiName template
*
* @apiParam {Array} [type] 汇报类型weekly:周报daily:日报
* @apiParam {Number} [offset] 偏移量
* @apiParam {String} [date] 时间
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function template(): array
{
$user = User::auth();
$type = trim(Request::input("type"));
$offset = abs(intval(Request::input("offset", 0)));
$id = intval(Request::input("offset", 0));
$now_dt = trim(Request::input("date")) ? Carbon::parse(Request::input("date")) : Carbon::now();
// 获取开始时间
if ($type === Report::DAILY) {
$start_time = Carbon::today();
if ($offset > 0) {
// 将当前时间调整为偏移量当天结束
$now_dt->subDays($offset)->endOfDay();
// 开始时间偏移量计算
$start_time->subDays($offset);
}
$end_time = Carbon::instance($start_time)->endOfDay();
} else {
$start_time = Carbon::now();
if ($offset > 0) {
// 将当前时间调整为偏移量当周结束
$now_dt->subWeeks($offset)->endOfDay();
// 开始时间偏移量计算
$start_time->subWeeks($offset);
}
$start_time->startOfWeek();
$end_time = Carbon::instance($start_time)->endOfWeek();
}
// 生成唯一标识
$sign = Report::generateSign($type, 0, Carbon::instance($start_time));
$one = Report::query()->whereSign($sign)->whereType($type)->first();
// 如果已经提交了相关汇报
if ($one && $id > 0) {
return Base::retSuccess('success', [
"content" => $one->content,
"title" => $one->title,
"id" => $one->id,
]);
}
// 已完成的任务
$completeContent = "";
$complete_task = ProjectTask::query()
->whereNotNull("complete_at")
->whereBetween("complete_at", [$start_time->toDateTimeString(), $end_time->toDateTimeString()])
->whereHas("taskUser", function ($query) use ($user) {
$query->where("userid", $user->userid);
})
->orderByDesc("id")
->get();
if ($complete_task->isNotEmpty()) {
foreach ($complete_task as $task) {
$complete_at = Carbon::parse($task->complete_at);
$pre = $type == Report::WEEKLY ? ('<span>[' . Base::Lang('周' . ['日', '一', '二', '三', '四', '五', '六'][$complete_at->dayOfWeek]) . ']</span>&nbsp;') : '';
$completeContent .= '<li>' . $pre . $task->name . '</li>';
}
} else {
$completeContent = '<li>&nbsp;</li>';
}
// 未完成的任务
$unfinishedContent = "";
$unfinished_task = ProjectTask::query()
->whereNull("complete_at")
->whereNotNull("start_at")
->where("end_at", "<", $end_time->toDateTimeString())
->whereHas("taskUser", function ($query) use ($user) {
$query->where("userid", $user->userid);
})
->orderByDesc("id")
->get();
if ($unfinished_task->isNotEmpty()) {
foreach ($unfinished_task as $task) {
empty($task->end_at) || $end_at = Carbon::parse($task->end_at);
$pre = (!empty($end_at) && $end_at->lt($now_dt)) ? '<span style="color:#ff0000;">[' . Base::Lang('超期') . ']</span>&nbsp;' : '';
$unfinishedContent .= '<li>' . $pre . $task->name . '</li>';
}
} else {
$unfinishedContent = '<li>&nbsp;</li>';
}
// 生成标题
if ($type === Report::WEEKLY) {
$title = $user->nickname . "的周报[" . $start_time->format("m/d") . "-" . $end_time->format("m/d") . "]";
$title .= "[" . $start_time->month . "月第" . $start_time->weekOfMonth . "周]";
} else {
$title = $user->nickname . "的日报[" . $start_time->format("Y/m/d") . "]";
}
$data = [
"time" => $start_time->toDateTimeString(),
"complete_task" => $complete_task,
"unfinished_task" => $unfinished_task,
"content" => '<h2>' . Base::Lang('已完成工作') . '</h2><ol>' .
$completeContent . '</ol><h2>' .
Base::Lang('未完成的工作') . '</h2><ol>' .
$unfinishedContent . '</ol>',
"title" => $title,
];
if ($one) {
$data['id'] = $one->id;
}
return Base::retSuccess('success', $data);
}
/**
* @api {get} api/report/detail 05. 报告详情
*
* @apiVersion 1.0.0
* @apiGroup report
* @apiName detail
*
* @apiParam {Number} [id] 报告id
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function detail(): array
{
$id = intval(trim(Request::input("id")));
if (empty($id))
return Base::retError("缺少ID参数");
$one = Report::getOne($id);
$one->type_val = $one->getRawOriginal("type");
$user = User::auth();
// 标记为已读
if (!empty($one->receivesUser)) {
foreach ($one->receivesUser as $item) {
if ($item->userid === $user->userid && $item->pivot->read === 0) {
$one->receivesUser()->updateExistingPivot($user->userid, [
"read" => 1,
]);
}
}
}
return Base::retSuccess("success", $one);
}
/**
* @api {get} api/report/last_submitter 06. 获取最后一次提交的接收人
*
* @apiVersion 1.0.0
* @apiGroup report
* @apiName last_submitter
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function last_submitter(): array
{
$one = Report::getLastOne();
return Base::retSuccess("success", empty($one["receives"]) ? [] : $one["receives"]);
}
/**
* @api {get} api/report/unread 07. 获取未读
*
* @apiVersion 1.0.0
* @apiGroup report
* @apiName unread
*
* @apiParam {Number} [userid] 用户id
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function unread(): array
{
$userid = intval(trim(Request::input("userid")));
$user = empty($userid) ? User::auth() : User::find($userid);
$data = Report::whereHas("Receives", function (Builder $query) use ($user) {
$query->where("userid", $user->userid)->where("read", 0);
})->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
return Base::retSuccess("success", $data);
}
/**
* @api {get} api/report/read 08. 标记汇报已读,可批量
*
* @apiVersion 1.0.0
* @apiGroup report
* @apiName read
*
* @apiParam {String} [ids] 报告id
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function read(): array
{
$user = User::auth();
$ids = Request::input("ids");
if (!is_array($ids) && !is_string($ids)) {
return Base::retError("请传入正确的工作汇报Id");
}
if (is_string($ids)) {
$ids = explode(",", $ids);
}
$data = Report::with(["receivesUser" => function (BelongsToMany $query) use ($user) {
$query->where("report_receives.userid", $user->userid)->where("read", 0);
}])->whereIn("id", $ids)->get();
if ($data->isNotEmpty()) {
foreach ($data as $item) {
(!empty($item->receivesUser) && $item->receivesUser->isNotEmpty()) && $item->receivesUser()->updateExistingPivot($user->userid, [
"read" => 1,
]);
}
}
return Base::retSuccess("success", $data);
}
}

View File

@ -24,7 +24,8 @@ class SystemController extends AbstractController
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置参数reg、login_code、password_policy、chat_nickname
* - all: 获取所有(需要管理员权限)
* - save: 保存设置参数reg、reg_invite、login_code、password_policy、project_invite、chat_nickname、auto_archived、archived_day
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@ -40,19 +41,37 @@ class SystemController extends AbstractController
User::auth('admin');
$all = Request::input();
foreach ($all AS $key => $value) {
if (!in_array($key, ['reg', 'login_code', 'password_policy', 'chat_nickname'])) {
if (!in_array($key, ['reg', 'reg_invite', 'login_code', 'password_policy', 'project_invite', 'chat_nickname', 'auto_archived', 'archived_day'])) {
unset($all[$key]);
}
}
$all['archived_day'] = floatval($all['archived_day']);
if ($all['auto_archived'] == 'open') {
if ($all['archived_day'] <= 0) {
return Base::retError('自动归档时间不可小于1天');
} elseif ($all['archived_day'] > 100) {
return Base::retError('自动归档时间不可大于100天');
}
}
$setting = Base::setting('system', Base::newTrim($all));
} else {
$setting = Base::setting('system');
}
//
if ($type == 'all' || $type == 'save') {
User::auth('admin');
$setting['reg_invite'] = $setting['reg_invite'] ?: Base::generatePassword(8);
} else {
if (isset($setting['reg_invite'])) unset($setting['reg_invite']);
}
//
$setting['reg'] = $setting['reg'] ?: 'open';
$setting['login_code'] = $setting['login_code'] ?: 'auto';
$setting['password_policy'] = $setting['password_policy'] ?: 'simple';
$setting['project_invite'] = $setting['project_invite'] ?: 'open';
$setting['chat_nickname'] = $setting['chat_nickname'] ?: 'optional';
$setting['auto_archived'] = $setting['auto_archived'] ?: 'close';
$setting['archived_day'] = floatval($setting['archived_day']) ?: 7;
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
@ -82,12 +101,16 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/priority 03. 获取优先级、保存优先级
* @api {post} api/system/priority 03. 任务优先级
*
* @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错误
@ -105,7 +128,7 @@ class SystemController extends AbstractController
return Base::retError('参数错误');
}
foreach ($list AS $item) {
if (empty($item['name']) || empty($item['color']) || empty($item['days']) || empty($item['priority'])) {
if (empty($item['name']) || empty($item['color']) || empty($item['priority'])) {
continue;
}
$array[] = [
@ -127,7 +150,54 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/info 04. 获取终端详细信息
* @api {post} api/system/column/template 04. 创建项目模板
*
* @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 05. 获取终端详细信息
*
* @apiVersion 1.0.0
* @apiGroup system
@ -156,7 +226,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ip 05. 获取IP地址
* @api {get} api/system/get/ip 06. 获取IP地址
*
* @apiVersion 1.0.0
* @apiGroup system
@ -171,7 +241,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/cnip 06. 是否中国IP地址
* @api {get} api/system/get/cnip 07. 是否中国IP地址
*
* @apiVersion 1.0.0
* @apiGroup system
@ -188,7 +258,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ipgcj02 07. 获取IP地址经纬度
* @api {get} api/system/get/ipgcj02 08. 获取IP地址经纬度
*
* @apiVersion 1.0.0
* @apiGroup system
@ -205,7 +275,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ipinfo 08. 获取IP地址详细信息
* @api {get} api/system/get/ipinfo 09. 获取IP地址详细信息
*
* @apiVersion 1.0.0
* @apiGroup system
@ -222,90 +292,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/appinfo 09. 获取应用下载信息
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName get__appinfo
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function get__appinfo() {
$array = [
'name' => '',
'version' => '',
'list' => [],
];
//
$files = [
base_path("package.json"),
base_path("electron/package.json")
];
$dist = base_path("electron/dist");
foreach ($files as $file) {
if (file_exists($file)) {
$packageArray = json_decode(file_get_contents($file), true);
$array['name'] = $packageArray['name'] ?? 'No app';
$array['version'] = $packageArray['version'] ?? '';
//
$list = [
[
'icon' => 'logo-apple',
'name' => 'macOS Intel',
'file' => "{$array['name']}-{$array['version']}.dmg"
],
[
'icon' => 'logo-apple',
'name' => 'macOS M1',
'file' => "{$array['name']}-{$array['version']}-arm64.dmg"
],
[
'icon' => 'logo-windows',
'name' => 'Windows x64',
'file' => "{$array['name']} Setup {$array['version']}.exe"
]
];
foreach ($list as $item) {
if (file_exists("{$dist}/{$item['file']}")) {
$item['url'] = Base::fillUrl('api/system/get/appdown?file=' . urlencode($item['file']));
$item['size'] = filesize("{$dist}/{$item['file']}");
$array['list'][] = $item;
}
}
}
if (count($array['list']) > 0) {
break;
}
}
//
if (count($array['list']) == 0) {
return Base::retError('No file');
}
return Base::retSuccess('success', $array);
}
/**
* @api {get} api/system/get/appdown 10. 下载应用
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName get__appdown
*
* @apiParam {String} file 文件名称
*/
public function get__appdown() {
$file = Request::input("file");
$path = base_path("electron/dist/" . $file);
if (!file_exists($path)) {
return Base::ajaxError("No file");
}
return Response::download($path);
}
/**
* @api {post} api/system/imgupload 11. 上传图片
* @api {post} api/system/imgupload 10. 上传图片
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -328,7 +315,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) {
@ -355,7 +342,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/imgview 12. 浏览图片空间
* @api {get} api/system/get/imgview 11. 浏览图片空间
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -373,7 +360,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 = [];
//
@ -451,7 +438,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/fileupload 13. 上传文件
* @api {post} api/system/fileupload 12. 上传文件
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -471,7 +458,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) {

View File

@ -31,7 +31,7 @@ class UsersController extends AbstractController
* @apiParam {String} email 邮箱
* @apiParam {String} password 密码
* @apiParam {String} [code] 登录验证码
* @apiParam {String} [key] 登陆验证码key
* @apiParam {String} [invite] 注册邀请码
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@ -46,25 +46,23 @@ class UsersController extends AbstractController
$setting = Base::setting('system');
if ($setting['reg'] == 'close') {
return Base::retError('未开放注册');
} elseif ($setting['reg'] == 'invite') {
$invite = trim(Request::input('invite'));
if (empty($invite) || $invite != $setting['reg_invite']) {
return Base::retError('请输入正确的邀请码');
}
}
$user = User::reg($email, $password);
} else {
$needCode = !Base::isError(User::needCode($email));
if ($needCode) {
$code = trim(Request::input('code'));
$key = trim(Request::input('key'));
if (empty($code)) {
return Base::retError('请输入验证码', ['code' => 'need']);
}
if (empty($key)) {
if (!Captcha::check($code)) {
return Base::retError('请输入正确的验证码', ['code' => 'need']);
}
} else {
if (!Captcha::check_api($code, $key)) {
return Base::retError('请输入正确的验证码', ['code' => 'need']);
}
}
}
//
$retError = function ($msg) use ($email) {
@ -155,7 +153,26 @@ class UsersController extends AbstractController
}
/**
* @api {get} api/users/info 05. 获取我的信息
* @api {get} api/users/reg/needinvite 05. 是否需要邀请码
*
* @apiDescription 用于判断注册是否需要邀请码
* @apiVersion 1.0.0
* @apiGroup users
* @apiName reg__needinvite
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function reg__needinvite()
{
return Base::retSuccess('success', [
'need' => Base::settingFind('system', 'reg') == 'invite'
]);
}
/**
* @api {get} api/users/info 06. 获取我的信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -191,7 +208,7 @@ class UsersController extends AbstractController
}
/**
* @api {get} api/users/editdata 06. 修改自己的资料
* @api {get} api/users/editdata 07. 修改自己的资料
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -218,13 +235,13 @@ class UsersController extends AbstractController
$userimg = is_array($userimg) ? $userimg[0]['path'] : $userimg;
$user->userimg = Base::unFillUrl($userimg);
} else {
$user->userimg = '';
$user->userimg = $user->getUserimgAttribute(null);
}
}
//昵称
if (Arr::exists($data, 'nickname')) {
$nickname = trim(Request::input('nickname'));
if (mb_strlen($nickname) < 2) {
if ($nickname && mb_strlen($nickname) < 2) {
return Base::retError('昵称不可以少于2个字');
} elseif (mb_strlen($nickname) > 20) {
return Base::retError('昵称最多只能设置20个字');
@ -235,7 +252,7 @@ class UsersController extends AbstractController
//职位/职称
if (Arr::exists($data, 'profession')) {
$profession = trim(Request::input('profession'));
if (mb_strlen($profession) < 2) {
if ($profession && mb_strlen($profession) < 2) {
return Base::retError('职位/职称不可以少于2个字');
} elseif (mb_strlen($profession) > 20) {
return Base::retError('职位/职称最多只能设置20个字');
@ -251,7 +268,7 @@ class UsersController extends AbstractController
}
/**
* @api {get} api/users/editpass 07. 修改自己的密码
* @api {get} api/users/editpass 08. 修改自己的密码
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -291,7 +308,7 @@ class UsersController extends AbstractController
}
/**
* @api {get} api/users/search 08. 搜索会员列表
* @api {get} api/users/search 09. 搜索会员列表
*
* @apiDescription 搜索会员列表
* @apiVersion 1.0.0
@ -299,11 +316,12 @@ class UsersController extends AbstractController
* @apiName searchinfo
*
* @apiParam {Object} keys 搜索条件
* - keys.key 昵称、邮箱
* - keys.key 昵称、邮箱关键字
* - keys.disable 0-排除禁止默认1-含禁止2-仅禁止
* - keys.project_id 在指定项目ID
* - keys.no_project_id 不在指定项目ID
* @apiParam {Object} sorts 排序方式
* - sorts.az 字母
* - sorts.az 字母asc|desc
*
* @apiParam {Number} [take] 获取数量10-100
* @apiParam {Number} [page] 当前页,默认:1赋值分页模式take参数无效
@ -319,13 +337,20 @@ class UsersController extends AbstractController
//
$keys = Request::input('keys');
$sorts = Request::input('sorts');
if (is_array($keys)) {
$keys = is_array($keys) ? $keys : [];
$sorts = is_array($sorts) ? $sorts : [];
//
if ($keys['key']) {
$builder->where(function($query) use ($keys) {
$query->where("email", "like", "%{$keys['key']}%")
->orWhere("nickname", "like", "%{$keys['key']}%");
});
}
if (intval($keys['disable']) == 0) {
$builder->whereNull("disable_at");
} elseif (intval($keys['disable']) == 2) {
$builder->whereNotNull("disable_at");
}
if (intval($keys['project_id']) > 0) {
$builder->whereIn('userid', function ($query) use ($keys) {
$query->select('userid')->from('project_users')->where('project_id', $keys['project_id']);
@ -336,12 +361,9 @@ class UsersController extends AbstractController
$query->select('userid')->from('project_users')->where('project_id', $keys['no_project_id']);
});
}
}
if (is_array($sorts)) {
if (in_array($sorts['az'], ['asc', 'desc'])) {
$builder->orderBy('az', $sorts['az']);
}
}
//
if (Request::exists('page')) {
$list = $builder->orderBy('userid')->paginate(Base::getPaginate(100, 10));
@ -352,7 +374,7 @@ class UsersController extends AbstractController
}
/**
* @api {get} api/users/basic 09. 获取指定会员基础信息
* @api {get} api/users/basic 10. 获取指定会员基础信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -386,7 +408,12 @@ class UsersController extends AbstractController
}
/**
* 会员列表(限管理员)
* @api {get} api/users/lists 11. 会员列表(限管理员)
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName lists
*
* @apiParam {Object} [keys] 搜索条件
* - keys.email 邮箱
@ -394,6 +421,10 @@ class UsersController extends AbstractController
* - keys.profession 职位
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:20,最大:50
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function lists()
{
@ -426,7 +457,12 @@ class UsersController extends AbstractController
}
/**
* 操作会员(限管理员)
* @api {get} api/users/operation 12. 操作会员(限管理员)
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName operation
*
* @apiParam {Number} userid 会员ID
* @apiParam {String} [type] 操作
@ -438,6 +474,10 @@ class UsersController extends AbstractController
* @apiParam {String} [password] 新的密码
* @apiParam {String} [nickname] 昵称
* @apiParam {String} [profession] 职位
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function operation()
{
@ -467,10 +507,12 @@ class UsersController extends AbstractController
case 'setdisable':
$upArray['identity'] = array_diff($userInfo->identity, ['disable']);
$upArray['identity'][] = 'disable';
$upArray['disable_at'] = Carbon::now();
break;
case 'cleardisable':
$upArray['identity'] = array_diff($userInfo->identity, ['disable']);
$upArray['disable_at'] = null;
break;
case 'delete':
@ -491,7 +533,7 @@ class UsersController extends AbstractController
// 昵称
if (Arr::exists($data, 'nickname')) {
$nickname = trim($data['nickname']);
if (mb_strlen($nickname) < 2) {
if ($nickname && mb_strlen($nickname) < 2) {
return Base::retError('昵称不可以少于2个字');
} elseif (mb_strlen($nickname) > 20) {
return Base::retError('昵称最多只能设置20个字');
@ -502,7 +544,7 @@ class UsersController extends AbstractController
// 职位/职称
if (Arr::exists($data, 'profession')) {
$profession = trim($data['profession']);
if (mb_strlen($profession) < 2) {
if ($profession && mb_strlen($profession) < 2) {
return Base::retError('职位/职称不可以少于2个字');
} elseif (mb_strlen($profession) > 20) {
return Base::retError('职位/职称最多只能设置20个字');

View File

@ -4,7 +4,7 @@
* 给apidoc项目增加顺序编号
*/
error_reporting(E_ALL & ~E_NOTICE);
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
$path = dirname(__FILE__). '/';
$lists = scandir($path);

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Module\Base;
use App\Tasks\AutoArchivedTask;
use App\Tasks\DeleteTmpTask;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Redirect;
@ -58,6 +59,8 @@ class IndexController extends InvokeController
// 删除过期的临时表数据
Task::deliver(new DeleteTmpTask('wg_tmp_msgs', 1));
Task::deliver(new DeleteTmpTask('tmp', 24));
// 自动归档任务
Task::deliver(new AutoArchivedTask());
return "success";
}

View File

@ -21,14 +21,20 @@ class VerifyCsrfToken extends Middleware
// 保存任务优先级
'api/system/priority/',
// 保存创建项目列表模板
'api/system/column/template/',
// 添加任务
'api/project/task/add/',
// 保存工作流
'api/project/flow/save/',
// 修改任务
'api/project/task/update/',
// 上传任务问题
'api/project/task/upload/',
// 聊天发文本
'api/dialog/msg/sendtext/',
// 聊天发文件
'api/dialog/msg/sendfile/',
@ -41,5 +47,8 @@ class VerifyCsrfToken extends Middleware
// 保存文件内容(上传)
'api/file/content/upload/',
// 保存汇报
'api/report/store/',
];
}

View File

@ -17,6 +17,8 @@ use Illuminate\Support\Facades\DB;
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel query()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
* @method static \Illuminate\Database\Eloquent\Model|object|static|null cancelAppend()
* @method static \Illuminate\Database\Eloquent\Model|object|static|null cancelHidden()
* @method static \Illuminate\Database\Eloquent\Builder|static with($relations)
* @method static \Illuminate\Database\Query\Builder|static select($columns = [])
* @method static \Illuminate\Database\Query\Builder|static whereNotIn($column, $values, $boolean = 'and')
@ -62,6 +64,24 @@ class AbstractModel extends Model
return $this->$key;
}
/**
* 取消附加值
* @return static
*/
protected function scopeCancelAppend()
{
return $this->setAppends([]);
}
/**
* 取消隐藏值
* @return static
*/
protected function scopeCancelHidden()
{
return $this->setHidden([]);
}
/**
* 为数组 / JSON 序列化准备日期。
* @param DateTimeInterface $date
@ -133,16 +153,23 @@ 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)) {
$row = new static;
$row->updateInstance(array_merge($where, $insert ?: $update));
$array = array_merge($where, $insert ?: $update);
if (isset($array[$row->primaryKey])) {
unset($array[$row->primaryKey]);
}
$row->updateInstance($array);
$isInsert = true;
} elseif ($update) {
$row->updateInstance($update);
$isInsert = false;
}
if (!$row->save()) {
return null;

View File

@ -51,43 +51,61 @@ class File extends AbstractModel
use SoftDeletes;
/**
* 是否有访问权限(没有时抛出异常)
* @param $userid
* 文件文件
*/
public function exceAllow($userid)
{
if (!$this->chackAllow($userid)) {
throw new ApiException('没有访问权限');
}
}
const codeExt = [
'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', '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 localExt = [
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw',
'tif', 'tiff',
'mp3', 'wav', 'mp4', 'flv',
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm',
];
/**
* 是否有访问权限
* 自己的文件夹
* 共享所有人的文件夹
* 在指定共享人员内
* @param $userid
* @return bool
* @return int -1:没有权限0:访问权限1:读写权限1000:所有者或创建者
*/
public function chackAllow($userid)
public function getPermission($userid)
{
if ($userid == $this->userid) {
// ① 自己的文件夹
return true;
if ($userid == $this->userid || $userid == $this->created_id) {
// ① 自己的文件夹 或 自己创建的文件夹
return 1000;
}
$row = $this->getShareInfo();
if ($row) {
if ($row->share == 1) {
// ② 共享所有人的文件夹
return true;
} elseif ($row->share == 2) {
// ③ 在指定共享人员内
if (FileUser::whereFileId($row->id)->whereUserid($userid)->exists()) {
return true;
$fileUser = FileUser::whereFileId($row->id)->where(function ($query) use ($userid) {
$query->where('userid', 0);
$query->orWhere('userid', $userid);
})->orderByDesc('permission')->first();
if ($fileUser) {
// ② 在指定共享成员内
return $fileUser->permission;
}
}
}
return false;
return -1;
}
/**
@ -96,7 +114,7 @@ class File extends AbstractModel
*/
public function getShareInfo()
{
if ($this->share > 0) {
if ($this->share) {
return $this;
}
$pid = $this->pid;
@ -105,7 +123,7 @@ class File extends AbstractModel
if (empty($row)) {
break;
}
if ($row->share > 0) {
if ($row->share) {
return $row;
}
$pid = $row->pid;
@ -125,7 +143,7 @@ class File extends AbstractModel
if (empty($row)) {
break;
}
if ($row->share > 0) {
if ($row->share) {
return true;
}
$pid = $row->pid;
@ -138,8 +156,12 @@ class File extends AbstractModel
* @param $share
* @return bool
*/
public function setShare($share)
public function setShare($share = null)
{
if ($share === null) {
$share = FileUser::whereFileId($this->id)->count() == 0 ? 0 : 1;
}
if ($this->share != $share) {
AbstractModel::transaction(function () use ($share) {
$this->share = $share;
$this->save();
@ -150,6 +172,7 @@ class File extends AbstractModel
}
}
});
}
return true;
}
@ -162,6 +185,8 @@ class File extends AbstractModel
AbstractModel::transaction(function () {
$this->delete();
$this->pushMsg('delete');
FileLink::whereFileId($this->id)->delete();
FileUser::whereFileId($this->id)->delete();
FileContent::whereFid($this->id)->delete();
$list = self::wherePid($this->id)->get();
if ($list->isNotEmpty()) {
@ -225,16 +250,99 @@ class File extends AbstractModel
/**
* 获取文件并检测权限
* @param $id
* @param null $noExistTis
* @param int $limit 要求权限: 0-访问权限、1-读写权限、1000-所有者或创建者
* @param $permission
* @return File
*/
public static function allowFind($id, $noExistTis = null)
public static function permissionFind($id, $limit = 0, &$permission = -1)
{
$file = File::find($id);
if (empty($file)) {
throw new ApiException($noExistTis ?: '文件不存在或已被删除');
throw new ApiException('文件不存在或已被删除');
}
//
$permission = $file->getPermission(User::userid());
if ($permission < $limit) {
$msg = match ($limit) {
1000 => '仅限所有者或创建者操作',
1 => '没有读写权限',
default => '没有访问权限',
};
throw new ApiException($msg);
}
$file->exceAllow(User::userid());
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),
];
$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);
$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;
}
}

View File

@ -8,9 +8,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Response;
/**
* Class FileContent
* App\Models\FileContent
*
* @package App\Models
* @property int $id
* @property int|null $fid 文件ID
* @property string|null $content 内容
@ -42,43 +41,53 @@ class FileContent extends AbstractModel
use SoftDeletes;
/**
* 获取格式内容
* @param $type
* 获取格式内容(或下载)
* @param File $file
* @param $content
* @param $download
* @return array|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public static function formatContent($type, $content)
public static function formatContent($file, $content, $download = false)
{
$content = Base::json2array($content);
if (in_array($type, ['word', 'excel', 'ppt'])) {
$name = $file->ext ? "{$file->name}.{$file->ext}" : null;
$content = Base::json2array($content ?: []);
if (in_array($file->type, ['word', 'excel', 'ppt'])) {
if (empty($content)) {
return Response::download(resource_path('assets/statics/office/empty.' . str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $type)));
return Response::download(resource_path('assets/statics/office/empty.' . str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $file->type)), $name);
}
return Response::download(public_path($content['url']));
return Response::download(public_path($content['url']), $name);
}
if (empty($content)) {
$content = match ($type) {
$content = match ($file->type) {
'document' => [
"type" => "md",
"type" => $file->ext,
"content" => "",
],
'sheet' => [
[
"name" => "Sheet1",
"config" => json_decode('{}'),
]
],
default => json_decode('{}'),
};
if ($download) {
abort(403, "This file is empty.");
}
} else {
$path = $content['url'];
if ($file->ext) {
$res = File::formatFileData([
'path' => $path,
'ext' => $file->ext,
'size' => $file->size,
'name' => $file->name,
]);
$content = $res['content'];
} else {
$content['preview'] = false;
if ($content['ext'] && !in_array($content['ext'], ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'])) {
$url = 'http://' . env('APP_IPPR') . '.3/' . $content['url'];
if ($type == 'image') {
$url = Base::fillUrl($content['url']);
}
$content['url'] = base64_encode($url);
$content['preview'] = true;
if ($download) {
$filePath = public_path($path);
if (isset($filePath)) {
return Response::download($filePath, $name);
} else {
abort(403, "This file not support download.");
}
}
}
return Base::retSuccess('success', [ 'content' => $content ]);

35
app/Models/FileLink.php Normal file
View File

@ -0,0 +1,35 @@
<?php
namespace App\Models;
/**
* App\Models\FileLink
*
* @property int $id
* @property int|null $file_id 项目ID
* @property int|null $num 累计访问
* @property string|null $code 链接码
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\File|null $file
* @method static \Illuminate\Database\Eloquent\Builder|FileLink newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|FileLink newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|FileLink query()
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereCode($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereFileId($value)
* @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)
* @mixin \Eloquent
*/
class FileLink extends AbstractModel
{
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function file(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(File::class, 'id', 'file_id');
}
}

View File

@ -4,12 +4,12 @@ namespace App\Models;
/**
* Class FileUser
* App\Models\FileUser
*
* @package App\Models
* @property int $id
* @property int|null $file_id 项目ID
* @property int|null $userid 成员ID
* @property int|null $permission 权限0只读1读写
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|FileUser newModelQuery()
@ -18,6 +18,7 @@ namespace App\Models;
* @method static \Illuminate\Database\Eloquent\Builder|FileUser whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileUser whereFileId($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileUser whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileUser wherePermission($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileUser whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileUser whereUserid($value)
* @mixin \Eloquent

View File

@ -3,40 +3,36 @@
namespace App\Models;
use App\Exceptions\ApiException;
use App\Module\Base;
use App\Tasks\PushTask;
use Carbon\Carbon;
use DB;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
use Request;
/**
* Class Project
* App\Models\Project
*
* @package App\Models
* @property int $id
* @property string|null $name 名称
* @property string|null $desc 描述、备注
* @property int|null $userid 创建人
* @property int|mixed $dialog_id 聊天会话ID
* @property int|null $dialog_id 聊天会话ID
* @property string|null $archived_at 归档时间
* @property int|null $archived_userid 归档会员
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read int $owner_userid
* @property-read int $task_complete
* @property-read int $task_my_complete
* @property-read int $task_my_num
* @property-read int $task_my_percent
* @property-read int $task_num
* @property-read int $task_percent
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectColumn[] $projectColumn
* @property-read int|null $project_column_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectLog[] $projectLog
* @property-read int|null $project_log_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectUser[] $projectUser
* @property-read int|null $project_user_count
* @method static \Illuminate\Database\Eloquent\Builder|Project authData($userid = null)
* @method static \Illuminate\Database\Eloquent\Builder|Project allData($userid = null)
* @method static \Illuminate\Database\Eloquent\Builder|Project authData($userid = null, $owner = null)
* @method static \Illuminate\Database\Eloquent\Builder|Project newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Project newQuery()
* @method static \Illuminate\Database\Query\Builder|Project onlyTrashed()
@ -59,99 +55,14 @@ class Project extends AbstractModel
{
use SoftDeletes;
const projectSelect = [
'projects.*',
'project_users.owner',
protected $hidden = [
'deleted_at',
];
protected $appends = [
'task_num',
'task_complete',
'task_percent',
'task_my_num',
'task_my_complete',
'task_my_percent',
'owner_userid',
];
/**
* 生成任务数据
*/
private function generateTaskData()
{
if (!isset($this->appendattrs['task_num'])) {
$builder = ProjectTask::whereProjectId($this->id)->whereParentId(0)->whereNull('archived_at');
$this->appendattrs['task_num'] = $builder->count();
$this->appendattrs['task_complete'] = $builder->whereNotNull('complete_at')->count();
$this->appendattrs['task_percent'] = $this->appendattrs['task_num'] ? intval($this->appendattrs['task_complete'] / $this->appendattrs['task_num'] * 100) : 0;
//
$builder = ProjectTask::whereProjectId($this->id)->whereParentId(0)->authData(User::userid())->whereNull('archived_at');
$this->appendattrs['task_my_num'] = $builder->count();
$this->appendattrs['task_my_complete'] = $builder->whereNotNull('complete_at')->count();
$this->appendattrs['task_my_percent'] = $this->appendattrs['task_my_num'] ? intval($this->appendattrs['task_my_complete'] / $this->appendattrs['task_my_num'] * 100) : 0;
}
}
/**
* 任务数量
* @return int
*/
public function getTaskNumAttribute()
{
$this->generateTaskData();
return $this->appendattrs['task_num'];
}
/**
* 任务完成数量
* @return int
*/
public function getTaskCompleteAttribute()
{
$this->generateTaskData();
return $this->appendattrs['task_complete'];
}
/**
* 任务完成率
* @return int
*/
public function getTaskPercentAttribute()
{
$this->generateTaskData();
return $this->appendattrs['task_percent'];
}
/**
* 任务数量(我的)
* @return int
*/
public function getTaskMyNumAttribute()
{
$this->generateTaskData();
return $this->appendattrs['task_my_num'];
}
/**
* 任务完成数量(我的)
* @return int
*/
public function getTaskMyCompleteAttribute()
{
$this->generateTaskData();
return $this->appendattrs['task_my_complete'];
}
/**
* 任务完成率(我的)
* @return int
*/
public function getTaskMyPercentAttribute()
{
$this->generateTaskData();
return $this->appendattrs['task_my_percent'];
}
/**
* 负责人会员ID
* @return int
@ -159,7 +70,7 @@ class Project extends AbstractModel
public function getOwnerUseridAttribute()
{
if (!isset($this->appendattrs['owner_userid'])) {
$ownerUser = $this->projectUser->where('owner', 1)->first();
$ownerUser = ProjectUser::whereProjectId($this->id)->whereOwner(1)->first();
$this->appendattrs['owner_userid'] = $ownerUser ? $ownerUser->userid : 0;
}
return $this->appendattrs['owner_userid'];
@ -190,19 +101,73 @@ class Project extends AbstractModel
}
/**
* 查询自己的项目
* 查询所有项目与正常查询多返回owner字段
* @param self $query
* @param null $userid
* @return self
*/
public function scopeAuthData($query, $userid = null)
public function scopeAllData($query, $userid = null)
{
$userid = $userid ?: User::userid();
$query->join('project_users', 'projects.id', '=', 'project_users.project_id')
->where('project_users.userid', $userid);
$query
->select([
'projects.*',
'project_users.owner',
'project_users.top_at',
])
->leftJoin('project_users', function ($leftJoin) use ($userid) {
$leftJoin
->on('project_users.userid', '=', DB::raw($userid))
->on('projects.id', '=', 'project_users.project_id');
});
return $query;
}
/**
* 查询自己负责或参与的项目
* @param self $query
* @param null $userid
* @param null $owner
* @return self
*/
public function scopeAuthData($query, $userid = null, $owner = null)
{
$userid = $userid ?: User::userid();
$query
->select([
'projects.*',
'project_users.owner',
'project_users.top_at',
])
->join('project_users', 'projects.id', '=', 'project_users.project_id')
->where('project_users.userid', $userid);
if ($owner !== null) {
$query->where('project_users.owner', $owner);
}
return $query;
}
/**
* 获取任务统计数据
* @param $userid
* @return array
*/
public function getTaskStatistics($userid)
{
$array = [];
$builder = ProjectTask::whereProjectId($this->id)->whereNull('archived_at');
$array['task_num'] = $builder->count();
$array['task_complete'] = $builder->whereNotNull('complete_at')->count();
$array['task_percent'] = $array['task_num'] ? intval($array['task_complete'] / $array['task_num'] * 100) : 0;
//
$builder = ProjectTask::authData($userid, 1)->where('project_tasks.project_id', $this->id)->whereNull('project_tasks.archived_at');
$array['task_my_num'] = $builder->count();
$array['task_my_complete'] = $builder->whereNotNull('project_tasks.complete_at')->count();
$array['task_my_percent'] = $array['task_my_num'] ? intval($array['task_my_complete'] / $array['task_my_num'] * 100) : 0;
//
return $array;
}
/**
* 加入项目
* @param int $userid 加入的会员ID
@ -249,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();
}
/**
@ -277,6 +242,7 @@ class Project extends AbstractModel
if ($archived_at === null) {
// 取消归档
$this->archived_at = null;
$this->archived_userid = User::userid();
$this->addLog("项目取消归档");
$this->pushMsg('add', $this);
ProjectTask::whereProjectId($this->id)->whereArchivedFollow(1)->update([
@ -322,18 +288,23 @@ class Project extends AbstractModel
/**
* 添加项目日志
* @param string $detail
* @param array $record
* @param int $userid
* @return ProjectLog
*/
public function addLog($detail, $userid = 0)
public function addLog($detail, $record = [], $userid = 0)
{
$log = ProjectLog::createInstance([
$array = [
'project_id' => $this->id,
'column_id' => 0,
'task_id' => 0,
'userid' => $userid ?: User::userid(),
'detail' => $detail,
]);
];
if ($record) {
$array['record'] = $record;
}
$log = ProjectLog::createInstance($array);
$log->save();
return $log;
}
@ -341,20 +312,36 @@ class Project extends AbstractModel
/**
* 推送消息
* @param string $action
* @param array $data 发送内容,默认为[id=>项目ID]
* @param array|self $data 发送内容,默认为[id=>项目ID]
* @param array $userid 指定会员,默认为项目所有成员
*/
public function pushMsg($action, $data = null, $userid = null)
{
if ($data === null) {
$data = ['id' => $this->id];
} elseif ($data instanceof self) {
$data = $data->toArray();
}
//
$array = [$userid, []];
if ($userid === null) {
$userid = $this->relationUserids();
$array[0] = $this->relationUserids();
} elseif (!is_array($userid)) {
$array[0] = [$userid];
}
//
if (isset($data['owner'])) {
$owners = ProjectUser::whereProjectId($data['id'])->whereOwner(1)->pluck('userid')->toArray();
$array = [array_intersect($array[0], $owners), array_diff($array[0], $owners)];
}
//
foreach ($array as $index => $item) {
if ($index > 0) {
$data['owner'] = 0;
}
$params = [
'ignoreFd' => Request::header('fd'),
'userid' => $userid,
'userid' => array_values($item),
'msg' => [
'type' => 'project',
'action' => $action,
@ -364,22 +351,141 @@ class Project extends AbstractModel
$task = new PushTask($params, false);
Task::deliver($task);
}
}
/**
* 根据用户获取项目信息(用于判断会员是否存在项目内)
* 添加工作流
* @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
* @param bool $ignoreArchived 排除已归档
* @param null|bool $archived true:仅限未归档, false:仅限已归档, null:不限制
* @param null|bool $mustOwner true:仅限项目负责人, false:仅限非项目负责人, null:不限制
* @return self
*/
public static function userProject($project_id, $ignoreArchived = true)
public static function userProject($project_id, $archived = true, $mustOwner = null)
{
$project = self::select(self::projectSelect)->authData()->where('projects.id', intval($project_id))->first();
$project = self::authData()->where('projects.id', intval($project_id))->first();
if (empty($project)) {
throw new ApiException('项目不存在或不在成员列表内', [ 'project_id' => $project_id ], -4001);
}
if ($ignoreArchived && $project->archived_at != null) {
if ($archived === true && $project->archived_at != null) {
throw new ApiException('项目已归档', [ 'project_id' => $project_id ], -4001);
}
if ($archived === false && $project->archived_at == null) {
throw new ApiException('项目未归档', [ 'project_id' => $project_id ]);
}
if ($mustOwner === true && !$project->owner) {
throw new ApiException('仅限项目负责人操作', [ 'project_id' => $project_id ]);
}
if ($mustOwner === false && $project->owner) {
throw new ApiException('禁止项目负责人操作', [ 'project_id' => $project_id ]);
}
return $project;
}
}

View File

@ -9,9 +9,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Request;
/**
* Class ProjectColumn
* App\Models\ProjectColumn
*
* @package App\Models
* @property int $id
* @property int|null $project_id 项目ID
* @property string|null $name 列表名称

View File

@ -0,0 +1,51 @@
<?php
namespace App\Models;
use App\Module\Base;
/**
* App\Models\ProjectFlow
*
* @property int $id
* @property int|null $project_id 项目ID
* @property string|null $name 流程名称
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectFlowItem[] $projectFlowItem
* @property-read int|null $project_flow_item_count
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereProjectId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereUpdatedAt($value)
* @mixin \Eloquent
*/
class ProjectFlow extends AbstractModel
{
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function projectFlowItem(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(ProjectFlowItem::class, 'flow_id', 'id')->orderBy('sort');
}
/**
* @return mixed
*/
public function deleteFlow()
{
return AbstractModel::transaction(function() {
ProjectFlowItem::whereProjectId($this->project_id)->chunk(100, function($list) {
foreach ($list as $item) {
$item->deleteFlowItem();
}
});
return $this->delete();
});
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace App\Models;
use App\Module\Base;
/**
* App\Models\ProjectFlowItem
*
* @property int $id
* @property int|null $project_id 项目ID
* @property int|null $flow_id 流程ID
* @property string|null $name 名称
* @property string|null $status 状态
* @property array $turns 可流转
* @property array $userids 自动负责人ID
* @property string|null $usertype 流转模式
* @property int|null $userlimit 限制负责人
* @property int|null $sort 排序
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\ProjectFlow|null $projectFlow
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereFlowId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereProjectId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereSort($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereStatus($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereTurns($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereUserids($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereUserlimit($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereUsertype($value)
* @mixin \Eloquent
*/
class ProjectFlowItem extends AbstractModel
{
protected $hidden = [
'created_at',
'updated_at',
];
/**
* @param $value
* @return array
*/
public function getTurnsAttribute($value)
{
if (is_array($value)) {
return $value;
}
return Base::json2array($value);
}
/**
* @param $value
* @return array
*/
public function getUseridsAttribute($value)
{
if (is_array($value)) {
return $value;
}
return Base::json2array($value);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function projectFlow(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(ProjectFlow::class, 'id', 'flow_id');
}
/**
* @return bool|null
*/
public function deleteFlowItem()
{
ProjectTask::whereFlowItemId($this->id)->update([
'flow_item_id' => 0,
'flow_item_name' => "",
]);
return $this->delete();
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Models;
/**
* App\Models\ProjectInvite
*
* @property int $id
* @property int|null $project_id 项目ID
* @property int|null $num 累计邀请
* @property string|null $code 链接码
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read bool $already
* @property-read \App\Models\Project|null $project
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite whereCode($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite whereNum($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite whereProjectId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite whereUpdatedAt($value)
* @mixin \Eloquent
*/
class ProjectInvite extends AbstractModel
{
protected $appends = [
'already',
];
/**
* 是否已加入
* @return bool
*/
public function getAlreadyAttribute()
{
if (!isset($this->appendattrs['already'])) {
$this->appendattrs['already'] = false;
if (User::userid()) {
$this->appendattrs['already'] = (bool)$this->project?->projectUser?->where('userid', User::userid())->count();
}
}
return $this->appendattrs['already'];
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function project(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(Project::class, 'id', 'project_id');
}
}

View File

@ -2,18 +2,21 @@
namespace App\Models;
use App\Module\Base;
/**
* Class ProjectLog
* App\Models\ProjectLog
*
* @package App\Models
* @property int $id
* @property int|null $project_id 项目ID
* @property int|null $column_id 列表ID
* @property int|null $task_id 项目ID
* @property int|null $userid 会员ID
* @property string|null $detail 详细信息
* @property array $record 记录数据
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\ProjectTask|null $projectTask
* @property-read \App\Models\User|null $user
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog newQuery()
@ -23,6 +26,7 @@ namespace App\Models;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereDetail($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereProjectId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereRecord($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereTaskId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereUserid($value)
@ -31,6 +35,18 @@ namespace App\Models;
class ProjectLog extends AbstractModel
{
/**
* @param $value
* @return array
*/
public function getRecordAttribute($value)
{
if (is_array($value)) {
return $value;
}
return Base::json2array($value);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
@ -39,4 +55,12 @@ class ProjectLog extends AbstractModel
return $this->hasOne(User::class, 'userid', 'userid');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function projectTask(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(ProjectTask::class, 'id', 'task_id');
}
}

View File

@ -7,6 +7,7 @@ use App\Module\Base;
use App\Tasks\PushTask;
use Arr;
use Carbon\Carbon;
use DB;
use Exception;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -20,6 +21,8 @@ use Request;
* @property int|null $project_id 项目ID
* @property int|null $column_id 列表ID
* @property int|null $dialog_id 聊天会话ID
* @property int|null $flow_item_id 工作流状态ID
* @property string|null $flow_item_name 工作流状态名称
* @property string|null $name 标题
* @property string|null $color 颜色
* @property string|null $desc 描述
@ -41,19 +44,21 @@ use Request;
* @property-read int $file_num
* @property-read int $msg_num
* @property-read bool $overdue
* @property-read bool $owner
* @property-read int $percent
* @property-read int $sub_complete
* @property-read int $sub_num
* @property-read bool $today
* @property-read \App\Models\Project|null $project
* @property-read \App\Models\ProjectColumn|null $projectColumn
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskFile[] $taskFile
* @property-read int|null $task_file_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskTag[] $taskTag
* @property-read int|null $task_tag_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskUser[] $taskUser
* @property-read int|null $task_user_count
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask authData($userid = null)
* @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 newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newQuery()
* @method static \Illuminate\Database\Query\Builder|ProjectTask onlyTrashed()
@ -69,6 +74,8 @@ use Request;
* @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)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereFlowItemId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereFlowItemName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask wherePColor($value)
@ -89,7 +96,6 @@ class ProjectTask extends AbstractModel
use SoftDeletes;
protected $appends = [
'owner',
'file_num',
'msg_num',
'sub_num',
@ -99,22 +105,6 @@ class ProjectTask extends AbstractModel
'overdue',
];
/**
* 是否我是负责人
* @return bool
*/
public function getOwnerAttribute()
{
if (!isset($this->appendattrs['owner'])) {
if ($this->parent_id > 0) {
$this->appendattrs['owner'] = ProjectTaskUser::whereTaskId($this->id)->whereUserid(User::userid())->whereOwner(1)->exists();
} else {
$this->appendattrs['owner'] = ProjectTaskUser::whereTaskPid($this->id)->whereUserid(User::userid())->whereOwner(1)->exists();
}
}
return $this->appendattrs['owner'];
}
/**
* 附件数量
* @return int
@ -122,7 +112,7 @@ class ProjectTask extends AbstractModel
public function getFileNumAttribute()
{
if (!isset($this->appendattrs['file_num'])) {
$this->appendattrs['file_num'] = ProjectTaskFile::whereTaskId($this->id)->count();
$this->appendattrs['file_num'] = $this->parent_id > 0 ? 0 : ProjectTaskFile::whereTaskId($this->id)->count();
}
return $this->appendattrs['file_num'];
}
@ -232,6 +222,14 @@ class ProjectTask extends AbstractModel
return $this->hasOne(Project::class, 'id', 'project_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function projectColumn(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(ProjectColumn::class, 'id', 'column_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
@ -265,16 +263,67 @@ class ProjectTask extends AbstractModel
}
/**
* 查询自己的任务
* 查询所有任务与正常查询多返回owner字段
* @param self $query
* @param null $userid
* @return self
*/
public function scopeAuthData($query, $userid = null)
public function scopeAllData($query, $userid = null)
{
$userid = $userid ?: User::userid();
$query->whereIn('id', function ($qy) use ($userid) {
$qy->select('task_pid')->from('project_task_users')->where('userid', $userid);
$query
->select([
'project_tasks.*',
'project_task_users.owner'
])
->leftJoin('project_task_users', function ($leftJoin) use ($userid) {
$leftJoin
->on('project_task_users.userid', '=', DB::raw($userid))
->on('project_tasks.id', '=', 'project_task_users.task_id');
});
return $query;
}
/**
* 查询自己负责或参与的任务
* @param self $query
* @param null $userid
* @param null $owner
* @return self
*/
public function scopeAuthData($query, $userid = null, $owner = null)
{
$userid = $userid ?: User::userid();
$query
->select([
'project_tasks.*',
'project_task_users.owner'
])
->join('project_task_users', 'project_tasks.id', '=', 'project_task_users.task_id')
->where('project_task_users.userid', $userid);
if ($owner !== null) {
$query->where('project_task_users.owner', $owner);
}
return $query;
}
/**
* 指定范围内的任务
* @param $query
* @param $start
* @param $end
* @return mixed
*/
public function scopeBetweenTime($query, $start, $end)
{
$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);
});
});
return $query;
}
@ -293,11 +342,32 @@ class ProjectTask extends AbstractModel
$content = $data['content'];
$times = $data['times'];
$owner = $data['owner'];
$add_assist = intval($data['add_assist']);
$subtasks = $data['subtasks'];
$p_level = intval($data['p_level']);
$p_name = $data['p_name'];
$p_color = $data['p_color'];
$top = intval($data['top']);
$userid = User::userid();
//
if (ProjectTask::whereProjectId($project_id)
->whereNull('project_tasks.complete_at')
->whereNull('project_tasks.archived_at')
->count() > 2000) {
throw new ApiException('项目内未完成任务最多不能超过2000个');
}
if (ProjectTask::whereColumnId($column_id)
->whereNull('project_tasks.complete_at')
->whereNull('project_tasks.archived_at')
->count() > 500) {
throw new ApiException('单个列表未完成任务最多不能超过500个');
}
if ($parent_id > 0 && ProjectTask::whereParentId($parent_id)
->whereNull('project_tasks.complete_at')
->whereNull('project_tasks.archived_at')
->count() > 50) {
throw new ApiException('每个任务的子任务最多不能超过50个');
}
//
$retPre = $parent_id ? '子任务' : '任务';
$task = self::createInstance([
@ -309,7 +379,7 @@ class ProjectTask extends AbstractModel
'p_color' => $p_color,
]);
if ($content) {
$task->desc = Base::getHtml($content);
$task->desc = Base::getHtml($content, 100);
}
// 标题
if (empty($name)) {
@ -321,13 +391,11 @@ class ProjectTask extends AbstractModel
// 时间
if ($times) {
list($start, $end) = is_string($times) ? explode(",", $times) : (is_array($times) ? $times : []);
if (Base::isDate($start) && Base::isDate($end)) {
if ($start != $end) {
if (Base::isDate($start) && Base::isDate($end) && $start != $end) {
$task->start_at = Carbon::parse($start);
$task->end_at = Carbon::parse($end);
}
}
}
// 负责人
$owner = is_array($owner) ? $owner : [$owner];
$tmpArray = [];
@ -336,20 +404,46 @@ class ProjectTask extends AbstractModel
if (!ProjectUser::whereProjectId($project_id)->whereUserid($uid)->exists()) {
throw new ApiException($retPre . '负责人填写错误');
}
if (ProjectTask::authData($uid)
->whereNull('project_tasks.complete_at')
->whereNull('project_tasks.archived_at')
->count() > 500) {
throw new ApiException(User::userid2nickname($uid) . '负责或参与的未完成任务最多不能超过500个');
}
$tmpArray[] = $uid;
}
$owner = $tmpArray;
// 协助人员
$assist = [];
if (!in_array($userid, $owner) && $add_assist) {
$assist = [$userid];
}
// 创建人
$task->userid = User::userid();
$task->userid = $userid;
// 排序位置
if ($top) {
$task->sort = intval(self::whereColumnId($task->column_id)->orderBy('sort')->value('sort')) - 1;
} else {
$task->sort = intval(self::whereColumnId($task->column_id)->orderByDesc('sort')->value('sort')) + 1;
}
// 工作流
$projectFlow = ProjectFlow::whereProjectId($project_id)->orderByDesc('id')->first();
if ($projectFlow) {
$projectFlowItem = ProjectFlowItem::whereFlowId($projectFlow->id)->orderBy('sort')->get();
// 赋一个开始状态
foreach ($projectFlowItem as $item) {
if ($item->status == 'start') {
$task->flow_item_id = $item->id;
$task->flow_item_name = $item->status . "|" . $item->name;
$owner = array_merge($owner, $item->userids);
break;
}
}
}
//
return AbstractModel::transaction(function() use ($subtasks, $content, $owner, $task) {
return AbstractModel::transaction(function() use ($assist, $times, $subtasks, $content, $owner, $task) {
$task->save();
$owner = array_values(array_unique($owner));
foreach ($owner as $uid) {
ProjectTaskUser::createInstance([
'project_id' => $task->project_id,
@ -359,6 +453,16 @@ class ProjectTask extends AbstractModel
'owner' => 1,
])->save();
}
$assist = array_values(array_unique(array_diff($assist, $owner)));
foreach ($assist as $uid) {
ProjectTaskUser::createInstance([
'project_id' => $task->project_id,
'task_id' => $task->id,
'task_pid' => $task->parent_id ?: $task->id,
'userid' => $uid,
'owner' => 0,
])->save();
}
if ($content) {
ProjectTaskContent::createInstance([
'project_id' => $task->project_id,
@ -368,13 +472,24 @@ class ProjectTask extends AbstractModel
}
if ($task->parent_id == 0 && $subtasks && is_array($subtasks)) {
foreach ($subtasks as $subtask) {
list($start, $end) = is_string($subtask['times']) ? explode(",", $subtask['times']) : (is_array($subtask['times']) ? $subtask['times'] : []);
if (Base::isDate($start) && Base::isDate($end) && $start != $end) {
if (Carbon::parse($start)->lt($task->start_at)) {
throw new ApiException('子任务开始时间不能小于主任务开始时间');
}
if (Carbon::parse($end)->gt($task->end_at)) {
throw new ApiException('子任务结束时间不能大于主任务结束时间');
}
} else {
$subtask['times'] = $times;
}
$subtask['parent_id'] = $task->id;
$subtask['project_id'] = $task->project_id;
$subtask['column_id'] = $task->column_id;
self::addTask($subtask);
}
}
$task->addLog("创建{任务}" . $task->name);
$task->addLog("创建{任务}");
return $task;
});
}
@ -382,12 +497,124 @@ class ProjectTask extends AbstractModel
/**
* 修改任务
* @param $data
* @param $updateContent
* @param array $updateMarking 更新的标记
* - is_update_project 是否更新项目数据(项目统计)
* - is_update_content 是否更新任务详情
* - is_update_maintask 是否更新主任务
* - is_update_subtask 是否更新子任务
* @return bool
*/
public function updateTask($data, &$updateContent)
public function updateTask($data, &$updateMarking = [])
{
AbstractModel::transaction(function () use ($data, &$updateContent) {
AbstractModel::transaction(function () use ($data, &$updateMarking) {
// 判断版本
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('任务状态未发生改变');
}
$flowData = [
'flow_item_id' => $this->flow_item_id,
'flow_item_name' => $this->flow_item_name,
];
$currentFlowItem = null;
$newFlowItem = ProjectFlowItem::whereProjectId($this->project_id)->find(intval($data['flow_item_id']));
if (empty($newFlowItem) || empty($newFlowItem->projectFlow)) {
throw new ApiException('任务状态不存在');
}
if ($this->flow_item_id) {
// 判断符合流转
$currentFlowItem = ProjectFlowItem::find($this->flow_item_id);
if ($currentFlowItem) {
if (!in_array($newFlowItem->id, $currentFlowItem->turns)) {
throw new ApiException("当前状态[{$currentFlowItem->name}]不可流转到[{$newFlowItem->name}]");
}
if ($currentFlowItem->userlimit) {
if (!$isProjectOwner && !in_array(User::userid(), $currentFlowItem->userids)) {
throw new ApiException("当前状态[{$currentFlowItem->name}]仅限状态负责人或项目负责人修改");
}
}
}
}
if ($newFlowItem->status == 'end') {
// 判断自动完成
if (!$this->complete_at) {
$flowData['complete_at'] = $this->complete_at;
$data['complete_at'] = date("Y-m-d H:i");
}
} else {
// 判断自动打开
if ($this->complete_at) {
$flowData['complete_at'] = $this->complete_at;
$data['complete_at'] = false;
}
}
if ($newFlowItem->userids) {
// 判断自动添加负责人
$flowData['owner'] = $data['owner'] = $this->taskUser->where('owner', 1)->pluck('userid')->toArray();
if (in_array($newFlowItem->usertype, ["replace", "merge"])) {
// 流转模式、剔除模式
if ($this->parent_id === 0) {
$flowData['assist'] = $data['assist'] = $this->taskUser->where('owner', 0)->pluck('userid')->toArray();
$data['assist'] = array_merge($data['assist'], $data['owner']);
}
$data['owner'] = $newFlowItem->userids;
// 判断剔除模式:保留操作状态的人员
if ($newFlowItem->usertype == "merge") {
$data['owner'][] = User::userid();
}
} else {
// 添加模式
$data['owner'] = array_merge($data['owner'], $newFlowItem->userids);
}
$data['owner'] = array_values(array_unique($data['owner']));
if (isset($data['assist'])) {
$data['assist'] = array_values(array_unique(array_diff($data['assist'], $data['owner'])));
}
}
$this->flow_item_id = $newFlowItem->id;
$this->flow_item_name = $newFlowItem->status . "|" . $newFlowItem->name;
$this->addLog("修改{任务}状态", [
'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')) {
// 子任务:主任务已完成时无法修改
if ($mainTask?->complete_at) {
throw new ApiException('主任务已完成,无法修改子任务状态');
}
if (Base::isDate($data['complete_at'])) {
// 标记已完成
if ($this->complete_at) {
throw new ApiException('任务已完成');
}
$this->completeTask(Carbon::now());
} else {
// 标记未完成
if (!$this->complete_at) {
throw new ApiException('未完成任务');
}
$this->completeTask(null);
}
$updateMarking['is_update_project'] = true;
}
// 标题
if (Arr::exists($data, 'name') && $this->name != $data['name']) {
if (empty($data['name'])) {
@ -395,14 +622,19 @@ class ProjectTask extends AbstractModel
} elseif (mb_strlen($data['name']) > 255) {
throw new ApiException('任务描述最多只能设置255个字');
}
$this->addLog("修改{任务}标题:{$this->name} => {$data['name']}");
$this->addLog("修改{任务}标题", [
'change' => [$this->name, $data['name']]
]);
$this->name = $data['name'];
}
// 负责人
if (Arr::exists($data, 'owner')) {
$count = $this->taskUser->count();
$count = $this->taskUser->where('owner', 1)->count();
$array = [];
$owner = is_array($data['owner']) ? $data['owner'] : [$data['owner']];
if (count($owner) > 10) {
throw new ApiException('任务负责人最多不能超过10个');
}
foreach ($owner as $uid) {
if (intval($uid) == 0) continue;
if (!$this->project->useridInTheProject($uid)) continue;
@ -424,31 +656,90 @@ class ProjectTask extends AbstractModel
if ($count == 0 && count($array) == 1 && $array[0] == User::userid()) {
$this->addLog("认领{任务}");
} else {
$this->addLog("修改{任务}负责人" . implode(",", $array));
$this->addLog("修改{任务}负责人", ['userid' => $array]);
}
}
$rows = ProjectTaskUser::whereTaskId($this->id)->whereOwner(1)->whereNotIn('userid', $array)->get();
if ($rows->isNotEmpty()) {
$this->addLog("删除{任务}负责人" . $rows->implode('userid', ','));
$this->addLog("删除{任务}负责人", ['userid' => $rows->implode('userid', ',')]);
foreach ($rows as $row) {
$row->delete();
}
}
$updateMarking['is_update_project'] = true;
$this->syncDialogUser();
}
// 计划时间
// 计划时间(原则:子任务时间在主任务时间内)
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'];
list($start, $end) = is_string($times) ? explode(",", $times) : (is_array($times) ? $times : []);
if (Base::isDate($start) && Base::isDate($end)) {
if ($start != $end) {
$this->start_at = Carbon::parse($start);
$this->end_at = Carbon::parse($end);
if (Base::isDate($start) && Base::isDate($end) && $start != $end) {
$start_at = Carbon::parse($start);
$end_at = Carbon::parse($end);
if ($this->parent_id > 0) {
// 判断同步主任务时间(子任务时间 超出 主任务)
if ($mainTask) {
$isUp = false;
if ($start_at->lt(Carbon::parse($mainTask->start_at))) {
$mainTask->start_at = $start_at;
$isUp = true;
}
if ($end_at->gt(Carbon::parse($mainTask->end_at))) {
$mainTask->end_at = $end_at;
$isUp = true;
}
if ($isUp) {
$updateMarking['is_update_maintask'] = true;
$mainTask->addLog("同步修改{任务}时间");
$mainTask->save();
}
}
$this->addLog("修改{任务}时间");
}
$this->start_at = $start_at;
$this->end_at = $end_at;
} else {
if ($this->parent_id > 0) {
// 清空子任务时间(子任务时间等于主任务时间)
$this->start_at = $mainTask->start_at;
$this->end_at = $mainTask->end_at;
}
}
if ($this->parent_id == 0) {
// 判断同步子任务时间(主任务时间 不在 子任务时间 之外)
self::whereParentId($this->id)->chunk(100, function($list) use ($oldAt, &$updateMarking) {
/** @var self $subTask */
foreach ($list as $subTask) {
$start_at = Carbon::parse($subTask->start_at);
$end_at = Carbon::parse($subTask->end_at);
$isUp = false;
if (empty($subTask->start_at) || $start_at->eq($oldAt[0]) || $start_at->lt(Carbon::parse($this->start_at))) {
$subTask->start_at = $this->start_at;
$isUp = true;
}
if (empty($subTask->end_at) || $end_at->eq($oldAt[1]) || $end_at->gt(Carbon::parse($this->end_at))) {
$subTask->end_at = $this->end_at;
$isUp = true;
}
if ($subTask->start_at && Carbon::parse($subTask->start_at)->gt($subTask->end_at)) {
$subTask->start_at = $this->start_at;
$isUp = true;
}
if ($isUp) {
$updateMarking['is_update_subtask'] = true;
$subTask->addLog("同步修改{任务}时间");
$subTask->save();
}
}
});
}
$newStringAt = $this->start_at ? ($this->start_at->toDateTimeString() . '~' . $this->end_at->toDateTimeString()) : '';
$this->addLog("修改{任务}时间", [
'change' => [$oldStringAt, $newStringAt]
]);
}
// 以下紧顶级任务可修改
if ($this->parent_id === 0) {
@ -456,6 +747,9 @@ class ProjectTask extends AbstractModel
if (Arr::exists($data, 'assist')) {
$array = [];
$assist = is_array($data['assist']) ? $data['assist'] : [$data['assist']];
if (count($assist) > 10) {
throw new ApiException('任务协助人员最多不能超过10个');
}
foreach ($assist as $uid) {
if (intval($uid) == 0) continue;
if (!$this->project->useridInTheProject($uid)) continue;
@ -465,17 +759,17 @@ class ProjectTask extends AbstractModel
'userid' => $uid,
], [
'project_id' => $this->project_id,
'task_pid' => $this->parent_id ?: $this->id,
'task_pid' => $this->id,
'owner' => 0,
]);
$array[] = $uid;
}
if ($array) {
$this->addLog("修改{任务}协助人员" . implode(",", $array));
$this->addLog("修改{任务}协助人员", ['userid' => $array]);
}
$rows = ProjectTaskUser::whereTaskId($this->id)->whereOwner(0)->whereNotIn('userid', $array)->get();
if ($rows->isNotEmpty()) {
$this->addLog("删除{任务}协助人员" . $rows->implode('userid', ','));
$this->addLog("删除{任务}协助人员", ['userid' => $rows->implode('userid', ',')]);
foreach ($rows as $row) {
$row->delete();
}
@ -484,7 +778,9 @@ class ProjectTask extends AbstractModel
}
// 背景色
if (Arr::exists($data, 'color') && $this->color != $data['color']) {
$this->addLog("修改{任务}背景色:{$this->color} => {$data['color']}");
$this->addLog("修改{任务}背景色", [
'change' => [$this->color, $data['color']]
]);
$this->color = $data['color'];
}
// 列表
@ -494,7 +790,9 @@ class ProjectTask extends AbstractModel
if (empty($column)) {
throw new ApiException('请选择正确的列表');
}
$this->addLog("修改{任务}列表:{$oldName} => {$column->name}");
$this->addLog("修改{任务}列表", [
'change' => [$oldName, $column->name]
]);
$this->column_id = $column->id;
}
// 内容
@ -505,12 +803,13 @@ class ProjectTask extends AbstractModel
], [
'content' => $data['content'],
]);
$this->desc = Base::getHtml($data['content']);
$this->desc = Base::getHtml($data['content'], 100);
$this->addLog("修改{任务}详细描述");
$updateContent = true;
$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;
@ -524,7 +823,9 @@ class ProjectTask extends AbstractModel
$p = true;
}
if ($p) {
$this->addLog("修改{任务}优先级");
$this->addLog("修改{任务}优先级", [
'change' => [$oldPName, $this->p_name]
]);
}
}
$this->save();
@ -541,9 +842,7 @@ class ProjectTask extends AbstractModel
{
if ($this->parent_id > 0) {
$task = self::find($this->parent_id);
if ($task) {
$task->syncDialogUser();
}
$task?->syncDialogUser();
return;
}
if (empty($this->dialog_id)) {
@ -567,7 +866,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());
@ -603,6 +902,74 @@ 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
*/
public function isOwner()
{
if ($this->owner) {
return true;
}
if ($this->parent_id > 0) {
$mainTask = self::allData()->find($this->parent_id);
if ($mainTask->owner) {
return true;
}
}
return false;
}
/**
* 是否有负责人
* @return bool
*/
public function hasOwner()
{
if (!isset($this->appendattrs['has_owner'])) {
$this->appendattrs['has_owner'] = ProjectTaskUser::whereTaskId($this->id)->whereOwner(1)->exists();
}
return $this->appendattrs['has_owner'];
}
/**
* 标记已完成、未完成
* @param Carbon|null $complete_at 完成时间
@ -614,7 +981,7 @@ class ProjectTask extends AbstractModel
if ($complete_at === null) {
// 标记未完成
$this->complete_at = null;
$this->addLog("{任务}标记未完成" . $this->name);
$this->addLog("标记{任务}未完成");
} else {
// 标记已完成
if ($this->parent_id == 0) {
@ -622,8 +989,11 @@ class ProjectTask extends AbstractModel
throw new ApiException('子任务未完成');
}
}
if (!$this->hasOwner()) {
throw new ApiException('请先领取任务');
}
$this->complete_at = $complete_at;
$this->addLog("{任务}标记已完成:" . $this->name);
$this->addLog("标记{任务}已完成");
}
$this->save();
});
@ -635,26 +1005,49 @@ class ProjectTask extends AbstractModel
* @param Carbon|null $archived_at 归档时间
* @return bool
*/
public function archivedTask($archived_at)
public function archivedTask($archived_at, $isAuto = false)
{
AbstractModel::transaction(function () use ($archived_at) {
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) {
// 取消归档
$this->archived_at = null;
$this->archived_follow = 0;
$this->addLog("任务取消归档:" . $this->name);
$this->pushMsg('add', [
'new_column' => null,
'task' => ProjectTask::with(['taskUser', 'taskTag'])->find($this->id),
]);
} else {
// 归档任务
$this->archived_at = $archived_at;
$this->archived_userid = User::userid();
$this->archived_follow = 0;
$this->addLog("任务归档:" . $this->name);
$this->pushMsg('archived');
$this->addLog("任务取消归档");
} else {
// 归档任务
if ($isAuto === true) {
$logText = "自动任务归档";
$userid = 0;
} else {
$logText = "任务归档";
$userid = User::userid();
}
$this->archived_at = $archived_at;
$this->archived_userid = $userid;
$this->archived_follow = 0;
$this->addLog($logText, [], $userid);
}
$this->pushMsg('update', [
'id' => $this->id,
'archived_at' => $this->archived_at,
'archived_userid' => $this->archived_userid,
]);
self::whereParentId($this->id)->update([
'archived_at' => $this->archived_at,
'archived_userid' => $this->archived_userid,
'archived_follow' => $this->archived_follow,
]);
$this->save();
});
return true;
@ -670,12 +1063,11 @@ class ProjectTask extends AbstractModel
AbstractModel::transaction(function () {
if ($this->dialog_id) {
$dialog = WebSocketDialog::find($this->dialog_id);
if ($dialog) {
$dialog->deleteDialog();
}
$dialog?->deleteDialog();
}
self::whereParentId($this->id)->delete();
$this->addLog("删除{任务}");
$this->delete();
$this->addLog("删除{任务}" . $this->name);
});
if ($pushMsg) {
$this->pushMsg('delete');
@ -686,19 +1078,27 @@ class ProjectTask extends AbstractModel
/**
* 添加任务日志
* @param string $detail
* @param array $record
* @param int $userid
* @return ProjectLog
*/
public function addLog($detail, $userid = 0)
public function addLog($detail, $record = [], $userid = 0)
{
$detail = str_replace("{任务}", $this->parent_id > 0 ? "子任务" : "任务", $detail);
$log = ProjectLog::createInstance([
$detail = str_replace("{任务}", $this->parent_id ? "子任务" : "任务", $detail);
$array = [
'project_id' => $this->project_id,
'column_id' => $this->column_id,
'task_id' => $this->parent_id ?: $this->id,
'userid' => $userid ?: User::userid(),
'detail' => $detail,
]);
];
if ($this->parent_id) {
$record['subtitle'] = $this->name;
}
if ($record) {
$array['record'] = $record;
}
$log = ProjectLog::createInstance($array);
$log->save();
return $log;
}
@ -706,7 +1106,7 @@ class ProjectTask extends AbstractModel
/**
* 推送消息
* @param string $action
* @param array $data 发送内容,默认为[id, parent_id, project_id, column_id, dialog_id]
* @param array|self $data 发送内容,默认为[id, parent_id, project_id, column_id, dialog_id]
* @param array $userid 指定会员,默认为项目所有成员
*/
public function pushMsg($action, $data = null, $userid = null)
@ -722,13 +1122,28 @@ class ProjectTask extends AbstractModel
'column_id' => $this->column_id,
'dialog_id' => $this->dialog_id,
];
} elseif ($data instanceof self) {
$data = $data->toArray();
}
//
$array = [$userid, []];
if ($userid === null) {
$userid = $this->project->relationUserids();
$array[0] = $this->project->relationUserids();
} elseif (!is_array($userid)) {
$array[0] = [$userid];
}
//
if (isset($data['owner'])) {
$owners = ProjectTaskUser::whereTaskId($data['id'])->whereOwner(1)->pluck('userid')->toArray();
$array = [array_intersect($array[0], $owners), array_diff($array[0], $owners)];
}
foreach ($array as $index => $item) {
if ($index > 0) {
$data['owner'] = 0;
}
$params = [
'ignoreFd' => Request::header('fd'),
'userid' => $userid,
'userid' => array_values($item),
'msg' => [
'type' => 'projectTask',
'action' => $action,
@ -738,36 +1153,57 @@ class ProjectTask extends AbstractModel
$task = new PushTask($params, false);
Task::deliver($task);
}
}
/**
* 根据会员ID获取任务、项目信息会员有任务权限 会员存在项目内)
* 获取任务
* @param $task_id
* @return ProjectTask|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
*/
public static function oneTask($task_id)
{
return self::with(['taskUser', 'taskTag'])->allData()->where("project_tasks.id", intval($task_id))->first();
}
/**
* 获取任务(会员有任务权限 会员存在项目内)
* @param int $task_id
* @param bool $archived true:仅限未归档, false:仅限已归档, null:不限制
* @param int|bool $permission 0|false:不限制, 1|true:限制项目负责人、任务负责人、协助人员及任务创建者, 2:已有负责人才限制true (子任务时如果是主任务负责人也可以)
* @param array $with
* @param bool $ignoreArchived 排除已归档
* @param null $project
* @return self
*/
public static function userTask($task_id, $with = [], $ignoreArchived = true, &$project = null)
public static function userTask($task_id, $archived = true, $permission = 0, $with = [])
{
$task = self::with($with)->whereId(intval($task_id))->first();
$task = self::with($with)->allData()->where("project_tasks.id", intval($task_id))->first();
//
if (empty($task)) {
throw new ApiException('任务不存在', [ 'task_id' => $task_id ], -4002);
}
if ($ignoreArchived && $task->archived_at != null) {
throw new ApiException('任务已归档', [ 'task_id' => $task_id ], -4002);
if ($archived === true && $task->archived_at != null) {
throw new ApiException('任务已归档', [ 'task_id' => $task_id ]);
}
if ($archived === false && $task->archived_at == null) {
throw new ApiException('任务未归档', [ 'task_id' => $task_id ]);
}
//
try {
$project = Project::userProject($task->project_id, $ignoreArchived);
$project = Project::userProject($task->project_id);
} catch (Exception $e) {
if (ProjectTaskUser::whereUserid(User::userid())->whereTaskPid($task->id)->exists()) {
if ($task->owner === null) {
throw new ApiException($e->getMessage(), [ 'task_id' => $task_id ], -4002);
}
$project = Project::find($task->project_id);
if (empty($project)) {
throw new ApiException('项目不存在或已被删除', [ 'task_id' => $task_id ], -4002);
}
} else {
throw new ApiException($e->getMessage(), [ 'task_id' => $task_id ], -4002);
}
//
if ($permission === 2) {
$permission = $task->hasOwner() ? 1 : 0;
}
if (($permission === 1 || $permission === true) && !$project->owner && !$task->permission(3)) {
throw new ApiException('仅限项目负责人、任务负责人、协助人员或任务创建者操作');
}
//
return $task;

View File

@ -3,9 +3,8 @@
namespace App\Models;
/**
* Class ProjectTaskContent
* App\Models\ProjectTaskContent
*
* @package App\Models
* @property int $id
* @property int|null $project_id 项目ID
* @property int|null $task_id 任务ID

View File

@ -5,17 +5,16 @@ namespace App\Models;
use App\Module\Base;
/**
* Class ProjectTaskFile
* App\Models\ProjectTaskFile
*
* @package App\Models
* @property int $id
* @property int|null $project_id 项目ID
* @property int|null $task_id 任务ID
* @property string|null $name 文件名称
* @property int|null $size 文件大小(B)
* @property string|null $ext 文件格式
* @property string|null $path 文件地址
* @property string|null $thumb 缩略图
* @property string $path 文件地址
* @property string $thumb 缩略图
* @property int|null $userid 上传用户ID
* @property int|null $download 下载次数
* @property \Illuminate\Support\Carbon|null $created_at

View 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
{
}

View File

@ -3,9 +3,8 @@
namespace App\Models;
/**
* Class ProjectTaskTag
* App\Models\ProjectTaskTag
*
* @package App\Models
* @property int $id
* @property int|null $project_id 项目ID
* @property int|null $task_id 任务ID

View File

@ -3,9 +3,8 @@
namespace App\Models;
/**
* Class ProjectTaskUser
* App\Models\ProjectTaskUser
*
* @package App\Models
* @property int $id
* @property int|null $project_id 项目ID
* @property int|null $task_id 任务ID

View File

@ -5,13 +5,13 @@ namespace App\Models;
use App\Module\Base;
/**
* Class ProjectUser
* App\Models\ProjectUser
*
* @package App\Models
* @property int $id
* @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
@ -22,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
@ -42,12 +43,21 @@ class ProjectUser extends AbstractModel
*/
public function exitProject()
{
$tasks = ProjectTask::whereProjectId($this->project_id)->authData($this->userid)->get();
ProjectTaskUser::whereProjectId($this->project_id)
->whereUserid($this->userid)
->chunk(100, function ($list) {
$tastIds = [];
foreach ($list as $item) {
if (!in_array($item->task_pid, $tastIds)) {
$tastIds[] = $item->task_pid;
}
$item->delete();
}
$tasks = ProjectTask::whereIn('id', $tastIds)->get();
foreach ($tasks as $task) {
if (ProjectTaskUser::whereTaskId($task->id)->whereUserid($this->userid)->delete()) {
$task->syncDialogUser();
}
}
});
$this->delete();
}
}

158
app/Models/Report.php Normal file
View File

@ -0,0 +1,158 @@
<?php
namespace App\Models;
use App\Exceptions\ApiException;
use Carbon\Carbon;
use Carbon\Traits\Creator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use JetBrains\PhpStorm\Pure;
/**
* App\Models\Report
*
* @property int $id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property string $title 标题
* @property string $type 汇报类型
* @property int $userid
* @property string $content
* @property string $sign 汇报唯一标识
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ReportReceive[] $Receives
* @property-read int|null $receives_count
* @property-read mixed $receives
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\User[] $receivesUser
* @property-read int|null $receives_user_count
* @property-read \App\Models\User|null $sendUser
* @method static Builder|Report newModelQuery()
* @method static Builder|Report newQuery()
* @method static Builder|Report query()
* @method static Builder|Report whereContent($value)
* @method static Builder|Report whereCreatedAt($value)
* @method static Builder|Report whereId($value)
* @method static Builder|Report whereSign($value)
* @method static Builder|Report whereTitle($value)
* @method static Builder|Report whereType($value)
* @method static Builder|Report whereUpdatedAt($value)
* @method static Builder|Report whereUserid($value)
* @mixin \Eloquent
*/
class Report extends AbstractModel
{
use HasFactory;
const WEEKLY = "weekly";
const DAILY = "daily";
protected $fillable = [
"title",
"type",
"userid",
"content",
];
protected $appends = [
'receives',
];
public function Receives(): HasMany
{
return $this->hasMany(ReportReceive::class, "rid");
}
public function receivesUser(): BelongsToMany
{
return $this->belongsToMany(User::class, ReportReceive::class, "rid", "userid")
->withPivot("receive_time", "read");
}
public function sendUser()
{
return $this->hasOne(User::class, "userid", "userid");
}
public function getTypeAttribute($value): string
{
return match ($value) {
Report::WEEKLY => "周报",
Report::DAILY => "日报",
default => "",
};
}
public function getContentAttribute($value): string
{
return htmlspecialchars_decode($value);
}
public function getReceivesAttribute()
{
if (!isset($this->appendattrs['receives'])) {
$this->appendattrs['receives'] = empty( $this->receivesUser ) ? [] : array_column($this->receivesUser->toArray(), "userid");
}
return $this->appendattrs['receives'];
}
/**
* 获取单条记录
* @param $id
* @param User|null $user
* @return Report|Builder|Model|object|null
* @throw ApiException
*/
public static function getOne($id, User $user = null)
{
$user === null && $user = User::auth();
$one = self::whereUserid($user->userid)->whereId($id)->first();
if ( empty($one) )
throw new ApiException("记录不存在");
return $one;
}
/**
* 获取最后一条提交记录
* @param User|null $user
* @return Builder|Model|\Illuminate\Database\Query\Builder|object
*/
public static function getLastOne(User $user = null)
{
$user === null && $user = User::auth();
$one = self::whereUserid($user->userid)->orderByDesc("created_at")->first();
if ( empty($one) )
throw new ApiException("记录不存在");
return $one;
}
/**
* 生成唯一标识
* @param $type
* @param $offset
* @param Carbon|null $time
* @return string
*/
public static function generateSign($type, $offset, Carbon $time = null): string
{
$user = User::auth();
$now_dt = $time === null ? Carbon::now() : $time;
$time_s = match ($type) {
Report::WEEKLY => function() use ($now_dt, $offset) {
// 如果设置了周期偏移量
empty( $offset ) || $now_dt->subWeeks( abs( $offset ) );
$now_dt->startOfWeek(); // 设置为当周第一天
return $now_dt->year . $now_dt->weekOfYear;
},
Report::DAILY => function() use ($now_dt, $offset) {
// 如果设置了周期偏移量
empty( $offset ) || $now_dt->subDays( abs( $offset ) );
return $now_dt->format("Ymd");
},
default => "",
};
return $user->userid . ( is_callable($time_s) ? $time_s() : "" );
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* App\Models\ReportReceive
*
* @property int $id
* @property int $rid
* @property string|null $receive_time 接收时间
* @property int $userid 接收人
* @property int $read 是否已读
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive query()
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereRead($value)
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereReceiveTime($value)
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereRid($value)
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereUserid($value)
* @mixin \Eloquent
*/
class ReportReceive extends AbstractModel
{
use HasFactory;
// 关闭时间戳自动写入
public $timestamps = false;
protected $fillable = [
"rid",
"receive_time",
"userid",
"read",
];
}

View File

@ -3,9 +3,8 @@
namespace App\Models;
/**
* Class Setting
* App\Models\Setting
*
* @package App\Models
* @property int $id
* @property string|null $name
* @property string|null $desc 参数描述、备注

View File

@ -3,9 +3,8 @@
namespace App\Models;
/**
* Class Tmp
* App\Models\Tmp
*
* @package App\Models
* @property int $id
* @property string|null $name
* @property string|null $value

View File

@ -9,9 +9,8 @@ use Cache;
use Carbon\Carbon;
/**
* Class User
* App\Models\User
*
* @package App\Models
* @property int $userid
* @property array $identity 身份
* @property string|null $az A-Z
@ -29,8 +28,10 @@ use Carbon\Carbon;
* @property string|null $line_at 最后在线时间(接口)
* @property int|null $task_dialog_id 最后打开的任务会话ID
* @property string|null $created_ip 注册IP
* @property string|null $disable_at 禁用时间
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Database\Factories\UserFactory factory(...$parameters)
* @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|User newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|User query()
@ -38,6 +39,7 @@ use Carbon\Carbon;
* @method static \Illuminate\Database\Eloquent\Builder|User whereChangepass($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedAt($value)
* @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 whereEncrypt($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereIdentity($value)
@ -60,6 +62,7 @@ class User extends AbstractModel
protected $primaryKey = 'userid';
protected $hidden = [
'disable_at',
'updated_at',
];
@ -178,7 +181,7 @@ 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) {

View File

@ -4,14 +4,13 @@ namespace App\Models;
/**
* Class WebSocket
* App\Models\WebSocket
*
* @package App\Models
* @property int $id
* @property string $key
* @property string|null $fd
* @property int|null $userid
* @property string|null $path
* @property int|null $userid
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|WebSocket newModelQuery()

View File

@ -7,9 +7,8 @@ use Carbon\Carbon;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* Class WebSocketDialog
* App\Models\WebSocketDialog
*
* @package App\Models
* @property int $id
* @property string|null $type 对话类型
* @property string|null $group_type 聊天室类型
@ -121,10 +120,10 @@ class WebSocketDialog extends AbstractModel
break;
case "group":
if ($dialog->group_type === 'project') {
$dialog->group_info = Project::withTrashed()->select(['id', 'name'])->whereDialogId($dialog->id)->first();
$dialog->group_info = Project::withTrashed()->select(['id', 'name', 'archived_at', 'deleted_at'])->whereDialogId($dialog->id)->first()?->cancelAppend()->cancelHidden();
$dialog->name = $dialog->group_info ? $dialog->group_info->name : '';
} elseif ($dialog->group_type === 'task') {
$dialog->group_info = ProjectTask::withTrashed()->select(['id', 'name'])->whereDialogId($dialog->id)->first();
$dialog->group_info = ProjectTask::withTrashed()->select(['id', 'name', 'complete_at', 'archived_at', 'deleted_at'])->whereDialogId($dialog->id)->first()?->cancelAppend()->cancelHidden();
$dialog->name = $dialog->group_info ? $dialog->group_info->name : '';
}
break;

View File

@ -8,11 +8,11 @@ use App\Tasks\PushTask;
use App\Tasks\WebSocketDialogMsgTask;
use Carbon\Carbon;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* Class WebSocketDialogMsg
* App\Models\WebSocketDialogMsg
*
* @package App\Models
* @property int $id
* @property int|null $dialog_id 对话ID
* @property int|null $userid 发送会员ID
@ -22,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)
@ -35,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',
];
@ -47,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
@ -54,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'];
}
@ -82,6 +94,22 @@ class WebSocketDialogMsg extends AbstractModel
return $value;
}
/**
* 获取占比
* @param bool $increment 是否新增阅读数
* @return int
*/
public function generatePercentage($increment = false) {
if ($increment) {
$this->increment('read');
}
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
@ -111,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,
],
]
]);
}
@ -125,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

View File

@ -3,9 +3,8 @@
namespace App\Models;
/**
* Class WebSocketDialogMsgRead
* App\Models\WebSocketDialogMsgRead
*
* @package App\Models
* @property int $id
* @property int|null $dialog_id 对话ID
* @property int|null $msg_id 消息ID

View File

@ -3,12 +3,12 @@
namespace App\Models;
/**
* Class WebSocketDialogUser
* App\Models\WebSocketDialogUser
*
* @package App\Models
* @property int $id
* @property int|null $dialog_id 对话ID
* @property int|null $userid 会员ID
* @property string|null $top_at 置顶时间
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newModelQuery()
@ -17,6 +17,7 @@ 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 whereTopAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUserid($value)
* @mixin \Eloquent

View File

@ -3,9 +3,8 @@
namespace App\Models;
/**
* Class WebSocketTmpMsg
* App\Models\WebSocketTmpMsg
*
* @package App\Models
* @property int $id
* @property string|null $md5 MD5(会员ID-消息)
* @property string|null $msg 详细消息

View File

@ -2,6 +2,7 @@
namespace App\Module;
use App\Exceptions\ApiException;
use App\Models\Setting;
use App\Models\Tmp;
use Cache;
@ -74,6 +75,31 @@ class Base
});
}
/**
* 获取客户端版本号
* @return string
*/
public static function getClientVersion()
{
global $_A;
if (!isset($_A["__static_client_version"])) {
$_A["__static_client_version"] = Request::header('version') ?: '0.0.1';
}
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
@ -316,19 +342,15 @@ class Base
{
if (strtolower($charset) == 'utf-8') {
if (Base::getStrlen($string) <= $length) return $string;
$strcut = str_replace(array('&amp;', '&quot;', '&lt;', '&gt;'), array('&', '"', '<', '>'), $string);
$strcut = Base::utf8Substr($strcut, $length, $start);
$strcut = str_replace(array('&', '"', '<', '>'), array('&amp;', '&quot;', '&lt;', '&gt;'), $strcut);
$strcut = Base::utf8Substr($string, $length, $start);
return $strcut . $dot;
} else {
$length = $length * 2;
if (strlen($string) <= $length) return $string;
$string = str_replace(array('&amp;', '&quot;', '&lt;', '&gt;'), 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('&amp;', '&quot;', '&lt;', '&gt;'), $strcut);
}
return $strcut . $dot;
}
@ -725,6 +747,7 @@ class Base
*/
public static function fillUrl($str = '')
{
global $_A;
if (is_array($str)) {
foreach ($str as $key => $item) {
$str[$key] = Base::fillUrl($item);
@ -743,9 +766,12 @@ class Base
) {
return $str;
} else {
if ($_A['__fill_url_remote_url'] === true) {
return "{{RemoteURL}}" . $str;
}
try {
return url($str);
} catch (\Throwable $e) {
} catch (\Throwable) {
return self::getSchemeAndHost() . "/" . $str;
}
}
@ -766,7 +792,7 @@ class Base
}
try {
$find = url('');
} catch (\Throwable $e) {
} catch (\Throwable) {
$find = self::getSchemeAndHost();
}
return Base::leftDelete($str, $find . '/');
@ -782,6 +808,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
@ -830,13 +881,16 @@ class Base
/**
* 数组只保留数字的
* @param $array
* @param bool $int 是否格式化值
* @return array
*/
public static function arrayRetainInt($array)
public static function arrayRetainInt($array, $int = false)
{
foreach ($array as $k => $v) {
if (!is_numeric($v)) {
unset($array[$k]);
} elseif ($int === true) {
$array[$k] = intval($v);
}
}
return array_values($array);
@ -934,7 +988,7 @@ class Base
* @param string $str 需要检测的字符串
* @return int
*/
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);
@ -2216,7 +2270,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'];
@ -2226,6 +2280,9 @@ class Base
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',
@ -2234,10 +2291,17 @@ class Base
'ofd',
'pdf',
'txt',
'html', 'htm', 'asp', 'jsp', 'xml', 'json', 'properties', 'md', 'gitignore', 'log', 'java', 'py', 'c', 'cpp', 'sql', 'sh', 'bat', 'm', 'bas', 'prg', 'cmd',
'php', 'go', 'python', 'js', 'ftl', 'css', 'lua', 'rb', 'yaml', 'yml', 'h', 'cs', 'aspx',
'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', '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:
@ -2359,6 +2423,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 源图绝对完整地址{带文件名及后缀名}
@ -2868,4 +2963,19 @@ class Base
$matrix = array_unique($matrix, SORT_REGULAR);
return array_merge($matrix);
}
/**
* 去除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);
}
}

144
app/Module/BillExport.php Normal file
View 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
View 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;
}
}

View File

@ -23,6 +23,14 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot()
{
//
\Illuminate\Database\Query\Builder::macro('rawSql', function(){
return array_reduce($this->getBindings(), function($sql, $binding){
return preg_replace('/\?/', is_numeric($binding) ? $binding : "'".$binding."'" , $sql, 1);
}, $this->toSql());
});
\Illuminate\Database\Eloquent\Builder::macro('rawSql', function(){
return ($this->getQuery()->rawSql());
});
}
}

View File

@ -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) {
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);
}
}
/**

View File

@ -0,0 +1,45 @@
<?php
namespace App\Tasks;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
use App\Models\AbstractModel;
use App\Models\ProjectTask;
use App\Module\Base;
use Carbon\Carbon;
/**
* 完成的任务自动归档
* Class AutoArchivedTask
* @package App\Tasks
*/
class AutoArchivedTask extends AbstractTask
{
public function __construct()
{
//
}
public function start()
{
$setting = Base::setting('system');
if ($setting['auto_archived'] === 'open') {
$archivedDay = floatval($setting['archived_day']);
if ($archivedDay > 0) {
$archivedDay = min(100, $archivedDay);
$archivedTime = Carbon::now()->subDays($archivedDay);
//获取已完成未归档的任务
$taskLists = ProjectTask::whereNotNull('complete_at')
->where('complete_at', '<=', $archivedTime)
->where('archived_userid', 0)
->whereNull('archived_at')
->take(100)
->get();
foreach ($taskLists AS $task) {
$task->archivedTask(Carbon::now(), true);
}
}
}
}
}

View File

@ -33,6 +33,11 @@ class WebSocketDialogMsgTask extends AbstractTask
public function start()
{
global $_A;
$_A = [
'__fill_url_remote_url' => true,
];
//
$msg = WebSocketDialogMsg::find($this->id);
if (empty($msg)) {
return;

192
cmd
View File

@ -12,6 +12,8 @@ OK="${Green}[OK]${Font}"
Error="${Red}[错误]${Font}"
cur_path="$(pwd)"
cur_arg=$@
COMPOSE="docker-compose"
judge() {
if [[ 0 -eq $? ]]; then
@ -30,6 +32,15 @@ rand() {
echo $(($num%$max+$min))
}
rand_string() {
local lan=$1
if [[ `uname` == 'Linux' ]]; then
echo "$(date +%s%N | md5sum | cut -c 1-${lan})"
else
echo "$(docker run -it --rm alpine sh -c "date +%s%N | md5sum | cut -c 1-${lan}")"
fi
}
supervisorctl_restart() {
local RES=`run_exec php "supervisorctl update $1"`
if [ -z "$RES" ]; then
@ -45,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
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
@ -61,12 +81,11 @@ check_node() {
}
docker_name() {
echo `docker-compose ps | awk '{print $1}' | grep "\-$1\-"`
echo `$COMPOSE ps | awk '{print $1}' | grep "\-$1\-"`
}
run_compile() {
local type=$1
local npxcmd=""
check_node
if [ ! -d "./node_modules" ]; then
npm install
@ -74,34 +93,39 @@ run_compile() {
run_exec php "php bin/run --mode=$type"
supervisorctl_restart php
#
mix -V &> /dev/null
if [ $? -ne 0 ]; then
npxcmd="npx"
fi
if [ "$type" = "prod" ]; then
rm -rf "./public/js/build"
$npxcmd mix --production
npx mix --production
else
$npxcmd mix watch --hot
npx mix watch --hot
fi
}
run_electron() {
local type=$1
local argv=$@
check_node
if [ ! -d "./node_modules" ]; then
npm install
fi
if [ ! -d "./electron/node_modules" ]; then
pushd electron
npm install
popd
fi
#
if [ -d "./electron/dist" ]; then
rm -rf "./electron/dist"
fi
if [ "$type" = "prod" ]; then
node ./electron/build.js --build
else
node ./electron/build.js
if [ -d "./electron/public" ] && [ "$argv" != "--nobuild" ]; then
rm -rf "./electron/public"
fi
mkdir -p ./electron/public
cp ./electron/index.html ./electron/public/index.html
#
if [ "$argv" != "dev" ] && [ "$argv" != "--nobuild" ]; then
npx mix --production -- --env --electron
fi
node ./electron/build.js $argv
}
run_exec() {
@ -112,11 +136,7 @@ run_exec() {
echo -e "${Error} ${RedBG} 没有找到 $container 容器! ${Font}"
exit 1
fi
if [ "$container" = "mariadb" ] || [ "$container" = "nginx" ] || [ "$container" = "redis" ]; then
docker exec -it "$name" /bin/sh -c "$cmd"
else
docker exec -it "$name" /bin/bash -c "$cmd"
fi
}
run_mysql() {
@ -155,6 +175,7 @@ run_mysql() {
fi
docker cp $filename $container_name:/
run_exec mariadb "gunzip < /$inputname | mysql -u$username -p$password $database"
run_exec php "php artisan migrate"
judge "还原数据库"
fi
}
@ -172,8 +193,11 @@ env_set() {
if [ -z "$exist" ]; then
echo "$key=$val" >> $cur_path/.env
else
command="sed -i '/^$key=/c\\$key=$val' /www/.env"
docker run -it --rm -v ${cur_path}:/www alpine sh -c "$command"
if [[ `uname` == 'Linux' ]]; then
sed -i "/^${key}=/c\\${key}=${val}" ${cur_path}/.env
else
docker run -it --rm -v ${cur_path}:/www alpine sh -c "sed -i "/^${key}=/c\\${key}=${val}" /www/.env"
fi
if [ $? -ne 0 ]; then
echo -e "${Error} ${RedBG} 设置env参数失败${Font}"
exit 1
@ -186,51 +210,116 @@ env_init() {
cp .env.docker .env
fi
if [ -z "$(env_get DB_ROOT_PASSWORD)" ]; then
env_set DB_ROOT_PASSWORD "$(docker run -it --rm alpine sh -c "date +%s%N | md5sum | cut -c 1-16")"
env_set DB_ROOT_PASSWORD "$(rand_string 16)"
fi
if [ -z "$(env_get APP_ID)" ]; then
env_set APP_ID "$(docker run -it --rm alpine sh -c "date +%s%N | md5sum | cut -c 1-6")"
env_set APP_ID "$(rand_string 6)"
fi
if [ -z "$(env_get APP_IPPR)" ]; then
env_set APP_IPPR "10.$(rand 50 100).$(rand 100 200)"
fi
}
arg_get() {
local find="n"
local value=""
for var in $cur_arg; do
if [[ "$find" == "y" ]]; then
if [[ ! $var =~ "--" ]]; then
value=$var
fi
break
fi
if [[ "--$1" == "$var" ]] || [[ "-$1" == "$var" ]]; then
find="y"
value="yes"
fi
done
echo $value
}
is_arm() {
local get_arch=`arch`
if [[ $get_arch =~ "aarch" ]] || [[ $get_arch =~ "arm" ]]; then
echo "yes"
else
echo "no"
fi
}
####################################################################################
####################################################################################
####################################################################################
check_docker
env_init
if [[ "$1" != "electron" ]]; then
check_docker
env_init
fi
if [ $# -gt 0 ]; then
if [[ "$1" == "init" ]] || [[ "$1" == "install" ]]; then
shift 1
rm -rf composer.lock
# 判断架构
if [[ "$(is_arm)" == "yes" ]] && [[ -z "$(arg_get force)" ]]; then
echo -e "${Error} ${RedBG}暂不支持arm架构强制安装请使用./cmd install --force${Font}"
exit 1
fi
# 初始化文件
if [[ -n "$(arg_get relock)" ]]; then
rm -rf node_modules
rm -rf package-lock.json
mkdir -p ${cur_path}/docker/mysql/data
chmod -R 777 ${cur_path}/docker/mysql/data
docker-compose up -d
docker-compose restart php
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)"
$COMPOSE up php -d
# 安装composer依赖
run_exec php "composer install"
[ -z "$(env_get APP_KEY)" ] && run_exec php "php artisan key:generate"
run_exec php "php artisan migrate --seed"
if [ ! -f "${cur_path}/vendor/autoload.php" ]; then
run_exec php "composer config repo.packagist composer https://packagist.phpcomposer.com"
run_exec php "composer install"
run_exec php "composer config --unset repos.packagist"
fi
if [ ! -f "${cur_path}/vendor/autoload.php" ]; then
echo -e "${Error} ${RedBG}composer install 失败,请重试! ${Font}"
exit 1
fi
[[ -z "$(env_get APP_KEY)" ]] && run_exec php "php artisan key:generate"
run_exec php "php bin/run --mode=prod"
# 检查数据库
remaining=10
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}"
exit 1
fi
chmod -R 775 "${cur_path}/docker/mysql/data"
sleep 3
done
run_exec php "php artisan migrate --seed"
# 设置初始化密码
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"
elif [[ "$1" == "update" ]]; then
shift 1
run_mysql backup
git fetch --all
git reset --hard origin/$(git branch | sed -n -e 's/^\* \(.*\)/\1/p')
git pull
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
@ -244,11 +333,22 @@ 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
echo -e "${OK} ${GreenBG} 卸载完成 ${Font}"
elif [[ "$1" == "reinstall" ]]; then
shift 1
./cmd uninstall $@
sleep 3
./cmd install $@
elif [[ "$1" == "port" ]]; then
shift 1
env_set APP_PORT "$1"
$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
shift 1
run_exec mariadb "sh /etc/mysql/repassword.sh \"$@\""
@ -260,11 +360,7 @@ if [ $# -gt 0 ]; then
run_compile prod
elif [[ "$1" == "electron" ]]; then
shift 1
if [[ "$@" == "dev" ]]; then
run_electron dev
else
run_electron prod
fi
run_electron $@
elif [[ "$1" == "doc" ]]; then
shift 1
run_exec php "php app/Http/Controllers/Api/apidoc.php"
@ -309,10 +405,10 @@ if [ $# -gt 0 ]; then
elif [[ "$1" == "composer" ]]; then
shift 1
e="composer $@" && run_exec php "$e"
elif [[ "$1" == "super" ]]; then
elif [[ "$1" == "service" ]]; then
shift 1
supervisorctl_restart "$@"
elif [[ "$1" == "supervisorctl" ]]; then
e="service $@" && run_exec php "$e"
elif [[ "$1" == "super" ]] || [[ "$1" == "supervisorctl" ]]; then
shift 1
e="supervisorctl $@" && run_exec php "$e"
elif [[ "$1" == "models" ]]; then
@ -323,11 +419,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

9554
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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();

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProjectInvitesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('project_invites', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('project_id')->nullable()->default(0)->comment('项目ID');
$table->integer('num')->nullable()->default(0)->comment('累计邀请');
$table->string('code')->nullable()->default('')->comment('链接码');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('project_invites');
}
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateFileLinksTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('file_links', function (Blueprint $table) {
$table->bigIncrements('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();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('file_links');
}
}

View File

@ -0,0 +1,55 @@
<?php
use App\Models\File;
use App\Models\FileUser;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class FileUsersAddPermission extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('file_users', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('file_users', 'permission')) {
$isAdd = true;
$table->tinyInteger('permission')->nullable()->default(0)->after('userid')->comment('权限0只读1读写');
}
});
if ($isAdd) {
// 更新数据
File::whereShare(1)->chunkById(100, function ($lists) {
foreach ($lists as $file) {
FileUser::updateInsert([
'file_id' => $file->id,
'userid' => 0,
]);
}
});
File::whereShare(2)->update([
'share' => 1,
]);
FileUser::wherePermission(0)->update([
'permission' => 1,
]);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('file_users', function (Blueprint $table) {
$table->dropColumn("permission");
});
}
}

View File

@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateReportsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if ( Schema::hasTable('reports') )
return;
Schema::create('reports', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->string("title")->default("")->comment("标题");
$table->enum("type", ["weekly", "daily"])->default("daily")->comment("汇报类型");
$table->unsignedBigInteger("userid")->default(0);
$table->longText("content")->nullable();
$table->index(["userid", "created_at"], "default");
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('reports');
}
}

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateReportReceivesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if ( Schema::hasTable('report_receives') )
return;
Schema::create('report_receives', function (Blueprint $table) {
$table->bigIncrements("id");
$table->unsignedInteger("rid")->default(0);
$table->timestamp("receive_time")->nullable()->comment("接收时间");
$table->unsignedBigInteger("userid")->default(0)->comment("接收人");
$table->unsignedTinyInteger("read")->default(0)->comment("是否已读");
$table->index(["userid", "receive_time"], "default");
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('report_receives');
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddReportSign extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('reports', function (Blueprint $table) {
$table->string("sign")->default("")->comment("汇报唯一标识");
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('reports', function (Blueprint $table) {
$table->dropColumn("sign");
});
}
}

View File

@ -0,0 +1,43 @@
<?php
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class UsersAddDisableAt extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('users', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('users', 'disable_at')) {
$isAdd = true;
$table->timestamp('disable_at')->nullable()->after('created_ip')->comment('禁用时间');
}
});
if ($isAdd) {
User::where("identity", "like", "%,disable,%")->update([
'disable_at' => Carbon::now(),
]);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn("disable_at");
});
}
}

View File

@ -0,0 +1,39 @@
<?php
use App\Models\ProjectTask;
use Illuminate\Database\Migrations\Migration;
class ProjectTasksUpdateSubtaskTime extends Migration
{
/**
* 子任务同步主任务(任务时间)
*
* @return void
*/
public function up()
{
ProjectTask::where('parent_id', '>', 0)
->whereNull('end_at')
->chunkById(100, function ($lists) {
/** @var ProjectTask $task */
foreach ($lists as $task) {
$parent = ProjectTask::whereNotNull('end_at')->find($task->parent_id);
if ($parent) {
$task->start_at = $parent->start_at;
$task->end_at = $parent->end_at;
$task->save();
}
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,51 @@
<?php
use App\Models\ProjectTask;
use Illuminate\Database\Migrations\Migration;
class ProjectTasksUpdateSubtaskArchivedDelete extends Migration
{
/**
* 子任务同步主任务(归档、删除)
*
* @return void
*/
public function up()
{
// 归档
ProjectTask::whereParentId(0)
->whereNotNull('archived_at')
->chunkById(100, function ($lists) {
/** @var ProjectTask $task */
foreach ($lists as $task) {
ProjectTask::whereParentId($task->id)->update([
'archived_at' => $task->archived_at,
'archived_userid' => $task->archived_userid,
'archived_follow' => $task->archived_follow,
]);
}
});
// 删除
ProjectTask::onlyTrashed()
->whereParentId(0)
->chunkById(100, function ($lists) {
/** @var ProjectTask $task */
foreach ($lists as $task) {
ProjectTask::whereParentId($task->id)->update([
'deleted_at' => $task->deleted_at,
]);
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProjectFlowItemsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('project_flow_items', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('project_id')->nullable()->default(0)->comment('项目ID');
$table->bigInteger('flow_id')->nullable()->default(0)->comment('流程ID');
$table->string('name', 50)->nullable()->default('')->comment('名称');
$table->string('status', 20)->nullable()->default('')->comment('状态');
$table->string('turns')->nullable()->default('')->comment('可流转');
$table->string('userids')->nullable()->default('')->comment('状态负责人ID');
$table->integer('sort')->nullable()->comment('排序');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('project_flow_items');
}
}

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProjectFlowsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('project_flows', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('project_id')->nullable()->default(0)->comment('项目ID');
$table->string('name', 50)->nullable()->default('')->comment('流程名称');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('project_flows');
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ProjectTasksAddFlowItemIdFlowItemName extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('project_tasks', function (Blueprint $table) {
if (!Schema::hasColumn('project_tasks', 'flow_item_id')) {
$table->bigInteger('flow_item_id')->nullable()->default(0)->after('dialog_id')->comment('工作流状态ID');
$table->string('flow_item_name', 50)->nullable()->default('')->after('flow_item_id')->comment('工作流状态名称');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('project_tasks', function (Blueprint $table) {
$table->dropColumn("flow_item_id");
$table->dropColumn("flow_item_name");
});
}
}

View File

@ -0,0 +1,42 @@
<?php
use App\Models\ProjectFlowItem;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ProjectFlowItemsAddUsertype extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('project_flow_items', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('project_flow_items', 'usertype')) {
$isAdd = true;
$table->string('usertype', 10)->nullable()->default('')->after('userids')->comment('流转模式');
}
});
if ($isAdd) {
ProjectFlowItem::where("usertype", "")->update([
'usertype' => 'add',
]);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('project_flow_items', function (Blueprint $table) {
$table->dropColumn("usertype");
});
}
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ProjectLogsAddRecord extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('project_logs', function (Blueprint $table) {
if (!Schema::hasColumn('project_logs', 'record')) {
$table->text('record')->nullable()->after('detail')->comment('记录数据');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('project_logs', function (Blueprint $table) {
$table->dropColumn("record");
});
}
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ProjectFlowItemsAddUserlimit extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('project_flow_items', function (Blueprint $table) {
if (!Schema::hasColumn('project_flow_items', 'userlimit')) {
$table->tinyInteger('userlimit')->nullable()->default(0)->after('usertype')->comment('限制负责人');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('project_flow_items', function (Blueprint $table) {
$table->dropColumn("userlimit");
});
}
}

View File

@ -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();
});
}
}

View File

@ -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()
{
}
}

View File

@ -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");
});
}
}

View 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'
]);
}
}

View 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' => ''
]);
// ... 退回去意义不大,文件内容不做回滚操作
}
}

View File

@ -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");
});
}
}

View File

@ -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');
}
}

View File

@ -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) {
//
});
}
}

View File

@ -16,7 +16,10 @@ class DatabaseSeeder extends Seeder
$this->call(FileContentsTableSeeder::class);
$this->call(FilesTableSeeder::class);
$this->call(FileUsersTableSeeder::class);
$this->call(ProjectColumnsTableSeeder::class);
$this->call(ProjectFlowItemsTableSeeder::class);
$this->call(ProjectFlowsTableSeeder::class);
$this->call(ProjectLogsTableSeeder::class);
$this->call(ProjectTaskContentsTableSeeder::class);
$this->call(ProjectTaskUsersTableSeeder::class);

View File

@ -599,7 +599,5 @@ curl -O https://task.hitosea.com/uploads/files/3/202105/ba786dfc2f4c2fe916880474
'deleted_at' => NULL,
),
));
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class FileUsersTableSeeder extends Seeder
{
/**
* Auto generated seed file
*
* @return void
*/
public function run()
{
if (\DB::table('file_users')->count() > 0) {
return;
}
\DB::table('file_users')->insert(array (
0 =>
array (
'id' => 1,
'file_id' => 1,
'userid' => 0,
'permission' => 1,
'created_at' => seeders_at('2021-07-01 14:03:29'),
'updated_at' => seeders_at('2021-07-01 14:03:29'),
),
1 =>
array (
'id' => 2,
'file_id' => 11,
'userid' => 0,
'permission' => 1,
'created_at' => seeders_at('2021-07-01 14:03:29'),
'updated_at' => seeders_at('2021-07-01 14:03:29'),
),
2 =>
array (
'id' => 3,
'file_id' => 12,
'userid' => 0,
'permission' => 1,
'created_at' => seeders_at('2021-07-01 14:03:29'),
'updated_at' => seeders_at('2021-07-01 14:03:29'),
),
3 =>
array (
'id' => 4,
'file_id' => 13,
'userid' => 0,
'permission' => 1,
'created_at' => seeders_at('2021-07-01 14:03:29'),
'updated_at' => seeders_at('2021-07-01 14:03:29'),
),
4 =>
array (
'id' => 5,
'file_id' => 15,
'userid' => 0,
'permission' => 1,
'created_at' => seeders_at('2021-07-01 14:03:29'),
'updated_at' => seeders_at('2021-07-01 14:03:29'),
),
5 =>
array (
'id' => 6,
'file_id' => 16,
'userid' => 0,
'permission' => 1,
'created_at' => seeders_at('2021-07-01 14:03:29'),
'updated_at' => seeders_at('2021-07-01 14:03:29'),
),
));
}
}

View File

@ -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
@ -28,6 +32,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '设计知识',
'type' => 'folder',
'ext' => '',
'size' => 0,
'userid' => 1,
'share' => 1,
@ -43,6 +48,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '如何搭建B端设计规范',
'type' => 'document',
'ext' => '',
'size' => 16976,
'userid' => 1,
'share' => 0,
@ -58,6 +64,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '页面设计中的信息组织策略探索-言之有序',
'type' => 'document',
'ext' => '',
'size' => 11971,
'userid' => 1,
'share' => 0,
@ -73,6 +80,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '素材整理',
'type' => 'folder',
'ext' => '',
'size' => 0,
'userid' => 1,
'share' => 0,
@ -88,6 +96,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '配置静态IP地址',
'type' => 'document',
'ext' => '',
'size' => 285,
'userid' => 1,
'share' => 0,
@ -103,6 +112,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '脑图',
'type' => 'mind',
'ext' => '',
'size' => 1947,
'userid' => 1,
'share' => 0,
@ -118,6 +128,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '数据统计',
'type' => 'excel',
'ext' => 'xlsx',
'size' => 8128,
'userid' => 1,
'share' => 0,
@ -133,6 +144,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '会议纪要',
'type' => 'document',
'ext' => '',
'size' => 8088,
'userid' => 1,
'share' => 0,
@ -148,6 +160,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '部门周报',
'type' => 'document',
'ext' => '',
'size' => 23266,
'userid' => 1,
'share' => 0,
@ -163,6 +176,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '项目管理',
'type' => 'excel',
'ext' => 'xlsx',
'size' => 8128,
'userid' => 1,
'share' => 0,
@ -178,6 +192,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '工作计划',
'type' => 'excel',
'ext' => 'xlsx',
'size' => 8128,
'userid' => 1,
'share' => 1,
@ -192,7 +207,8 @@ class FilesTableSeeder extends Seeder
'pid' => 0,
'cid' => 0,
'name' => '流程图',
'type' => 'flow',
'type' => 'drawio',
'ext' => '',
'size' => 5418,
'userid' => 1,
'share' => 1,
@ -208,6 +224,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '项目管理',
'type' => 'folder',
'ext' => '',
'size' => 0,
'userid' => 1,
'share' => 1,
@ -223,6 +240,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '会议纪要',
'type' => 'folder',
'ext' => '',
'size' => 0,
'userid' => 1,
'share' => 0,
@ -238,6 +256,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '会议发言',
'type' => 'word',
'ext' => 'docx',
'size' => 10994,
'userid' => 1,
'share' => 1,
@ -253,6 +272,7 @@ class FilesTableSeeder extends Seeder
'cid' => 0,
'name' => '产品介绍',
'type' => 'ppt',
'ext' => 'pptx',
'size' => 26882,
'userid' => 1,
'share' => 1,
@ -264,5 +284,47 @@ 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();
}
});
}
}

View File

@ -0,0 +1,388 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class ProjectFlowItemsTableSeeder extends Seeder
{
/**
* Auto generated seed file
*
* @return void
*/
public function run()
{
if (\DB::table('project_flow_items')->count() > 0) {
return;
}
\DB::table('project_flow_items')->insert(array (
0 =>
array (
'id' => 1,
'project_id' => 2,
'flow_id' => 1,
'name' => '待处理',
'status' => 'start',
'turns' => '[1,2,3,4]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 0,
'created_at' => '2022-01-15 23:43:15',
'updated_at' => '2022-01-15 23:43:15',
),
1 =>
array (
'id' => 2,
'project_id' => 2,
'flow_id' => 1,
'name' => '进行中',
'status' => 'progress',
'turns' => '[1,2,3,4]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 1,
'created_at' => '2022-01-15 23:43:15',
'updated_at' => '2022-01-15 23:43:15',
),
2 =>
array (
'id' => 3,
'project_id' => 2,
'flow_id' => 1,
'name' => '已完成',
'status' => 'end',
'turns' => '[1,2,3,4]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => '2022-01-15 23:43:15',
'updated_at' => '2022-01-15 23:43:15',
),
3 =>
array (
'id' => 4,
'project_id' => 2,
'flow_id' => 1,
'name' => '已取消',
'status' => 'end',
'turns' => '[1,2,3,4]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 3,
'created_at' => '2022-01-15 23:43:15',
'updated_at' => '2022-01-15 23:43:15',
),
4 =>
array (
'id' => 5,
'project_id' => 3,
'flow_id' => 2,
'name' => '待处理',
'status' => 'start',
'turns' => '[5,6,7,8]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 0,
'created_at' => '2022-01-15 23:43:23',
'updated_at' => '2022-01-15 23:43:23',
),
5 =>
array (
'id' => 6,
'project_id' => 3,
'flow_id' => 2,
'name' => '进行中',
'status' => 'progress',
'turns' => '[5,6,7,8]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 1,
'created_at' => '2022-01-15 23:43:23',
'updated_at' => '2022-01-15 23:43:23',
),
6 =>
array (
'id' => 7,
'project_id' => 3,
'flow_id' => 2,
'name' => '已完成',
'status' => 'end',
'turns' => '[5,6,7,8]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => '2022-01-15 23:43:23',
'updated_at' => '2022-01-15 23:43:23',
),
7 =>
array (
'id' => 8,
'project_id' => 3,
'flow_id' => 2,
'name' => '已取消',
'status' => 'end',
'turns' => '[5,6,7,8]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 3,
'created_at' => '2022-01-15 23:43:23',
'updated_at' => '2022-01-15 23:43:23',
),
8 =>
array (
'id' => 9,
'project_id' => 4,
'flow_id' => 3,
'name' => '待处理',
'status' => 'start',
'turns' => '[9,10,11,12]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 0,
'created_at' => '2022-01-15 23:43:28',
'updated_at' => '2022-01-15 23:43:28',
),
9 =>
array (
'id' => 10,
'project_id' => 4,
'flow_id' => 3,
'name' => '进行中',
'status' => 'progress',
'turns' => '[9,10,11,12]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 1,
'created_at' => '2022-01-15 23:43:28',
'updated_at' => '2022-01-15 23:43:28',
),
10 =>
array (
'id' => 11,
'project_id' => 4,
'flow_id' => 3,
'name' => '已完成',
'status' => 'end',
'turns' => '[9,10,11,12]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => '2022-01-15 23:43:28',
'updated_at' => '2022-01-15 23:43:28',
),
11 =>
array (
'id' => 12,
'project_id' => 4,
'flow_id' => 3,
'name' => '已取消',
'status' => 'end',
'turns' => '[9,10,11,12]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 3,
'created_at' => '2022-01-15 23:43:28',
'updated_at' => '2022-01-15 23:43:28',
),
12 =>
array (
'id' => 13,
'project_id' => 5,
'flow_id' => 4,
'name' => '待处理',
'status' => 'start',
'turns' => '[13,14,15,16]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 0,
'created_at' => '2022-01-15 23:43:34',
'updated_at' => '2022-01-15 23:43:34',
),
13 =>
array (
'id' => 14,
'project_id' => 5,
'flow_id' => 4,
'name' => '进行中',
'status' => 'progress',
'turns' => '[13,14,15,16]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 1,
'created_at' => '2022-01-15 23:43:34',
'updated_at' => '2022-01-15 23:43:34',
),
14 =>
array (
'id' => 15,
'project_id' => 5,
'flow_id' => 4,
'name' => '已完成',
'status' => 'end',
'turns' => '[13,14,15,16]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => '2022-01-15 23:43:34',
'updated_at' => '2022-01-15 23:43:34',
),
15 =>
array (
'id' => 16,
'project_id' => 5,
'flow_id' => 4,
'name' => '已取消',
'status' => 'end',
'turns' => '[13,14,15,16]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 3,
'created_at' => '2022-01-15 23:43:34',
'updated_at' => '2022-01-15 23:43:34',
),
16 =>
array (
'id' => 17,
'project_id' => 6,
'flow_id' => 5,
'name' => '待处理',
'status' => 'start',
'turns' => '[17,18,19,20]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 0,
'created_at' => '2022-01-15 23:43:40',
'updated_at' => '2022-01-15 23:43:40',
),
17 =>
array (
'id' => 18,
'project_id' => 6,
'flow_id' => 5,
'name' => '进行中',
'status' => 'progress',
'turns' => '[17,18,19,20]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 1,
'created_at' => '2022-01-15 23:43:40',
'updated_at' => '2022-01-15 23:43:40',
),
18 =>
array (
'id' => 19,
'project_id' => 6,
'flow_id' => 5,
'name' => '已完成',
'status' => 'end',
'turns' => '[17,18,19,20]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => '2022-01-15 23:43:40',
'updated_at' => '2022-01-15 23:43:40',
),
19 =>
array (
'id' => 20,
'project_id' => 6,
'flow_id' => 5,
'name' => '已取消',
'status' => 'end',
'turns' => '[17,18,19,20]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 3,
'created_at' => '2022-01-15 23:43:40',
'updated_at' => '2022-01-15 23:43:40',
),
20 =>
array (
'id' => 21,
'project_id' => 7,
'flow_id' => 6,
'name' => '待处理',
'status' => 'start',
'turns' => '[21,22,23,24]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 0,
'created_at' => '2022-01-15 23:43:45',
'updated_at' => '2022-01-15 23:43:45',
),
21 =>
array (
'id' => 22,
'project_id' => 7,
'flow_id' => 6,
'name' => '进行中',
'status' => 'progress',
'turns' => '[21,22,23,24]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 1,
'created_at' => '2022-01-15 23:43:45',
'updated_at' => '2022-01-15 23:43:45',
),
22 =>
array (
'id' => 23,
'project_id' => 7,
'flow_id' => 6,
'name' => '已完成',
'status' => 'end',
'turns' => '[21,22,23,24]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => '2022-01-15 23:43:45',
'updated_at' => '2022-01-15 23:43:45',
),
23 =>
array (
'id' => 24,
'project_id' => 7,
'flow_id' => 6,
'name' => '已取消',
'status' => 'end',
'turns' => '[21,22,23,24]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 3,
'created_at' => '2022-01-15 23:43:45',
'updated_at' => '2022-01-15 23:43:45',
),
));
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class ProjectFlowsTableSeeder extends Seeder
{
/**
* Auto generated seed file
*
* @return void
*/
public function run()
{
if (\DB::table('project_flows')->count() > 0) {
return;
}
\DB::table('project_flows')->insert(array (
0 =>
array (
'id' => 1,
'project_id' => 2,
'name' => 'Default',
'created_at' => '2022-01-15 23:43:15',
'updated_at' => '2022-01-15 23:43:15',
),
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',
),
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',
),
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',
),
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',
),
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',
),
));
}
}

View File

@ -296,7 +296,7 @@ class ProjectLogsTableSeeder extends Seeder
'column_id' => 6,
'task_id' => 8,
'userid' => 1,
'detail' => '任务标记已完成:官网项目一期',
'detail' => '标记任务已完成:官网项目一期',
'created_at' => seeders_at('2021-07-01 10:53:47'),
'updated_at' => seeders_at('2021-07-01 10:53:47'),
),
@ -2998,7 +2998,7 @@ class ProjectLogsTableSeeder extends Seeder
'column_id' => 27,
'task_id' => 77,
'userid' => 1,
'detail' => '任务标记已完成:🚴 里程碑 1 需求评审完成,资源准备到位',
'detail' => '标记任务已完成:🚴 里程碑 1 需求评审完成,资源准备到位',
'created_at' => seeders_at('2021-07-01 15:39:46'),
'updated_at' => seeders_at('2021-07-01 15:39:46'),
),
@ -3229,7 +3229,7 @@ class ProjectLogsTableSeeder extends Seeder
'column_id' => 32,
'task_id' => 85,
'userid' => 1,
'detail' => '子任务标记已完成:首页',
'detail' => '子标记任务已完成:首页',
'created_at' => seeders_at('2021-07-01 16:17:30'),
'updated_at' => seeders_at('2021-07-01 16:17:30'),
),
@ -3240,7 +3240,7 @@ class ProjectLogsTableSeeder extends Seeder
'column_id' => 32,
'task_id' => 85,
'userid' => 1,
'detail' => '子任务标记已完成:公司介绍',
'detail' => '子标记任务已完成:公司介绍',
'created_at' => seeders_at('2021-07-01 16:17:31'),
'updated_at' => seeders_at('2021-07-01 16:17:31'),
),
@ -3251,7 +3251,7 @@ class ProjectLogsTableSeeder extends Seeder
'column_id' => 32,
'task_id' => 85,
'userid' => 1,
'detail' => '子任务标记已完成:新闻动态',
'detail' => '子标记任务已完成:新闻动态',
'created_at' => seeders_at('2021-07-01 16:17:32'),
'updated_at' => seeders_at('2021-07-01 16:17:32'),
),
@ -3262,7 +3262,7 @@ class ProjectLogsTableSeeder extends Seeder
'column_id' => 32,
'task_id' => 85,
'userid' => 1,
'detail' => '子任务标记已完成:产品介绍',
'detail' => '子标记任务已完成:产品介绍',
'created_at' => seeders_at('2021-07-01 16:17:34'),
'updated_at' => seeders_at('2021-07-01 16:17:34'),
),
@ -3273,7 +3273,7 @@ class ProjectLogsTableSeeder extends Seeder
'column_id' => 32,
'task_id' => 85,
'userid' => 1,
'detail' => '子任务标记已完成:案例展示',
'detail' => '子标记任务已完成:案例展示',
'created_at' => seeders_at('2021-07-01 16:17:35'),
'updated_at' => seeders_at('2021-07-01 16:17:35'),
),
@ -3284,7 +3284,7 @@ class ProjectLogsTableSeeder extends Seeder
'column_id' => 32,
'task_id' => 85,
'userid' => 1,
'detail' => '子任务标记已完成:联系我们',
'detail' => '子标记任务已完成:联系我们',
'created_at' => seeders_at('2021-07-01 16:17:36'),
'updated_at' => seeders_at('2021-07-01 16:17:36'),
),
@ -3295,7 +3295,7 @@ class ProjectLogsTableSeeder extends Seeder
'column_id' => 32,
'task_id' => 85,
'userid' => 1,
'detail' => '任务标记已完成:产品官网设计',
'detail' => '标记任务已完成:产品官网设计',
'created_at' => seeders_at('2021-07-01 16:17:37'),
'updated_at' => seeders_at('2021-07-01 16:17:37'),
),
@ -3306,7 +3306,7 @@ class ProjectLogsTableSeeder extends Seeder
'column_id' => 32,
'task_id' => 85,
'userid' => 1,
'detail' => '子任务标记未完成:联系我们',
'detail' => '标记子任务未完成:联系我们',
'created_at' => seeders_at('2021-07-01 16:17:41'),
'updated_at' => seeders_at('2021-07-01 16:17:41'),
),
@ -3317,7 +3317,7 @@ class ProjectLogsTableSeeder extends Seeder
'column_id' => 32,
'task_id' => 85,
'userid' => 1,
'detail' => '任务标记未完成:产品官网设计',
'detail' => '标记任务未完成:产品官网设计',
'created_at' => seeders_at('2021-07-01 16:17:44'),
'updated_at' => seeders_at('2021-07-01 16:17:44'),
),

View File

@ -28,6 +28,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 2,
'column_id' => 2,
'dialog_id' => 0,
'flow_item_id' => 1,
'flow_item_name' => 'start|待处理',
'name' => '说明:将进度分成多级
每张卡片为一个项目任务,标签表示任务状况
通过将卡片拖至不同的进度列表下,来表示各项目进度',
@ -54,6 +56,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 2,
'column_id' => 2,
'dialog_id' => 0,
'flow_item_id' => 1,
'flow_item_name' => 'start|待处理',
'name' => '官网项目',
'color' => '',
'desc' => '',
@ -78,7 +82,9 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 2,
'column_id' => 2,
'dialog_id' => 0,
'name' => '1',
'flow_item_id' => 1,
'flow_item_name' => 'start|待处理',
'name' => '新增职位需求',
'color' => '',
'desc' => '',
'start_at' => NULL,
@ -102,7 +108,9 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 2,
'column_id' => 2,
'dialog_id' => 0,
'name' => '11',
'flow_item_id' => 1,
'flow_item_name' => 'start|待处理',
'name' => '更新公司简介',
'color' => '',
'desc' => '',
'start_at' => NULL,
@ -126,6 +134,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 2,
'column_id' => 3,
'dialog_id' => 0,
'flow_item_id' => 1,
'flow_item_name' => 'start|待处理',
'name' => '官网项目四期',
'color' => '',
'desc' => '',
@ -150,6 +160,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 2,
'column_id' => 4,
'dialog_id' => 16,
'flow_item_id' => 1,
'flow_item_name' => 'start|待处理',
'name' => '官网项目三期',
'color' => '',
'desc' => '',
@ -174,6 +186,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 2,
'column_id' => 5,
'dialog_id' => 0,
'flow_item_id' => 1,
'flow_item_name' => 'start|待处理',
'name' => '官网项目二期',
'color' => '',
'desc' => '',
@ -198,6 +212,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 2,
'column_id' => 6,
'dialog_id' => 0,
'flow_item_id' => 3,
'flow_item_name' => 'end|已完成',
'name' => '官网项目一期',
'color' => '',
'desc' => '',
@ -222,6 +238,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 7,
'dialog_id' => 18,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => '免费高品质的Sketch资源',
'color' => '',
'desc' => 'https://sketchrepo.com/',
@ -246,6 +264,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 7,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => '免费高品质的PSD资源',
'color' => '',
'desc' => 'https://psdrepo.com/',
@ -270,6 +290,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 7,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => '免费高清灵感图片网(偏文艺)',
'color' => '',
'desc' => 'https://magdeleine.co/',
@ -294,6 +316,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 7,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => '每天发现充满灵感且美丽的图片',
'color' => '',
'desc' => 'https://weheartit.com/',
@ -318,6 +342,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 8,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => '花瓣网:设计师寻找灵感的天堂',
'color' => '',
'desc' => 'https://huaban.com/',
@ -342,6 +368,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 8,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'WallHaven高清壁纸图片搜索引擎',
'color' => '',
'desc' => 'https://wallhaven.typepad.com/',
@ -366,6 +394,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 8,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'Pexels免费高品质图片 可商用',
'color' => '',
'desc' => 'https://www.pexels.com/',
@ -390,6 +420,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 9,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'RetinizeiOS切图神器',
'color' => '',
'desc' => 'http://retinize.it/',
@ -414,6 +446,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 9,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'GuideGuide一款PS参考线插件',
'color' => '',
'desc' => 'https://guideguide.me/photoshop/',
@ -438,6 +472,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 9,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'Assistor PS一个功能强大的PS辅助工具',
'color' => '',
'desc' => 'http://wit-web.azurewebsites.net/assistor/download',
@ -462,6 +498,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 10,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'Fontello图标字体生成器',
'color' => '',
'desc' => 'http://fontello.com/',
@ -486,6 +524,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 10,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'inconfont: 免费提供高度可辨识符号图标',
'color' => '',
'desc' => 'https://www.iconfont.cn/',
@ -510,6 +550,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 10,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'The Noun Project免费提供高度可辨识符号图标',
'color' => '',
'desc' => 'https://thenounproject.com/',
@ -534,6 +576,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 10,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'EasyIcon免费图标搜索和下载平台',
'color' => '',
'desc' => 'https://www.easyicon.net/',
@ -558,6 +602,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 10,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'Icon Deposit一个奇妙的图标下载站',
'color' => '',
'desc' => 'https://www.icondeposit.com/',
@ -582,6 +628,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 10,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'iConifyMac平台的苹果应用图标自动化生成工具',
'color' => '',
'desc' => 'https://iconify.net/',
@ -606,6 +654,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 11,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'Logaster教你在线几分钟内搞定专业的LOGO',
'color' => '',
'desc' => '设计https://www.logaster.cn/',
@ -630,6 +680,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 11,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'LogoLounge国际知名的LOGO设计权威网站',
'color' => '',
'desc' => 'https://www.logolounge.com/',
@ -654,6 +706,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 11,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'LogoMoose一个优秀的logo素材站点',
'color' => '',
'desc' => 'https://www.logomoose.com/',
@ -678,6 +732,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 11,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'LOGOED一个展示Logo设计的博客',
'color' => '',
'desc' => 'http://www.logoed.co.uk/page/2/',
@ -702,6 +758,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 11,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'Logo of the Day汇集世界各地优秀LOGO作品的站点',
'color' => '',
'desc' => 'https://logooftheday.com/',
@ -726,6 +784,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 2,
'column_id' => 5,
'dialog_id' => 0,
'flow_item_id' => 1,
'flow_item_name' => 'start|待处理',
'name' => 'asdasdad1111',
'color' => '',
'desc' => '7777777',
@ -750,6 +810,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 2,
'column_id' => 5,
'dialog_id' => 0,
'flow_item_id' => 1,
'flow_item_name' => 'start|待处理',
'name' => 'hjhjhjjh',
'color' => '',
'desc' => '',
@ -774,6 +836,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 11,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'Logo of the Day汇集世界各地优秀LOGO作品的站点',
'color' => '',
'desc' => '',
@ -798,6 +862,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 11,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'LogoDesignLoveLogo设计技巧分享网',
'color' => '',
'desc' => 'LogoDesignLoveLogo设计技巧分享网',
@ -822,6 +888,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 11,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'LogoDesignLoveLogo设计技巧分享网',
'color' => '',
'desc' => '',
@ -846,6 +914,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 12,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'Coolors自动生成配色色板的小工具',
'color' => '',
'desc' => 'https://coolors.co/',
@ -870,6 +940,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 12,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'Material PaletteMaterial Design专用在线配色工具',
'color' => '',
'desc' => 'https://www.materialpalette.com/',
@ -894,6 +966,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 12,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'Web安全色WEB设计、开发中常用的安全颜色',
'color' => '',
'desc' => 'https://www.bootcss.com/p/websafecolors/',
@ -918,6 +992,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 12,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'ColorZilla火狐浏览器网页取色器插件',
'color' => '',
'desc' => 'https://www.colorzilla.com/',
@ -942,6 +1018,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 12,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'Color Palette Generator图片配色工具',
'color' => '',
'desc' => '',
@ -966,6 +1044,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 12,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'inconfont: 免费提供高度可辨识符号图标',
'color' => '',
'desc' => 'https://www.iconfont.cn/',
@ -990,6 +1070,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 12,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'inconfont: 免费提供高度可辨识符号图标https://www.iconfont.cn/',
'color' => '',
'desc' => 'https://www.iconfont.cn/',
@ -1014,6 +1096,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 3,
'column_id' => 12,
'dialog_id' => 0,
'flow_item_id' => 5,
'flow_item_name' => 'start|待处理',
'name' => 'https://www.iconfont.cn/',
'color' => '',
'desc' => '',
@ -1038,6 +1122,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 13,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '将收集箱的事务进行判断。要立即执行进入Q2列表。非立即执行判断——1.不做删掉、2.稍晚再做进入「稍后做」、3.可做可不做的任务或可能有用的资源进入「Mark」列表',
'color' => '',
'desc' => '',
@ -1062,6 +1148,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 13,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '2分钟内能做完贴上2分钟标签进入「2分钟速战」列表。2分钟以上做完的事务进入Q3',
'color' => '',
'desc' => '',
@ -1086,6 +1174,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 13,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '判断能否一步做完能进入Q4不能打上多步标签进入「项目清单」或将多步骤任务分解成多个一步做完任务进入Q4。',
'color' => '',
'desc' => '',
@ -1110,6 +1200,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 13,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '如果自己做,打上自己做标签(进入「执行清单」);如果别人做,打上别人做标签(进入「等待清单」)。',
'color' => '',
'desc' => '',
@ -1134,6 +1226,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 2,
'column_id' => 2,
'dialog_id' => 0,
'flow_item_id' => 1,
'flow_item_name' => 'start|待处理',
'name' => '❓❗ 说明:将进度分成多级
每张卡片为一个项目任务,标签表示任务状况
@ -1162,6 +1256,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 13,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '每天晚上复查整套流程',
'color' => '',
'desc' => '',
@ -1186,6 +1282,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 13,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '1.检查「收集箱」是否清空。',
'color' => '',
'desc' => '',
@ -1210,6 +1308,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 13,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '2.检查「Mark」是否有条目需要执行转化成行动或项目。',
'color' => '',
'desc' => '',
@ -1234,6 +1334,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 13,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '3.检查「项目清单」了解项目进度。',
'color' => '',
'desc' => '',
@ -1258,6 +1360,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 13,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '4.检查「等待清单」是否有条目需要转化成行动,也就是催促。',
'color' => '',
'desc' => '',
@ -1282,6 +1386,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 13,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '5.检查各清单是否有已完成,完成的、已作废的卡片,立刻删除。',
'color' => '',
'desc' => '',
@ -1306,6 +1412,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 14,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '任何事务或信息,先以最简单的方式记录到“收集箱”。然后判断,贴上标签后,拖动到相应列表里',
'color' => '',
'desc' => '',
@ -1330,6 +1438,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 15,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '优先级1立即去做的事',
'color' => '',
'desc' => '',
@ -1354,6 +1464,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 14,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '任何事务或信息,先以最简单的方式记录到“收集箱”。然后判断,贴上标签后,拖动到相应列表里',
'color' => '',
'desc' => '',
@ -1378,6 +1490,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 16,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '优先级2主要的执行清单',
'color' => '',
'desc' => '',
@ -1402,6 +1516,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 17,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '优先级3存放需要多步骤做的事持续追踪',
'color' => '',
'desc' => '',
@ -1426,6 +1542,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 17,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '可以为这个多步骤项目单独建一个项目,并把项目的链接放到卡片的详情页里,点击就能跳转进去。',
'color' => '',
'desc' => '',
@ -1450,6 +1568,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 17,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '活动策划',
'color' => '',
'desc' => '',
@ -1474,6 +1594,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 16,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '填问卷',
'color' => '',
'desc' => '',
@ -1498,6 +1620,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 18,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '存放等待协同的事',
'color' => '',
'desc' => '',
@ -1522,6 +1646,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 18,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '设计稿反馈',
'color' => '',
'desc' => '',
@ -1546,6 +1672,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 18,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '存放等待协同的事',
'color' => '',
'desc' => '',
@ -1570,6 +1698,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 19,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '存放稍后做的任务',
'color' => '',
'desc' => '',
@ -1594,6 +1724,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 19,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '阅实习生简历',
'color' => '',
'desc' => '',
@ -1618,6 +1750,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 20,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '存放可做可不做的任务,以及各种可能用到的资源',
'color' => '',
'desc' => '',
@ -1642,6 +1776,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 4,
'column_id' => 20,
'dialog_id' => 0,
'flow_item_id' => 9,
'flow_item_name' => 'start|待处理',
'name' => '团建KTV',
'color' => '',
'desc' => '',
@ -1666,6 +1802,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 5,
'column_id' => 21,
'dialog_id' => 0,
'flow_item_id' => 13,
'flow_item_name' => 'start|待处理',
'name' => '产品新需求',
'color' => '',
'desc' => '',
@ -1690,6 +1828,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 5,
'column_id' => 22,
'dialog_id' => 0,
'flow_item_id' => 13,
'flow_item_name' => 'start|待处理',
'name' => '需要调研的需求',
'color' => '',
'desc' => '',
@ -1714,6 +1854,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 5,
'column_id' => 23,
'dialog_id' => 17,
'flow_item_id' => 13,
'flow_item_name' => 'start|待处理',
'name' => '进入交互设计的需求',
'color' => '',
'desc' => '',
@ -1738,6 +1880,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 5,
'column_id' => 25,
'dialog_id' => 0,
'flow_item_id' => 13,
'flow_item_name' => 'start|待处理',
'name' => '设计稿(放入设计稿)',
'color' => '',
'desc' => '',
@ -1762,6 +1906,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 5,
'column_id' => 25,
'dialog_id' => 0,
'flow_item_id' => 13,
'flow_item_name' => 'start|待处理',
'name' => '提测(放入提测时间规划表)',
'color' => '',
'desc' => '',
@ -1786,6 +1932,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 5,
'column_id' => 25,
'dialog_id' => 0,
'flow_item_id' => 13,
'flow_item_name' => 'start|待处理',
'name' => '发布流程(放入发布流程,应用文案等)',
'color' => '',
'desc' => '',
@ -1810,6 +1958,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 5,
'column_id' => 25,
'dialog_id' => 0,
'flow_item_id' => 13,
'flow_item_name' => 'start|待处理',
'name' => '-----上线需求-----(将左边列表中的需求拖动至下方,表示本版本上线需求)',
'color' => '',
'desc' => '',
@ -1834,6 +1984,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 6,
'column_id' => 26,
'dialog_id' => 0,
'flow_item_id' => 17,
'flow_item_name' => 'start|待处理',
'name' => '将小组一年工作目标拆解,设置出里程碑时间节点。并指派相关责任人。用标签来展示进行状况',
'color' => '',
'desc' => '',
@ -1858,6 +2010,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 6,
'column_id' => 27,
'dialog_id' => 0,
'flow_item_id' => 19,
'flow_item_name' => 'end|已完成',
'name' => '🚴 里程碑 1 需求评审完成,资源准备到位',
'color' => '',
'desc' => '',
@ -1882,6 +2036,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 6,
'column_id' => 27,
'dialog_id' => 0,
'flow_item_id' => 17,
'flow_item_name' => 'start|待处理',
'name' => '🚴 里程碑 2 设计完成,进行评审',
'color' => '',
'desc' => '',
@ -1906,6 +2062,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 6,
'column_id' => 28,
'dialog_id' => 0,
'flow_item_id' => 17,
'flow_item_name' => 'start|待处理',
'name' => '🚴 里程碑 3 产品开发完成,开始提测',
'color' => '',
'desc' => '',
@ -1930,6 +2088,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 6,
'column_id' => 28,
'dialog_id' => 0,
'flow_item_id' => 17,
'flow_item_name' => 'start|待处理',
'name' => '🚴 里程碑 4 测试完成准备线上发布',
'color' => '',
'desc' => '',
@ -1954,11 +2114,13 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 6,
'column_id' => 28,
'dialog_id' => 0,
'flow_item_id' => 17,
'flow_item_name' => 'start|待处理',
'name' => '测试1',
'color' => '',
'desc' => '',
'start_at' => NULL,
'end_at' => NULL,
'start_at' => seeders_at('2021-07-01 16:33:00'),
'end_at' => seeders_at('2021-07-02 23:59:00'),
'archived_at' => NULL,
'archived_userid' => 0,
'complete_at' => NULL,
@ -1978,11 +2140,13 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 6,
'column_id' => 28,
'dialog_id' => 0,
'flow_item_id' => 17,
'flow_item_name' => 'start|待处理',
'name' => '测试2',
'color' => '',
'desc' => '',
'start_at' => NULL,
'end_at' => NULL,
'start_at' => seeders_at('2021-07-01 16:33:00'),
'end_at' => seeders_at('2021-07-02 23:59:00'),
'archived_at' => NULL,
'archived_userid' => 0,
'complete_at' => NULL,
@ -2002,11 +2166,13 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 6,
'column_id' => 28,
'dialog_id' => 0,
'flow_item_id' => 17,
'flow_item_name' => 'start|待处理',
'name' => '测试3',
'color' => '',
'desc' => '',
'start_at' => NULL,
'end_at' => NULL,
'start_at' => seeders_at('2021-07-01 16:33:00'),
'end_at' => seeders_at('2021-07-02 23:59:00'),
'archived_at' => NULL,
'archived_userid' => 0,
'complete_at' => NULL,
@ -2026,6 +2192,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 6,
'column_id' => 29,
'dialog_id' => 0,
'flow_item_id' => 17,
'flow_item_name' => 'start|待处理',
'name' => '🚴 里程碑 5 市场发布',
'color' => '',
'desc' => '',
@ -2050,6 +2218,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 7,
'column_id' => 32,
'dialog_id' => 0,
'flow_item_id' => 21,
'flow_item_name' => 'start|待处理',
'name' => '产品官网设计',
'color' => '',
'desc' => '',
@ -2074,11 +2244,13 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 7,
'column_id' => 32,
'dialog_id' => 0,
'flow_item_id' => 23,
'flow_item_name' => 'end|已完成',
'name' => '首页',
'color' => '',
'desc' => '',
'start_at' => NULL,
'end_at' => NULL,
'start_at' => seeders_at('2021-07-01 16:15:55'),
'end_at' => seeders_at('2021-07-02 16:15:55'),
'archived_at' => NULL,
'archived_userid' => 0,
'complete_at' => seeders_at('2021-07-01 16:17:30'),
@ -2098,11 +2270,13 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 7,
'column_id' => 32,
'dialog_id' => 0,
'flow_item_id' => 23,
'flow_item_name' => 'end|已完成',
'name' => '公司介绍',
'color' => '',
'desc' => '',
'start_at' => NULL,
'end_at' => NULL,
'start_at' => seeders_at('2021-07-01 16:15:55'),
'end_at' => seeders_at('2021-07-02 16:15:55'),
'archived_at' => NULL,
'archived_userid' => 0,
'complete_at' => seeders_at('2021-07-01 16:17:31'),
@ -2122,11 +2296,13 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 7,
'column_id' => 32,
'dialog_id' => 0,
'flow_item_id' => 23,
'flow_item_name' => 'end|已完成',
'name' => '新闻动态',
'color' => '',
'desc' => '',
'start_at' => NULL,
'end_at' => NULL,
'start_at' => seeders_at('2021-07-01 16:15:55'),
'end_at' => seeders_at('2021-07-02 16:15:55'),
'archived_at' => NULL,
'archived_userid' => 0,
'complete_at' => seeders_at('2021-07-01 16:17:32'),
@ -2146,11 +2322,13 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 7,
'column_id' => 32,
'dialog_id' => 0,
'flow_item_id' => 23,
'flow_item_name' => 'end|已完成',
'name' => '产品介绍',
'color' => '',
'desc' => '',
'start_at' => NULL,
'end_at' => NULL,
'start_at' => seeders_at('2021-07-01 16:15:55'),
'end_at' => seeders_at('2021-07-02 16:15:55'),
'archived_at' => NULL,
'archived_userid' => 0,
'complete_at' => seeders_at('2021-07-01 16:17:34'),
@ -2170,11 +2348,13 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 7,
'column_id' => 32,
'dialog_id' => 0,
'flow_item_id' => 23,
'flow_item_name' => 'end|已完成',
'name' => '案例展示',
'color' => '',
'desc' => '',
'start_at' => NULL,
'end_at' => NULL,
'start_at' => seeders_at('2021-07-01 16:15:55'),
'end_at' => seeders_at('2021-07-02 16:15:55'),
'archived_at' => NULL,
'archived_userid' => 0,
'complete_at' => seeders_at('2021-07-01 16:17:35'),
@ -2194,11 +2374,13 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 7,
'column_id' => 32,
'dialog_id' => 0,
'flow_item_id' => 21,
'flow_item_name' => 'start|待处理',
'name' => '联系我们',
'color' => '',
'desc' => '',
'start_at' => NULL,
'end_at' => NULL,
'start_at' => seeders_at('2021-07-01 16:15:55'),
'end_at' => seeders_at('2021-07-02 16:15:55'),
'archived_at' => NULL,
'archived_userid' => 0,
'complete_at' => NULL,
@ -2218,6 +2400,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 7,
'column_id' => 32,
'dialog_id' => 0,
'flow_item_id' => 21,
'flow_item_name' => 'start|待处理',
'name' => '官网新增一级栏目,“招聘信息”',
'color' => '',
'desc' => '',
@ -2242,6 +2426,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 7,
'column_id' => 33,
'dialog_id' => 0,
'flow_item_id' => 21,
'flow_item_name' => 'start|待处理',
'name' => '产品官网',
'color' => '',
'desc' => '',
@ -2266,6 +2452,8 @@ class ProjectTasksTableSeeder extends Seeder
'project_id' => 5,
'column_id' => 24,
'dialog_id' => 0,
'flow_item_id' => 13,
'flow_item_name' => 'start|待处理',
'name' => '版本的确定',
'color' => '',
'desc' => '',

View File

@ -16,26 +16,24 @@ 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","login_code":"auto"}',
'setting' => '{"reg":"open","project_invite":"open","login_code":"auto"}',
'created_at' => seeders_at('2021-07-01 11:05:06'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
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":7,"priority":4}]',
'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}]',
'created_at' => seeders_at('2021-07-01 08:04:30'),
'updated_at' => seeders_at('2021-07-01 09:20:26'),
),

View File

@ -40,6 +40,7 @@ class UsersTableSeeder extends Seeder
'line_at' => seeders_at('2021-07-01 17:43:48'),
'task_dialog_id' => 18,
'created_ip' => '',
'disable_at' => null,
'created_at' => seeders_at('2021-07-01 11:01:14'),
'updated_at' => seeders_at('2021-07-01 17:43:48'),
),
@ -62,6 +63,7 @@ class UsersTableSeeder extends Seeder
'line_at' => seeders_at('2021-07-01 16:57:40'),
'task_dialog_id' => 16,
'created_ip' => '',
'disable_at' => null,
'created_at' => seeders_at('2021-07-01 11:01:14'),
'updated_at' => seeders_at('2021-07-01 16:58:00'),
),

File diff suppressed because one or more lines are too long

View File

@ -4,8 +4,8 @@ services:
php:
container_name: "dootask-php-${APP_ID}"
image: "kuaifan/php:swoole-8.0"
shm_size: "1024m"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./docker/crontab/crontab.conf:/etc/supervisor/conf.d/crontab.conf
- ./docker/php/php.conf:/etc/supervisor/conf.d/php.conf
- ./docker/php/php.ini:/usr/local/etc/php/php.ini
@ -45,6 +45,8 @@ services:
- php
- office
- fileview
- drawio-webapp
- drawio-export
restart: unless-stopped
redis:
@ -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"
image: "kuaifan/fileview:4.1.0-SNAPSHOT-RC3"
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/crontab/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.log

View File

@ -1,3 +1,5 @@
#!/bin/sh
curl "http://127.0.0.1:20000/crontab" >> /dev/null 2>&1
echo "$(date "+%Y-%m-%d %H:%M:%S")" > /var/www/docker/crontab/last.log
curl "http://127.0.0.1:20000/crontab" >> /var/www/docker/crontab/last.log
echo "\n$(date "+%Y-%m-%d %H:%M:%S")" >> /var/www/docker/crontab/last.log

1
docker/drawio/export/fonts/.gitignore vendored Normal file
View File

@ -0,0 +1 @@

View 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
- 隐藏加载中的提示

View File

@ -0,0 +1,477 @@
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5" ><![endif]-->
<!DOCTYPE html>
<html>
<head>
<title>Flowchart Maker &amp; 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&trade; and Lucidchart&trade; 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

File diff suppressed because one or more lines are too long

View 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:'↻'}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Some files were not shown because too many files have changed in this diff Show More