Compare commits

..

211 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
韦荣超
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
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
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
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
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
938 changed files with 30533 additions and 282201 deletions

View File

@ -19,7 +19,7 @@ jobs:
uses: loopwerk/tag-changelog@v1 uses: loopwerk/tag-changelog@v1
with: with:
token: ${{ secrets.GH_PAT }} token: ${{ secrets.GH_PAT }}
exclude_types: other,chore exclude_types: other,chore,build
- name: Create release - name: Create release
uses: actions/create-release@latest uses: actions/create-release@latest

1
.gitignore vendored
View File

@ -19,7 +19,6 @@ Homestead.yaml
npm-debug.log npm-debug.log
yarn-error.log yarn-error.log
test.* test.*
composer.lock
package-lock.json package-lock.json
laravels-timer-process.pid laravels-timer-process.pid
.DS_Store .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

@ -21,9 +21,9 @@ Group No.: `546574618`
# 1、Clone the repository # 1、Clone the repository
# Clone projects on github # Clone projects on github
git clone https://github.com/kuaifan/dootask.git git clone --depth=1 https://github.com/kuaifan/dootask.git
# Or you can use gitee # Or you can use gitee
git clone https://gitee.com/aipaw/dootask.git git clone --depth=1 https://gitee.com/aipaw/dootask.git
# 2、Enter directory # 2、Enter directory
cd dootask cd dootask

View File

@ -21,9 +21,9 @@
# 1、克隆项目到您的本地或服务器 # 1、克隆项目到您的本地或服务器
# 通过github克隆项目 # 通过github克隆项目
git clone https://github.com/kuaifan/dootask.git git clone --depth=1 https://github.com/kuaifan/dootask.git
# 或者你也可以使用gitee # 或者你也可以使用gitee
git clone https://gitee.com/aipaw/dootask.git git clone --depth=1 https://gitee.com/aipaw/dootask.git
# 2、进入目录 # 2、进入目录
cd dootask cd dootask

View File

@ -2,12 +2,14 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Models\File;
use App\Models\ProjectTask; use App\Models\ProjectTask;
use App\Models\ProjectTaskFile; use App\Models\ProjectTaskFile;
use App\Models\User; use App\Models\User;
use App\Models\WebSocketDialog; use App\Models\WebSocketDialog;
use App\Models\WebSocketDialogMsg; use App\Models\WebSocketDialogMsg;
use App\Models\WebSocketDialogMsgRead; use App\Models\WebSocketDialogMsgRead;
use App\Models\WebSocketDialogUser;
use App\Module\Base; use App\Module\Base;
use Carbon\Carbon; use Carbon\Carbon;
use Request; use Request;
@ -39,9 +41,10 @@ class DialogController extends AbstractController
{ {
$user = User::auth(); $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') ->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('u.userid', $user->userid) ->where('u.userid', $user->userid)
->orderByDesc('u.top_at')
->orderByDesc('web_socket_dialogs.last_at') ->orderByDesc('web_socket_dialogs.last_at')
->paginate(Base::getPaginate(200, 100)); ->paginate(Base::getPaginate(200, 100));
$list->transform(function (WebSocketDialog $item) use ($user) { $list->transform(function (WebSocketDialog $item) use ($user) {
@ -189,7 +192,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/sendtext 06. 发送消息 * @api {post} api/dialog/msg/sendtext 06. 发送消息
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -205,6 +208,7 @@ class DialogController extends AbstractController
*/ */
public function msg__sendtext() public function msg__sendtext()
{ {
Base::checkClientVersion('0.8.1');
$user = User::auth(); $user = User::auth();
// //
$chat_nickname = Base::settingFind('system', 'chat_nickname'); $chat_nickname = Base::settingFind('system', 'chat_nickname');
@ -215,8 +219,8 @@ class DialogController extends AbstractController
} }
} }
// //
$dialog_id = intval(Request::input('dialog_id')); $dialog_id = Base::getPostInt('dialog_id');
$text = trim(Request::input('text')); $text = trim(Base::getPostValue('text'));
// //
if (mb_strlen($text) < 1) { if (mb_strlen($text) < 1) {
return Base::retError('消息内容不能为空'); return Base::retError('消息内容不能为空');
@ -226,11 +230,21 @@ class DialogController extends AbstractController
// //
WebSocketDialog::checkDialog($dialog_id); WebSocketDialog::checkDialog($dialog_id);
// //
$msg = [ if (mb_strlen($text) > 2000) {
'text' => $text $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);
} }
/** /**
@ -258,7 +272,7 @@ class DialogController extends AbstractController
// //
$dialog = WebSocketDialog::checkDialog($dialog_id); $dialog = WebSocketDialog::checkDialog($dialog_id);
// //
$path = "uploads/chat/" . $user->userid . "/"; $path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
$image64 = Base::getPostValue('image64'); $image64 = Base::getPostValue('image64');
$fileName = Base::getPostValue('filename'); $fileName = Base::getPostValue('filename');
if ($image64) { if ($image64) {
@ -380,31 +394,13 @@ class DialogController extends AbstractController
$data = $dialogMsg->toArray(); $data = $dialogMsg->toArray();
// //
if ($data['type'] == 'file') { if ($data['type'] == 'file') {
$codeExt = ['txt'];
$officeExt = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
$localExt = ['jpg', 'jpeg', 'png', 'gif'];
$msg = Base::json2array($dialogMsg->getRawOriginal('msg')); $msg = Base::json2array($dialogMsg->getRawOriginal('msg'));
$filePath = public_path($msg['path']); $msg = File::formatFileData($msg);
if (in_array($msg['ext'], $codeExt) && $msg['size'] < 2 * 1024 * 1024) { $data['content'] = $msg['content'];
// 文本预览限制2M内的文件 $data['file_mode'] = $msg['file_mode'];
$data['content'] = file_get_contents($filePath);
$data['file_mode'] = 1;
} elseif (in_array($msg['ext'], $officeExt)) {
// office预览
$data['file_mode'] = 2;
} else {
// 其他预览
if (in_array($msg['ext'], $localExt)) {
$url = Base::fillUrl($msg['path']);
} else {
$url = 'http://' . env('APP_IPPR') . '.3/' . $msg['path'];
}
$data['url'] = base64_encode($url);
$data['file_mode'] = 3;
}
} }
// //
return Base::retSuccess("success", $data); return Base::retSuccess('success', $data);
} }
/** /**
@ -464,4 +460,31 @@ class DialogController extends AbstractController
$msg->deleteMsg(); $msg->deleteMsg();
return Base::retSuccess("success"); 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

@ -206,21 +206,30 @@ class FileController extends AbstractController
'folder', 'folder',
'document', 'document',
'mind', 'mind',
'flow', 'drawio',
'word', 'word',
'excel', 'excel',
'ppt', 'ppt',
])) { ])) {
return Base::retError('类型错误'); return Base::retError('类型错误');
} }
$ext = ''; $ext = str_replace([
if (in_array($type, [ 'folder',
'document',
'mind',
'drawio',
'word', 'word',
'excel', 'excel',
'ppt', 'ppt',
])) { ], [
$ext = str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $type); '',
} 'md',
'mind',
'drawio',
'docx',
'xlsx',
'pptx',
], $type);
// //
$userid = $user->userid; $userid = $user->userid;
if ($pid > 0) { if ($pid > 0) {
@ -293,9 +302,19 @@ class FileController extends AbstractController
'userid' => $userid, 'userid' => $userid,
'created_id' => $user->userid, 'created_id' => $user->userid,
]); ]);
$file->save(); $data = AbstractModel::transaction(function() use ($file) {
$content = FileContent::select(['content', 'text', 'size'])->whereFid($file->cid)->orderByDesc('id')->first();
$file->size = $content?->size ?: 0;
$file->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); $data->pushMsg('add', $data);
return Base::retSuccess('复制成功', $data); return Base::retSuccess('复制成功', $data);
} }
@ -308,7 +327,7 @@ class FileController extends AbstractController
* @apiGroup file * @apiGroup file
* @apiName move * @apiName move
* *
* @apiParam {Number} id 文件ID * @apiParam {Numbers} ids 文件ID(格式:[id1, id2]
* @apiParam {Number} pid 移动到的文件夹ID * @apiParam {Number} pid 移动到的文件夹ID
* *
* @apiSuccess {Number} ret 返回状态码1正确、0错误 * @apiSuccess {Number} ret 返回状态码1正确、0错误
@ -319,30 +338,45 @@ class FileController extends AbstractController
{ {
$user = User::auth(); $user = User::auth();
// //
$id = intval(Request::input('id')); $ids = Request::input('ids');
$pid = intval(Request::input('pid')); $pid = intval(Request::input('pid'));
// //
$file = File::permissionFind($id, 1000); if (!is_array($ids) || empty($ids)) {
// return Base::retError('请选择移动的文件或文件夹');
}
if (count($ids) > 100) {
return Base::retError('一次最多只能移动100个文件或文件夹');
}
if ($pid > 0) { if ($pid > 0) {
if (!File::whereUserid($user->userid)->whereId($pid)->exists()) { File::permissionFind($pid, 1);
return Base::retError('参数错误');
}
$arr = [];
$tid = $pid;
while ($tid > 0) {
$arr[] = $tid;
$tid = intval(File::whereId($tid)->value('pid'));
}
if (in_array($id, $arr)) {
return Base::retError('位置错误');
}
} }
// //
$file->pid = $pid; $files = [];
$file->save(); AbstractModel::transaction(function() use ($pid, $ids, &$files) {
$file->pushMsg('update', $file); foreach ($ids as $id) {
return Base::retSuccess('操作成功', $file); $file = File::permissionFind($id, 1000);
//
if ($pid > 0) {
$arr = [];
$tid = $pid;
while ($tid > 0) {
$arr[] = $tid;
$tid = intval(File::whereId($tid)->value('pid'));
}
if (in_array($id, $arr)) {
throw new ApiException('移动位置错误');
}
}
//
$file->pid = $pid;
$file->save();
$files[] = $file;
}
});
foreach ($files as $file) {
$file->pushMsg('update', $file);
}
return Base::retSuccess('操作成功', $files);
} }
/** /**
@ -353,7 +387,7 @@ class FileController extends AbstractController
* @apiGroup file * @apiGroup file
* @apiName remove * @apiName remove
* *
* @apiParam {Number} id 文件ID * @apiParam {Numbers} ids 文件ID(格式:[id1, id2]
* *
* @apiSuccess {Number} ret 返回状态码1正确、0错误 * @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述) * @apiSuccess {String} msg 返回信息(错误描述)
@ -363,12 +397,25 @@ class FileController extends AbstractController
{ {
User::auth(); User::auth();
// //
$id = intval(Request::input('id')); $ids = Request::input('ids');
// //
$file = File::permissionFind($id, 1000); if (!is_array($ids) || empty($ids)) {
return Base::retError('请选择删除的文件或文件夹');
}
if (count($ids) > 100) {
return Base::retError('一次最多只能删除100个文件或文件夹');
}
// //
$file->deleteFile(); $files = [];
return Base::retSuccess('删除成功', $file); AbstractModel::transaction(function() use ($ids, &$files) {
foreach ($ids as $id) {
$file = File::permissionFind($id, 1000);
$file->deleteFile();
$files[] = $file;
}
});
//
return Base::retSuccess('删除成功', $files);
} }
/** /**
@ -431,7 +478,7 @@ class FileController extends AbstractController
* @apiGroup file * @apiGroup file
* @apiName content__save * @apiName content__save
* *
* @apiParam {Number} id 文件ID * @apiParam {Number} id 文件ID
* @apiParam {Object} [D] Request Payload 提交 * @apiParam {Object} [D] Request Payload 提交
* - content: 内容 * - content: 内容
* *
@ -441,6 +488,7 @@ class FileController extends AbstractController
*/ */
public function content__save() public function content__save()
{ {
Base::checkClientVersion('0.9.13');
$user = User::auth(); $user = User::auth();
// //
$id = Base::getPostInt('id'); $id = Base::getPostInt('id');
@ -454,12 +502,11 @@ class FileController extends AbstractController
$isRep = false; $isRep = false;
preg_match_all("/<img\s*src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"/s", $data['content'], $matchs); preg_match_all("/<img\s*src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"/s", $data['content'], $matchs);
foreach ($matchs[2] as $key => $text) { foreach ($matchs[2] as $key => $text) {
$p = "uploads/files/document/" . $id . "/"; $tmpPath = "uploads/file/document/" . date("Ym") . "/" . $id . "/attached/";
Base::makeDir(public_path($p)); Base::makeDir(public_path($tmpPath));
$p.= md5($text) . "." . $matchs[1][$key]; $tmpPath .= md5($text) . "." . $matchs[1][$key];
$r = file_put_contents(public_path($p), base64_decode($text)); if (file_put_contents(public_path($tmpPath), base64_decode($text))) {
if ($r) { $data['content'] = str_replace($matchs[0][$key], '<img src="' . Base::fillUrl($tmpPath) . '"', $data['content']);
$data['content'] = str_replace($matchs[0][$key], '<img src="' . Base::fillUrl($p) . '"', $data['content']);
$isRep = true; $isRep = true;
} }
} }
@ -469,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([ $content = FileContent::createInstance([
'fid' => $file->id, 'fid' => $file->id,
'content' => $content, 'content' => [
'type' => $file->ext,
'url' => $path
],
'text' => $text, 'text' => $text,
'size' => strlen($content), 'size' => filesize($save),
'userid' => $user->userid, 'userid' => $user->userid,
]); ]);
$content->save(); $content->save();
@ -513,7 +590,7 @@ class FileController extends AbstractController
if ($status === 2) { if ($status === 2) {
$parse = parse_url($url); $parse = parse_url($url);
$from = 'http://' . env('APP_IPPR') . '.3' . $parse['path'] . '?' . $parse['query']; $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); $save = public_path($path);
Base::makeDir(dirname($save)); Base::makeDir(dirname($save));
$res = Ihttp::download($from, $save); $res = Ihttp::download($from, $save);
@ -531,6 +608,7 @@ class FileController extends AbstractController
$content->save(); $content->save();
// //
$file->size = $content->size; $file->size = $content->size;
$file->updated_at = Carbon::now();
$file->save(); $file->save();
$file->pushMsg('update', $file); $file->pushMsg('update', $file);
} }
@ -603,7 +681,7 @@ class FileController extends AbstractController
} }
} }
// //
$path = 'uploads/file/' . date("Ym") . '/u' . $user->userid . '/'; $path = 'uploads/tmp/' . date("Ym") . '/';
$data = Base::upload([ $data = Base::upload([
"file" => Request::file('files'), "file" => Request::file('files'),
"type" => 'more', "type" => 'more',
@ -616,6 +694,9 @@ class FileController extends AbstractController
$data = $data['data']; $data = $data['data'];
// //
$type = match ($data['ext']) { $type = match ($data['ext']) {
'text', 'md', 'markdown' => 'document',
'drawio' => 'drawio',
'mind' => 'mind',
'doc', 'docx' => "word", 'doc', 'docx' => "word",
'xls', 'xlsx' => "excel", 'xls', 'xlsx' => "excel",
'ppt', 'pptx' => "ppt", 'ppt', 'pptx' => "ppt",
@ -629,7 +710,7 @@ class FileController extends AbstractController
'txt' => "txt", 'txt' => "txt",
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css', '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', 'dockerfile', 'go', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
'ocamlmakefile', 'make', 'md', 'markdown', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php', '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', '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', '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', 'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
@ -640,6 +721,9 @@ class FileController extends AbstractController
'rp' => "axure", 'rp' => "axure",
default => "", default => "",
}; };
if ($data['ext'] == 'markdown') {
$data['ext'] = 'md';
}
$file = File::createInstance([ $file = File::createInstance([
'pid' => $pid, 'pid' => $pid,
'name' => Base::rightDelete($data['name'], '.' . $data['ext']), 'name' => Base::rightDelete($data['name'], '.' . $data['ext']),
@ -653,6 +737,7 @@ class FileController extends AbstractController
$file->size = $data['size'] * 1024; $file->size = $data['size'] * 1024;
$file->save(); $file->save();
// //
$data = Base::uploadMove($data, "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $file->id . "/");
$content = FileContent::createInstance([ $content = FileContent::createInstance([
'fid' => $file->id, 'fid' => $file->id,
'content' => [ 'content' => [

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
use App\Exceptions\ApiException; use App\Exceptions\ApiException;
use App\Models\AbstractModel; use App\Models\AbstractModel;
use App\Models\File;
use App\Models\Project; use App\Models\Project;
use App\Models\ProjectColumn; use App\Models\ProjectColumn;
use App\Models\ProjectFlow; use App\Models\ProjectFlow;
@ -16,10 +17,13 @@ use App\Models\ProjectUser;
use App\Models\User; use App\Models\User;
use App\Models\WebSocketDialog; use App\Models\WebSocketDialog;
use App\Module\Base; use App\Module\Base;
use App\Module\BillExport;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Madzipper;
use Request; use Request;
use Response; use Response;
use Session;
/** /**
* @apiDefine project * @apiDefine project
@ -603,13 +607,14 @@ class ProjectController extends AbstractController
if (!is_array($item['task'])) continue; if (!is_array($item['task'])) continue;
$index = 0; $index = 0;
foreach ($item['task'] as $task_id) { foreach ($item['task'] as $task_id) {
ProjectTask::whereId($task_id)->whereProjectId($project->id)->update([ if (ProjectTask::whereId($task_id)->whereProjectId($project->id)->whereCompleteAt(null)->update([
'column_id' => $item['id'], 'column_id' => $item['id'],
'sort' => $index 'sort' => $index
]); ])) {
ProjectTask::whereParentId($task_id)->whereProjectId($project->id)->update([ ProjectTask::whereParentId($task_id)->whereProjectId($project->id)->update([
'column_id' => $item['id'], 'column_id' => $item['id'],
]); ]);
}
$index++; $index++;
} }
} }
@ -975,6 +980,154 @@ class ProjectController extends AbstractController
return Base::retSuccess('success', $list); return Base::retSuccess('success', $list);
} }
/**
* @api {get} api/project/task/export 18. 导出任务(限管理员)
*
* @apiDescription 导出指定范围任务已完成、未完成、已归档返回下载地址需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__export
*
* @apiParam {Array} [userid] 指定会员,如:[1, 2]
* @apiParam {Array} [time] 指定时间范围,如:['2020-12-12', '2020-12-30']
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function task__export()
{
$user = User::auth('admin');
//
$userid = Base::arrayRetainInt(Request::input('userid'), true);
$time = Request::input('time');
if (empty($userid) || empty($time)) {
return Base::retError('参数错误');
}
if (count($userid) > 20) {
return Base::retError('导出会员限制最多20个');
}
if (!(is_array($time) && Base::isDateOrTime($time[0]) && Base::isDateOrTime($time[1]))) {
return Base::retError('时间选择错误');
}
if (Carbon::parse($time[1])->timestamp - Carbon::parse($time[0])->timestamp > 90 * 86400) {
return Base::retError('时间范围限制最大90天');
}
//
$headings = [];
$headings[] = '任务ID';
$headings[] = '任务标题';
$headings[] = '负责人';
$headings[] = '创建人';
$headings[] = '是否完成';
$headings[] = '完成时间';
$headings[] = '是否归档';
$headings[] = '归档时间';
$headings[] = '任务开始时间';
$headings[] = '任务结束时间';
$headings[] = '结束剩余';
$headings[] = '所属项目';
$headings[] = '父级任务ID';
$datas = [];
//
$builder = ProjectTask::select(['project_tasks.*', 'project_task_users.userid as ownerid'])
->join('project_task_users', 'project_tasks.id', '=', 'project_task_users.task_id')
->where('project_task_users.owner', 1)
->whereIn('project_task_users.userid', $userid)
->betweenTime(Carbon::parse($time[0])->startOfDay(), Carbon::parse($time[1])->endOfDay());
$builder->orderByDesc('project_tasks.id')->chunk(100, function($tasks) use (&$datas) {
/** @var ProjectTask $task */
foreach ($tasks as $task) {
if ($task->complete_at) {
$a = Carbon::parse($task->complete_at)->timestamp;
$b = Carbon::parse($task->end_at)->timestamp;
if ($b > $a) {
$endSurplus = Base::timeDiff($a, $b);
} else {
$endSurplus = "-" . Base::timeDiff($b, $a);
}
} else {
$endSurplus = '-';
}
$datas[] = [
$task->id,
Base::filterEmoji($task->name),
Base::filterEmoji(User::userid2nickname($task->ownerid)) . " (ID: {$task->ownerid})",
Base::filterEmoji(User::userid2nickname($task->userid)) . " (ID: {$task->userid})",
$task->complete_at ? '已完成' : '-',
$task->complete_at ?: '-',
$task->archived_at ? '已归档' : '-',
$task->archived_at ?: '-',
$task->start_at ?: '-',
$task->end_at ?: '-',
$endSurplus,
Base::filterEmoji($task->project?->name) ?: '-',
$task->parent_id ?: '-',
];
}
});
//
$fileName = User::userid2nickname($userid[0]) ?: $userid[0];
if (count($userid) > 1) {
$fileName .= "" . count($userid) . "位成员";
}
$fileName .= '任务统计_' . Base::time() . '.xls';
$filePath = "temp/task/export/" . date("Ym", Base::time());
$res = BillExport::create()->setHeadings($headings)->setData($datas)->store($filePath . "/" . $fileName);
if ($res != 1) {
return Base::retError('导出失败,' . $fileName . '');
}
$xlsPath = storage_path("app/" . $filePath . "/" . $fileName);
$zipFile = "app/" . $filePath . "/" . Base::rightDelete($fileName, '.xls'). ".zip";
$zipPath = storage_path($zipFile);
if (file_exists($zipPath)) {
Base::deleteDirAndFile($zipPath, true);
}
try {
Madzipper::make($zipPath)->add($xlsPath)->close();
} catch (\Exception) { }
//
if (file_exists($zipPath)) {
$base64 = base64_encode(Base::array2string([
'file' => $zipFile,
]));
Session::put('task::export:userid', $user->userid);
return Base::retSuccess('success', [
'size' => Base::twoFloat(filesize($zipPath) / 1024, true),
'url' => Base::fillUrl('api/project/task/down?key=' . urlencode($base64)),
]);
} else {
return Base::retError('打包失败,请稍后再试...');
}
}
/**
* @api {get} api/project/task/down 18. 导出任务(限管理员)
*
* @apiDescription 导出指定范围任务已完成、未完成、已归档返回下载地址需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__down
*
* @apiParam {String} key 通过export接口得到的下载钥匙
*
* @apiSuccess {File} 文件下载
*/
public function task__down()
{
$userid = Session::get('task::export:userid');
if (empty($userid)) {
return Base::ajaxError("请求已过期,请重新导出!", [], 0, 502);
}
//
$array = Base::string2array(base64_decode(urldecode(Request::input('key'))));
$file = $array['file'];
if (empty($file) || !file_exists(storage_path($file))) {
return Base::ajaxError("文件不存在!", [], 0, 502);
}
return response()->download(storage_path($file));
}
/** /**
* @api {get} api/project/task/one 19. 获取单个任务信息 * @api {get} api/project/task/one 19. 获取单个任务信息
* *
@ -1094,7 +1247,7 @@ class ProjectController extends AbstractController
/** /**
* @api {get} api/project/task/filedetail 23. 获取任务文件详情 * @api {get} api/project/task/filedetail 23. 获取任务文件详情
* *
* @apiDescription 需要token身份(限:项目、任务负责人) * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiGroup project * @apiGroup project
* @apiName task__filedetail * @apiName task__filedetail
@ -1130,37 +1283,15 @@ class ProjectController extends AbstractController
$data = $file->toArray(); $data = $file->toArray();
$data['path'] = $file->getRawOriginal('path'); $data['path'] = $file->getRawOriginal('path');
// //
ProjectTask::userTask($file->task_id, true, true); ProjectTask::userTask($file->task_id, null);
// //
$codeExt = ['txt']; return Base::retSuccess('success', File::formatFileData($data));
$officeExt = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
$localExt = ['jpg', 'jpeg', 'png', 'gif'];
$filePath = public_path($data['path']);
if (in_array($data['ext'], $codeExt) && $data['size'] < 2 * 1024 * 1024) {
// 文本预览限制2M内的文件
$data['content'] = file_get_contents($filePath);
$data['file_mode'] = 1;
} elseif (in_array($data['ext'], $officeExt)) {
// office预览
$data['file_mode'] = 2;
} else {
// 其他预览
if (in_array($data['ext'], $localExt)) {
$url = Base::fillUrl($data['path']);
} else {
$url = 'http://' . env('APP_IPPR') . '.3/' . $data['path'];
}
$data['url'] = base64_encode($url);
$data['file_mode'] = 3;
}
//
return Base::retSuccess('success', $data);
} }
/** /**
* @api {get} api/project/task/filedown 24. 下载任务文件 * @api {get} api/project/task/filedown 24. 下载任务文件
* *
* @apiDescription 需要token身份(限:项目、任务负责人) * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiGroup project * @apiGroup project
* @apiName task__filedown * @apiName task__filedown
@ -1183,7 +1314,7 @@ class ProjectController extends AbstractController
} }
// //
try { try {
ProjectTask::userTask($file->task_id, true, true); ProjectTask::userTask($file->task_id, null);
} catch (\Exception $e) { } catch (\Exception $e) {
abort(403, $e->getMessage() ?: "This file not support download."); abort(403, $e->getMessage() ?: "This file not support download.");
} }
@ -1747,4 +1878,31 @@ class ProjectController extends AbstractController
// //
return Base::retSuccess('success', $list); return Base::retSuccess('success', $list);
} }
/**
* @api {get} api/project/top 37. 项目置顶
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName top
*
* @apiParam {Number} project_id 项目ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function top()
{
$user = User::auth();
$projectId = intval(Request::input('project_id'));
$projectUser = ProjectUser::whereUserid($user->userid)->whereProjectId($projectId)->first();
if (!$projectUser) {
return Base::retError("项目不存在");
}
$projectUser->top_at = $projectUser->top_at ? null : Carbon::now();
$projectUser->save();
return Base::retSuccess("success", $projectId);
}
} }

View File

@ -150,7 +150,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {post} api/system/column/template 03. 创建项目模板 * @api {post} api/system/column/template 04. 创建项目模板
* *
* @apiDescription 获取创建项目模板、保存创建项目模板 * @apiDescription 获取创建项目模板、保存创建项目模板
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -197,7 +197,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {get} api/system/get/info 04. 获取终端详细信息 * @api {get} api/system/get/info 05. 获取终端详细信息
* *
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiGroup system * @apiGroup system
@ -226,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 * @apiVersion 1.0.0
* @apiGroup system * @apiGroup system
@ -241,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 * @apiVersion 1.0.0
* @apiGroup system * @apiGroup system
@ -258,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 * @apiVersion 1.0.0
* @apiGroup system * @apiGroup system
@ -275,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 * @apiVersion 1.0.0
* @apiGroup system * @apiGroup system
@ -292,7 +292,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {post} api/system/imgupload 09. 上传图片 * @api {post} api/system/imgupload 10. 上传图片
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -315,7 +315,7 @@ class SystemController extends AbstractController
if (!$scale[0] && !$scale[1]) { if (!$scale[0] && !$scale[1]) {
$scale = [2160, 4160, -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')); $image64 = trim(Base::getPostValue('image64'));
$fileName = trim(Base::getPostValue('filename')); $fileName = trim(Base::getPostValue('filename'));
if ($image64) { if ($image64) {
@ -342,7 +342,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {get} api/system/get/imgview 10. 浏览图片空间 * @api {get} api/system/get/imgview 11. 浏览图片空间
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -360,7 +360,7 @@ class SystemController extends AbstractController
if (User::userid() === 0) { if (User::userid() === 0) {
return Base::retError('身份失效,等重新登录'); return Base::retError('身份失效,等重新登录');
} }
$publicPath = "uploads/picture/" . User::userid() . "/"; $publicPath = "uploads/user/picture/" . User::userid() . "/";
$dirPath = public_path($publicPath); $dirPath = public_path($publicPath);
$dirs = $files = []; $dirs = $files = [];
// //
@ -438,7 +438,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {post} api/system/fileupload 11. 上传文件 * @api {post} api/system/fileupload 12. 上传文件
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -458,7 +458,7 @@ class SystemController extends AbstractController
if (User::userid() === 0) { if (User::userid() === 0) {
return Base::retError('身份失效,等重新登录'); return Base::retError('身份失效,等重新登录');
} }
$path = "uploads/files/" . User::userid() . "/" . date("Ym") . "/"; $path = "uploads/user/file/" . User::userid() . "/" . date("Ym") . "/";
$image64 = trim(Base::getPostValue('image64')); $image64 = trim(Base::getPostValue('image64'));
$fileName = trim(Base::getPostValue('filename')); $fileName = trim(Base::getPostValue('filename'));
if ($image64) { if ($image64) {

View File

@ -33,6 +33,9 @@ class VerifyCsrfToken extends Middleware
// 修改任务 // 修改任务
'api/project/task/update/', 'api/project/task/update/',
// 聊天发文本
'api/dialog/msg/sendtext/',
// 聊天发文件 // 聊天发文件
'api/dialog/msg/sendfile/', 'api/dialog/msg/sendfile/',

View File

@ -153,9 +153,10 @@ class AbstractModel extends Model
* @param $where * @param $where
* @param array $update 存在时更新的内容 * @param array $update 存在时更新的内容
* @param array $insert 不存在时插入的内容,如果没有则插入更新内容 * @param array $insert 不存在时插入的内容,如果没有则插入更新内容
* @param bool $isInsert 是否是插入数据
* @return AbstractModel|\Illuminate\Database\Eloquent\Builder|Model|object|static|null * @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(); $row = static::where($where)->first();
if (empty($row)) { if (empty($row)) {
@ -165,8 +166,10 @@ class AbstractModel extends Model
unset($array[$row->primaryKey]); unset($array[$row->primaryKey]);
} }
$row->updateInstance($array); $row->updateInstance($array);
$isInsert = true;
} elseif ($update) { } elseif ($update) {
$row->updateInstance($update); $row->updateInstance($update);
$isInsert = false;
} }
if (!$row->save()) { if (!$row->save()) {
return null; return null;

View File

@ -50,6 +50,39 @@ class File extends AbstractModel
{ {
use SoftDeletes; use SoftDeletes;
/**
* 文件文件
*/
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 * @param $userid
@ -239,4 +272,77 @@ class File extends AbstractModel
} }
return $file; 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

@ -60,7 +60,7 @@ class FileContent extends AbstractModel
if (empty($content)) { if (empty($content)) {
$content = match ($file->type) { $content = match ($file->type) {
'document' => [ 'document' => [
"type" => "md", "type" => $file->ext,
"content" => "", "content" => "",
], ],
default => json_decode('{}'), default => json_decode('{}'),
@ -69,24 +69,20 @@ class FileContent extends AbstractModel
abort(403, "This file is empty."); abort(403, "This file is empty.");
} }
} else { } else {
$content['preview'] = false; $path = $content['url'];
if ($file->ext) { if ($file->ext) {
$filePath = public_path($content['url']); $res = File::formatFileData([
if (in_array($file->type, ['txt', 'code']) && $file->size < 2 * 1024 * 1024) { 'path' => $path,
// 支持编辑限制2M内的文件 'ext' => $file->ext,
$content['content'] = file_get_contents($filePath); 'size' => $file->size,
} else { 'name' => $file->name,
// 支持预览 ]);
if (in_array($file->type, ['picture', 'image', 'tif', 'media'])) { $content = $res['content'];
$url = Base::fillUrl($content['url']); } else {
} else { $content['preview'] = false;
$url = 'http://' . env('APP_IPPR') . '.3/' . $content['url'];
}
$content['url'] = base64_encode($url);
$content['preview'] = true;
}
} }
if ($download) { if ($download) {
$filePath = public_path($path);
if (isset($filePath)) { if (isset($filePath)) {
return Response::download($filePath, $name); return Response::download($filePath, $name);
} else { } else {

View File

@ -113,6 +113,7 @@ class Project extends AbstractModel
->select([ ->select([
'projects.*', 'projects.*',
'project_users.owner', 'project_users.owner',
'project_users.top_at',
]) ])
->leftJoin('project_users', function ($leftJoin) use ($userid) { ->leftJoin('project_users', function ($leftJoin) use ($userid) {
$leftJoin $leftJoin
@ -136,6 +137,7 @@ class Project extends AbstractModel
->select([ ->select([
'projects.*', 'projects.*',
'project_users.owner', 'project_users.owner',
'project_users.top_at',
]) ])
->join('project_users', 'projects.id', '=', 'project_users.project_id') ->join('project_users', 'projects.id', '=', 'project_users.project_id')
->where('project_users.userid', $userid); ->where('project_users.userid', $userid);
@ -374,6 +376,7 @@ class Project extends AbstractModel
$idc = []; $idc = [];
$hasStart = false; $hasStart = false;
$hasEnd = false; $hasEnd = false;
$upTaskList = [];
foreach ($flows as $item) { foreach ($flows as $item) {
$id = intval($item['id']); $id = intval($item['id']);
$turns = Base::arrayRetainInt($item['turns'] ?: [], true); $turns = Base::arrayRetainInt($item['turns'] ?: [], true);
@ -401,7 +404,7 @@ class Project extends AbstractModel
'userids' => $userids, 'userids' => $userids,
'usertype' => trim($item['usertype']), 'usertype' => trim($item['usertype']),
'userlimit' => $userlimit, 'userlimit' => $userlimit,
]); ], [], $isInsert);
if ($flow) { if ($flow) {
$ids[] = $flow->id; $ids[] = $flow->id;
if ($flow->id != $id) { if ($flow->id != $id) {
@ -413,6 +416,9 @@ class Project extends AbstractModel
if ($flow->status == 'end') { if ($flow->status == 'end') {
$hasEnd = true; $hasEnd = true;
} }
if (!$isInsert) {
$upTaskList[$flow->id] = $flow->status . "|" . $flow->name;
}
} }
} }
if (!$hasStart) { if (!$hasStart) {
@ -427,6 +433,12 @@ class Project extends AbstractModel
} }
}); });
// //
foreach ($upTaskList as $id => $value) {
ProjectTask::whereFlowItemId($id)->update([
'flow_item_name' => $value
]);
}
//
$projectFlow = ProjectFlow::with(['projectFlowItem'])->whereProjectId($this->id)->find($projectFlow->id); $projectFlow = ProjectFlow::with(['projectFlowItem'])->whereProjectId($this->id)->find($projectFlow->id);
$itemIds = $projectFlow->projectFlowItem->pluck('id')->toArray(); $itemIds = $projectFlow->projectFlowItem->pluck('id')->toArray();
foreach ($projectFlow->projectFlowItem as $item) { foreach ($projectFlow->projectFlowItem as $item) {

View File

@ -508,13 +508,15 @@ class ProjectTask extends AbstractModel
{ {
AbstractModel::transaction(function () use ($data, &$updateMarking) { AbstractModel::transaction(function () use ($data, &$updateMarking) {
// 判断版本 // 判断版本
if (version_compare(Base::getClientVersion(), '0.6.0', '<')) { Base::checkClientVersion('0.6.0');
throw new ApiException('当前版本过低');
}
// 主任务 // 主任务
$mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null; $mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null;
// 工作流 // 工作流
if (Arr::exists($data, 'flow_item_id')) { 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']) { if ($this->flow_item_id == $data['flow_item_id']) {
throw new ApiException('任务状态未发生改变'); throw new ApiException('任务状态未发生改变');
} }
@ -535,8 +537,7 @@ class ProjectTask extends AbstractModel
throw new ApiException("当前状态[{$currentFlowItem->name}]不可流转到[{$newFlowItem->name}]"); throw new ApiException("当前状态[{$currentFlowItem->name}]不可流转到[{$newFlowItem->name}]");
} }
if ($currentFlowItem->userlimit) { if ($currentFlowItem->userlimit) {
if (!in_array(User::userid(), $currentFlowItem->userids) if (!$isProjectOwner && !in_array(User::userid(), $currentFlowItem->userids)) {
&& !ProjectUser::whereProjectId($this->project_id)->whereOwner(1)->exists()) {
throw new ApiException("当前状态[{$currentFlowItem->name}]仅限状态负责人或项目负责人修改"); throw new ApiException("当前状态[{$currentFlowItem->name}]仅限状态负责人或项目负责人修改");
} }
} }
@ -584,6 +585,14 @@ class ProjectTask extends AbstractModel
'flow' => $flowData, 'flow' => $flowData,
'change' => [$currentFlowItem?->name, $newFlowItem->name] '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 (Arr::exists($data, 'complete_at')) {
@ -605,7 +614,6 @@ class ProjectTask extends AbstractModel
$this->completeTask(null); $this->completeTask(null);
} }
$updateMarking['is_update_project'] = true; $updateMarking['is_update_project'] = true;
return;
} }
// 标题 // 标题
if (Arr::exists($data, 'name') && $this->name != $data['name']) { if (Arr::exists($data, 'name') && $this->name != $data['name']) {
@ -664,6 +672,7 @@ class ProjectTask extends AbstractModel
// 计划时间(原则:子任务时间在主任务时间内) // 计划时间(原则:子任务时间在主任务时间内)
if (Arr::exists($data, 'times')) { if (Arr::exists($data, 'times')) {
$oldAt = [Carbon::parse($this->start_at), Carbon::parse($this->end_at)]; $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->start_at = null;
$this->end_at = null; $this->end_at = null;
$times = $data['times']; $times = $data['times'];
@ -727,7 +736,10 @@ class ProjectTask extends AbstractModel
} }
}); });
} }
$this->addLog("修改{任务}时间"); $newStringAt = $this->start_at ? ($this->start_at->toDateTimeString() . '~' . $this->end_at->toDateTimeString()) : '';
$this->addLog("修改{任务}时间", [
'change' => [$oldStringAt, $newStringAt]
]);
} }
// 以下紧顶级任务可修改 // 以下紧顶级任务可修改
if ($this->parent_id === 0) { if ($this->parent_id === 0) {
@ -797,6 +809,7 @@ class ProjectTask extends AbstractModel
} }
// 优先级 // 优先级
$p = false; $p = false;
$oldPName = $this->p_name;
if (Arr::exists($data, 'p_level') && $this->p_level != $data['p_level']) { if (Arr::exists($data, 'p_level') && $this->p_level != $data['p_level']) {
$this->p_level = intval($data['p_level']); $this->p_level = intval($data['p_level']);
$p = true; $p = true;
@ -810,7 +823,9 @@ class ProjectTask extends AbstractModel
$p = true; $p = true;
} }
if ($p) { if ($p) {
$this->addLog("修改{任务}优先级"); $this->addLog("修改{任务}优先级", [
'change' => [$oldPName, $this->p_name]
]);
} }
} }
$this->save(); $this->save();
@ -887,6 +902,44 @@ class ProjectTask extends AbstractModel
return $user->owner ? 2 : 1; 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 * @return bool
@ -954,6 +1007,16 @@ class ProjectTask extends AbstractModel
*/ */
public function archivedTask($archived_at, $isAuto = false) public function archivedTask($archived_at, $isAuto = false)
{ {
if (!$this->complete_at) {
$flowItems = ProjectFlowItem::whereProjectId($this->project_id)->whereStatus('end')->pluck('name');
if ($flowItems) {
$flowItems = implode(",", array_values(array_unique($flowItems->toArray())));
}
if (empty($flowItems)) {
$flowItems = "已完成";
}
throw new ApiException('仅限【' . $flowItems . '】状态的任务归档');
}
AbstractModel::transaction(function () use ($isAuto, $archived_at) { AbstractModel::transaction(function () use ($isAuto, $archived_at) {
if ($archived_at === null) { if ($archived_at === null) {
// 取消归档 // 取消归档
@ -1106,11 +1169,11 @@ class ProjectTask extends AbstractModel
* 获取任务(会员有任务权限 会员存在项目内) * 获取任务(会员有任务权限 会员存在项目内)
* @param int $task_id * @param int $task_id
* @param bool $archived true:仅限未归档, false:仅限已归档, null:不限制 * @param bool $archived true:仅限未归档, false:仅限已归档, null:不限制
* @param int|bool $mustOwner 0|false:不限制, 1|true:限制任务或项目负责人, 2:已有负责人才限制任务或项目负责人(子任务时如果是主任务负责人也可以) * @param int|bool $permission 0|false:不限制, 1|true:限制项目负责人、任务负责人、协助人员及任务创建者, 2:已有负责人才限制true (子任务时如果是主任务负责人也可以)
* @param array $with * @param array $with
* @return self * @return self
*/ */
public static function userTask($task_id, $archived = true, $mustOwner = 0, $with = []) public static function userTask($task_id, $archived = true, $permission = 0, $with = [])
{ {
$task = self::with($with)->allData()->where("project_tasks.id", intval($task_id))->first(); $task = self::with($with)->allData()->where("project_tasks.id", intval($task_id))->first();
// //
@ -1136,11 +1199,11 @@ class ProjectTask extends AbstractModel
} }
} }
// //
if ($mustOwner === 2) { if ($permission === 2) {
$mustOwner = $task->hasOwner() ? 1 : 0; $permission = $task->hasOwner() ? 1 : 0;
} }
if (($mustOwner === 1 || $mustOwner === true) && !$task->isOwner() && !$project->owner) { if (($permission === 1 || $permission === true) && !$project->owner && !$task->permission(3)) {
throw new ApiException('仅限项目或任务负责人操作'); throw new ApiException('仅限项目负责人、任务负责人、协助人员或任务创建者操作');
} }
// //
return $task; return $task;

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

@ -11,6 +11,7 @@ use App\Module\Base;
* @property int|null $project_id 项目ID * @property int|null $project_id 项目ID
* @property int|null $userid 成员ID * @property int|null $userid 成员ID
* @property int|null $owner 是否负责人 * @property int|null $owner 是否负责人
* @property string|null $top_at 置顶时间
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Project|null $project * @property-read \App\Models\Project|null $project
@ -21,6 +22,7 @@ use App\Module\Base;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereId($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereOwner($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 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 whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereUserid($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereUserid($value)
* @mixin \Eloquent * @mixin \Eloquent

View File

@ -181,7 +181,7 @@ class User extends AbstractModel
public static function reg($email, $password, $other = []) public static function reg($email, $password, $other = [])
{ {
//邮箱 //邮箱
if (!Base::isMail($email)) { if (!Base::isEmail($email)) {
throw new ApiException('请输入正确的邮箱地址'); throw new ApiException('请输入正确的邮箱地址');
} }
if (User::email2userid($email) > 0) { if (User::email2userid($email) > 0) {

View File

@ -70,11 +70,7 @@ class WebSocketDialogMsg extends AbstractModel
public function getPercentageAttribute() public function getPercentageAttribute()
{ {
if (!isset($this->appendattrs['percentage'])) { if (!isset($this->appendattrs['percentage'])) {
if ($this->read > $this->send || empty($this->send)) { $this->generatePercentage();
$this->appendattrs['percentage'] = 100;
} else {
$this->appendattrs['percentage'] = intval($this->read / $this->send * 100);
}
} }
return $this->appendattrs['percentage']; return $this->appendattrs['percentage'];
} }
@ -98,6 +94,22 @@ class WebSocketDialogMsg extends AbstractModel
return $value; 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 * @param $userid
@ -127,13 +139,17 @@ class WebSocketDialogMsg extends AbstractModel
if (!$msgRead->read_at) { if (!$msgRead->read_at) {
$msgRead->read_at = Carbon::now(); $msgRead->read_at = Carbon::now();
$msgRead->save(); $msgRead->save();
$this->increment('read'); $this->generatePercentage(true);
PushTask::push([ PushTask::push([
'userid' => $this->userid, 'userid' => $this->userid,
'msg' => [ 'msg' => [
'type' => 'dialog', 'type' => 'dialog',
'mode' => 'update', 'mode' => 'readed',
'data' => $this->toArray(), 'data' => [
'id' => $this->id,
'read' => $this->read,
'percentage' => $this->percentage,
],
] ]
]); ]);
} }

View File

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

View File

@ -2,6 +2,7 @@
namespace App\Module; namespace App\Module;
use App\Exceptions\ApiException;
use App\Models\Setting; use App\Models\Setting;
use App\Models\Tmp; use App\Models\Tmp;
use Cache; use Cache;
@ -87,6 +88,18 @@ class Base
return $_A["__static_client_version"]; 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 * @param $domain
@ -329,19 +342,15 @@ class Base
{ {
if (strtolower($charset) == 'utf-8') { if (strtolower($charset) == 'utf-8') {
if (Base::getStrlen($string) <= $length) return $string; if (Base::getStrlen($string) <= $length) return $string;
$strcut = str_replace(array('&amp;', '&quot;', '&lt;', '&gt;'), array('&', '"', '<', '>'), $string); $strcut = Base::utf8Substr($string, $length, $start);
$strcut = Base::utf8Substr($strcut, $length, $start);
$strcut = str_replace(array('&', '"', '<', '>'), array('&amp;', '&quot;', '&lt;', '&gt;'), $strcut);
return $strcut . $dot; return $strcut . $dot;
} else { } else {
$length = $length * 2; $length = $length * 2;
if (strlen($string) <= $length) return $string; if (strlen($string) <= $length) return $string;
$string = str_replace(array('&amp;', '&quot;', '&lt;', '&gt;'), array('&', '"', '<', '>'), $string);
$strcut = ''; $strcut = '';
for ($i = 0; $i < $length; $i++) { for ($i = 0; $i < $length; $i++) {
$strcut .= ord($string[$i]) > 127 ? $string[$i] . $string[++$i] : $string[$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; return $strcut . $dot;
} }
@ -799,6 +808,31 @@ class Base
return $scheme.($_SERVER['HTTP_HOST'] ?? ''); 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 * @param $content
@ -954,7 +988,7 @@ class Base
* @param string $str 需要检测的字符串 * @param string $str 需要检测的字符串
* @return int * @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'; $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); return preg_match($RegExp, $str);
@ -2246,6 +2280,9 @@ class Base
break; break;
case 'more': case 'more':
$type = [ $type = [
'text', 'md', 'markdown',
'drawio',
'mind',
'docx', 'wps', 'doc', 'xls', 'xlsx', 'ppt', 'pptx', 'docx', 'wps', 'doc', 'xls', 'xlsx', 'ppt', 'pptx',
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw',
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z', 'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z',
@ -2256,7 +2293,7 @@ class Base
'txt', 'txt',
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css', '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', 'dockerfile', 'go', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
'ocamlmakefile', 'make', 'md', 'markdown', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php', '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', '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', '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', 'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
@ -2386,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 源图绝对完整地址{带文件名及后缀名} * @param string $src_img 源图绝对完整地址{带文件名及后缀名}
@ -2895,4 +2963,19 @@ class Base
$matrix = array_unique($matrix, SORT_REGULAR); $matrix = array_unique($matrix, SORT_REGULAR);
return array_merge($matrix); 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

@ -127,9 +127,11 @@ class WebSocketService implements WebSocketHandlerInterface
case 'readMsg': case 'readMsg':
$ids = is_array($data['id']) ? $data['id'] : [$data['id']]; $ids = is_array($data['id']) ? $data['id'] : [$data['id']];
$userid = $this->getUserid($frame->fd); $userid = $this->getUserid($frame->fd);
$list = WebSocketDialogMsg::whereIn('id', $ids)->get(); WebSocketDialogMsg::whereIn('id', $ids)->chunkById(20, function($list) use ($userid) {
$list->transform(function(WebSocketDialogMsg $item) use ($userid) { /** @var WebSocketDialogMsg $item */
$item->readSuccess($userid); foreach ($list as $item) {
$item->readSuccess($userid);
}
}); });
return; return;

47
cmd
View File

@ -13,6 +13,7 @@ Error="${Red}[错误]${Font}"
cur_path="$(pwd)" cur_path="$(pwd)"
cur_arg=$@ cur_arg=$@
COMPOSE="docker-compose"
judge() { judge() {
if [[ 0 -eq $? ]]; then if [[ 0 -eq $? ]]; then
@ -55,15 +56,24 @@ check_docker() {
echo -e "${Error} ${RedBG} 未安装 Docker${Font}" echo -e "${Error} ${RedBG} 未安装 Docker${Font}"
exit 1 exit 1
fi fi
docker-compose --version &> /dev/null docker-compose version &> /dev/null
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo -e "${Error} ${RedBG} 未安装 Docker-compose${Font}" docker compose version &> /dev/null
if [ $? -ne 0 ]; then
echo -e "${Error} ${RedBG} 未安装 Docker-compose${Font}"
exit 1
fi
COMPOSE="docker compose"
fi
if [[ -n `$COMPOSE version | grep -E "\sv*1"` ]]; then
$COMPOSE version
echo -e "${Error} ${RedBG} Docker-compose 版本过低请升级至v2+${Font}"
exit 1 exit 1
fi fi
} }
check_node() { check_node() {
npm --version > /dev/null npm --version &> /dev/null
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo -e "${Error} ${RedBG} 未安装nodejs${Font}" echo -e "${Error} ${RedBG} 未安装nodejs${Font}"
exit 1 exit 1
@ -71,7 +81,7 @@ check_node() {
} }
docker_name() { docker_name() {
echo `docker-compose ps | awk '{print $1}' | grep "\-$1\-"` echo `$COMPOSE ps | awk '{print $1}' | grep "\-$1\-"`
} }
run_compile() { run_compile() {
@ -255,16 +265,19 @@ if [ $# -gt 0 ]; then
exit 1 exit 1
fi fi
# 初始化文件 # 初始化文件
rm -rf composer.lock if [[ -n "$(arg_get relock)" ]]; then
rm -rf package-lock.json rm -rf node_modules
rm -rf package-lock.json
rm -rf vendor
rm -rf composer.lock
fi
mkdir -p "${cur_path}/docker/log/supervisor" mkdir -p "${cur_path}/docker/log/supervisor"
mkdir -p "${cur_path}/docker/mysql/data" mkdir -p "${cur_path}/docker/mysql/data"
chmod -R 775 "${cur_path}/docker/log/supervisor" chmod -R 775 "${cur_path}/docker/log/supervisor"
chmod -R 775 "${cur_path}/docker/mysql/data" chmod -R 775 "${cur_path}/docker/mysql/data"
# 启动容器 # 启动容器
[[ "$(arg_get port)" -gt 0 ]] && env_set APP_PORT "$(arg_get port)" [[ "$(arg_get port)" -gt 0 ]] && env_set APP_PORT "$(arg_get port)"
docker-compose up -d $COMPOSE up php -d
docker-compose restart php
# 安装composer依赖 # 安装composer依赖
run_exec php "composer install" run_exec php "composer install"
if [ ! -f "${cur_path}/vendor/autoload.php" ]; then if [ ! -f "${cur_path}/vendor/autoload.php" ]; then
@ -292,8 +305,8 @@ if [ $# -gt 0 ]; then
run_exec php "php artisan migrate --seed" run_exec php "php artisan migrate --seed"
# 设置初始化密码 # 设置初始化密码
res=`run_exec mariadb "sh /etc/mysql/repassword.sh"` res=`run_exec mariadb "sh /etc/mysql/repassword.sh"`
docker-compose stop $COMPOSE up -d
docker-compose start supervisorctl_restart php
echo -e "${OK} ${GreenBG} 安装完成 ${Font}" echo -e "${OK} ${GreenBG} 安装完成 ${Font}"
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}" echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
echo -e "$res" echo -e "$res"
@ -306,7 +319,7 @@ if [ $# -gt 0 ]; then
run_exec php "composer update" run_exec php "composer update"
run_exec php "php artisan migrate" run_exec php "php artisan migrate"
supervisorctl_restart php supervisorctl_restart php
docker-compose up -d $COMPOSE up -d
elif [[ "$1" == "uninstall" ]]; then elif [[ "$1" == "uninstall" ]]; then
shift 1 shift 1
read -rp "确定要卸载(含:删除容器、数据库、日志)吗?(y/n): " uninstall read -rp "确定要卸载(含:删除容器、数据库、日志)吗?(y/n): " uninstall
@ -320,7 +333,7 @@ if [ $# -gt 0 ]; then
exit 2 exit 2
;; ;;
esac esac
docker-compose down $COMPOSE down
rm -rf "./docker/mysql/data" rm -rf "./docker/mysql/data"
rm -rf "./docker/log/supervisor" rm -rf "./docker/log/supervisor"
find "./storage/logs" -name "*.log" | xargs rm -rf find "./storage/logs" -name "*.log" | xargs rm -rf
@ -333,7 +346,7 @@ if [ $# -gt 0 ]; then
elif [[ "$1" == "port" ]]; then elif [[ "$1" == "port" ]]; then
shift 1 shift 1
env_set APP_PORT "$1" env_set APP_PORT "$1"
docker-compose up -d $COMPOSE up -d
echo -e "${OK} ${GreenBG} 修改成功 ${Font}" echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}" echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
elif [[ "$1" == "repassword" ]]; then elif [[ "$1" == "repassword" ]]; then
@ -406,11 +419,11 @@ if [ $# -gt 0 ]; then
e="./vendor/bin/phpunit $@" && run_exec php "$e" e="./vendor/bin/phpunit $@" && run_exec php "$e"
elif [[ "$1" == "restart" ]]; then elif [[ "$1" == "restart" ]]; then
shift 1 shift 1
docker-compose stop "$@" $COMPOSE stop "$@"
docker-compose start "$@" $COMPOSE start "$@"
else else
docker-compose "$@" $COMPOSE "$@"
fi fi
else else
docker-compose ps $COMPOSE ps
fi 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->bigIncrements('id');
$table->bigInteger('project_id')->nullable()->default(0)->comment('项目ID'); $table->bigInteger('project_id')->nullable()->default(0)->comment('项目ID');
$table->bigInteger('column_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->bigInteger('userid')->nullable()->default(0)->comment('会员ID');
$table->string('detail', 500)->nullable()->default('')->comment('详细信息'); $table->string('detail', 500)->nullable()->default('')->comment('详细信息');
$table->timestamps(); $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 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

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

View File

@ -2,6 +2,10 @@
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\File;
use App\Models\FileContent;
use App\Module\Base;
use Carbon\Carbon;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
class FilesTableSeeder extends Seeder class FilesTableSeeder extends Seeder
@ -203,7 +207,7 @@ class FilesTableSeeder extends Seeder
'pid' => 0, 'pid' => 0,
'cid' => 0, 'cid' => 0,
'name' => '流程图', 'name' => '流程图',
'type' => 'flow', 'type' => 'drawio',
'ext' => '', 'ext' => '',
'size' => 5418, 'size' => 5418,
'userid' => 1, 'userid' => 1,
@ -280,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();
}
});
} }
} }

File diff suppressed because one or more lines are too long

View File

@ -45,6 +45,8 @@ services:
- php - php
- office - office
- fileview - fileview
- drawio-webapp
- drawio-export
restart: unless-stopped restart: unless-stopped
redis: redis:
@ -80,7 +82,7 @@ services:
office: office:
container_name: "dootask-office-${APP_ID}" container_name: "dootask-office-${APP_ID}"
image: "onlyoffice/documentserver:6.4.2.6" image: "onlyoffice/documentserver:7.0.0.132"
volumes: volumes:
- ./docker/office/data:/var/www/onlyoffice/Data - ./docker/office/data:/var/www/onlyoffice/Data
- ./docker/office/logs:/var/log/onlyoffice - ./docker/office/logs:/var/log/onlyoffice
@ -108,6 +110,36 @@ services:
ipv4_address: "${APP_IPPR}.7" ipv4_address: "${APP_IPPR}.7"
restart: unless-stopped 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: networks:
extnetwork: extnetwork:
name: "dootask-networks-${APP_ID}" name: "dootask-networks-${APP_ID}"

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -18,14 +18,6 @@ upstream service {
server php:20000 weight=5 max_fails=3 fail_timeout=30s; server php:20000 weight=5 max_fails=3 fail_timeout=30s;
keepalive 16; keepalive 16;
} }
upstream office {
server office weight=5 max_fails=3 fail_timeout=30s;
keepalive 16;
}
upstream fileview {
server fileview:8012 weight=5 max_fails=3 fail_timeout=30s;
keepalive 16;
}
server { server {
listen 80; listen 80;
@ -85,6 +77,23 @@ server {
proxy_pass http://service; proxy_pass http://service;
} }
location /fileview {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Forwarded-Host $the_host;
proxy_set_header X-Forwarded-Proto $the_scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header Server-Protocol $server_protocol;
proxy_set_header Server-Name $server_name;
proxy_set_header Server-Addr $server_addr;
proxy_set_header Server-Port $server_port;
proxy_pass http://fileview:8012;
}
location /office/ { location /office/ {
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
@ -103,12 +112,11 @@ server {
proxy_pass http://office/; proxy_pass http://office/;
} }
location /fileview { location /drawio/webapp/ {
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-PORT $remote_port; proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Forwarded-Host $the_host; proxy_set_header X-Forwarded-Host $the_host/drawio/webapp;
proxy_set_header X-Forwarded-Proto $the_scheme; proxy_set_header X-Forwarded-Proto $the_scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
@ -117,7 +125,27 @@ server {
proxy_set_header Server-Name $server_name; proxy_set_header Server-Name $server_name;
proxy_set_header Server-Addr $server_addr; proxy_set_header Server-Addr $server_addr;
proxy_set_header Server-Port $server_port; proxy_set_header Server-Port $server_port;
proxy_pass http://fileview; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://drawio-webapp:8080/;
}
location /drawio/export/ {
proxy_http_version 1.1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Forwarded-Host $the_host/drawio/export;
proxy_set_header X-Forwarded-Proto $the_scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header Server-Protocol $server_protocol;
proxy_set_header Server-Name $server_name;
proxy_set_header Server-Addr $server_addr;
proxy_set_header Server-Port $server_port;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://drawio-export:8000/;
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@ command=php bin/laravels start -i
numprocs=1 numprocs=1
autostart=true autostart=true
autorestart=true autorestart=true
startretries=3 startretries=100
user=root user=root
redirect_stderr=true redirect_stderr=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log stdout_logfile=/var/log/supervisor/%(program_name)s.log

27
electron/build.js vendored
View File

@ -15,19 +15,42 @@ const packageFile = path.resolve(__dirname, "package.json");
const packageBakFile = path.resolve(__dirname, "package-bak.json"); const packageBakFile = path.resolve(__dirname, "package-bak.json");
const platform = ["build-mac", "build-mac-arm", "build-win"]; const platform = ["build-mac", "build-mac-arm", "build-win"];
// 克隆 Drawio
function cloneDrawio(systemInfo) {
child_process.spawnSync("git", ["submodule", "update", "--quiet", "--init", "--depth=1"], {stdio: "inherit"});
const drawioSrcDir = path.resolve(__dirname, "../resources/drawio/src/main/webapp");
const drawioCoverDir = path.resolve(__dirname, "../docker/drawio/webapp");
const drawioDestDir = path.resolve(electronDir, "drawio/webapp");
fse.copySync(drawioSrcDir, drawioDestDir)
fse.copySync(drawioCoverDir, drawioDestDir)
//
const preConfigFile = path.resolve(drawioDestDir, "js/PreConfig.js");
if (!fse.existsSync(preConfigFile)) {
console.log("clone drawio error!");
process.exit()
}
let preConfigString = fs.readFileSync(preConfigFile, 'utf8');
preConfigString += "\nwindow.systemInfo = " + JSON.stringify(systemInfo) + ";\n";
preConfigString += fs.readFileSync(path.resolve(__dirname, "drawio.js"), 'utf8');
fs.writeFileSync(preConfigFile, preConfigString, 'utf8');
}
// 生成配置、编译应用 // 生成配置、编译应用
function startBuild(data, publish) { function startBuild(data, publish) {
// information
console.log("Name: " + data.name); console.log("Name: " + data.name);
console.log("AppId: " + data.id); console.log("AppId: " + data.id);
console.log("Version: " + config.version); console.log("Version: " + config.version);
// config.js
let systemInfo = { let systemInfo = {
title: data.name, title: data.name,
version: config.version, version: config.version,
origin: "./", origin: "./",
apiUrl: utils.formatUrl(data.url) + "api/", apiUrl: utils.formatUrl(data.url) + "api/",
} }
fs.writeFileSync(electronDir + "/config.js", "window.systemInfo = " + JSON.stringify(systemInfo, null, 2), 'utf8'); // drawio
cloneDrawio(systemInfo)
// config.js
fs.writeFileSync(electronDir + "/config.js", "window.systemInfo = " + JSON.stringify(systemInfo), 'utf8');
fs.writeFileSync(nativeCachePath, utils.formatUrl(data.url)); fs.writeFileSync(nativeCachePath, utils.formatUrl(data.url));
fs.writeFileSync(devloadCachePath, "", 'utf8'); fs.writeFileSync(devloadCachePath, "", 'utf8');
// index.html // index.html

11
electron/drawio.js vendored Normal file
View File

@ -0,0 +1,11 @@
window.utilsStorage={getStorageString(key,def=''){let value=this.storage(key);return typeof value==="string"||typeof value==="number"?value:def},storage(key,value){if(!key){return}let keyName='__state__';if(key.substring(0,5)==='cache'){keyName='__state:'+key+'__'}if(typeof value==='undefined'){return this.loadFromlLocal(key,'',keyName)}else{this.savaToLocal(key,value,keyName)}},savaToLocal(key,value,keyName){try{if(typeof keyName==='undefined')keyName='__seller__';let seller=window.localStorage[keyName];if(!seller){seller={}}else{seller=JSON.parse(seller)}seller[key]=value;window.localStorage[keyName]=JSON.stringify(seller)}catch(e){}},loadFromlLocal(key,def,keyName){try{if(typeof keyName==='undefined')keyName='__seller__';let seller=window.localStorage[keyName];if(!seller){return def}seller=JSON.parse(seller);if(!seller||typeof seller[key]==='undefined'){return def}return seller[key]}catch(e){return def}},}
window.cacheServerUrl = window.utilsStorage.getStorageString("cacheServerUrl")
if (window.cacheServerUrl) {
window.systemInfo.apiUrl = window.cacheServerUrl
}
window.EXPORT_URL = window.systemInfo.apiUrl + "../drawio/export/";
window.DRAWIO_LIGHTBOX_URL = window.systemInfo.apiUrl + "../drawio/webapp";
while (window.EXPORT_URL.indexOf("/../") !== -1) {window.EXPORT_URL = window.EXPORT_URL.replace(/\/(((?!\/).)*)\/\.\.\//, "/")}
while (window.DRAWIO_LIGHTBOX_URL.indexOf("/../") !== -1) {window.DRAWIO_LIGHTBOX_URL = window.DRAWIO_LIGHTBOX_URL.replace(/\/(((?!\/).)*)\/\.\.\//, "/")}

67
electron/electron-preload.js vendored Normal file
View File

@ -0,0 +1,67 @@
const {
contextBridge,
ipcRenderer
} = require("electron");
let reqId = 1;
let reqInfo = {};
let fileChangedListeners = {};
ipcRenderer.on('mainResp', (event, resp) => {
let callbacks = reqInfo[resp.reqId];
if (resp.error) {
callbacks.error(resp.msg, resp.e);
} else {
callbacks.callback(resp.data);
}
delete reqInfo[resp.reqId];
});
ipcRenderer.on('fileChanged', (event, resp) => {
let listener = fileChangedListeners[resp.path];
if (listener) {
listener(resp.curr, resp.prev);
}
});
contextBridge.exposeInMainWorld(
'electron', {
request: (msg, callback, error) => {
msg.reqId = reqId++;
reqInfo[msg.reqId] = {callback: callback, error: error};
if (msg.action == 'watchFile') {
fileChangedListeners[msg.path] = msg.listener;
delete msg.listener;
}
ipcRenderer.send('rendererReq', msg);
},
registerMsgListener: (action, callback) => {
ipcRenderer.on(action, (event, args) => {
callback(args);
});
},
listenOnce: (action, callback) => {
ipcRenderer.once(action, (event, args) => {
callback(args);
});
},
sendMessage: (action, args) => {
ipcRenderer.send(action, args);
},
sendSyncMessage: (action, args) => {
ipcRenderer.sendSync(action, args);
}
}
);
contextBridge.exposeInMainWorld(
'process', {
type: process.type,
versions: process.versions
}
);

1199
electron/electron.js vendored Normal file

File diff suppressed because it is too large Load Diff

423
electron/main.js vendored
View File

@ -1,423 +0,0 @@
const fs = require('fs')
const fse = require('fs-extra')
const os = require("os");
const path = require('path')
const XLSX = require('xlsx');
const {app, BrowserWindow, ipcMain, dialog} = require('electron')
const utils = require('./utils');
const config = require('./package.json');
const log = require("electron-log");
let mainWindow = null,
subWindow = [],
willQuitApp = false,
inheritClose = false,
devloadUrl = "",
devloadCachePath = path.resolve(__dirname, ".devload"),
downloadList = [],
downloadCacheFile = path.join(app.getPath('cache'), config.name, '.downloadCache');
if (fs.existsSync(devloadCachePath)) {
devloadUrl = fs.readFileSync(devloadCachePath, 'utf8')
}
if (fs.existsSync(downloadCacheFile)) {
downloadList = utils.jsonParse(fs.readFileSync(downloadCacheFile, 'utf8'), [])
} else {
fse.ensureDirSync(path.join(app.getPath('cache'), config.name))
}
function downloadUpdate(item) {
const chain = item.getURLChain()
if (chain.length == 0) {
return
}
let currentState = item.getState()
if (currentState == "progressing" && item.isPaused()) {
currentState = "paused"
}
//
const downloadItem = downloadList.find(item => item.url == chain[0])
if (downloadItem && downloadItem.state != currentState) {
downloadItem.state = currentState;
downloadItem.result = {
url: item.getURL(),
name: item.getFilename(),
savePath: item.getSavePath(),
mimeType: item.getMimeType(),
totalBytes: item.getTotalBytes(),
chain,
};
fs.writeFileSync(downloadCacheFile, utils.jsonStringify(downloadList), 'utf8');
//
if (currentState == 'completed') {
mainWindow.webContents.send("downloadDone", downloadItem)
log.info("下载完成", downloadItem)
} else {
mainWindow.webContents.send("downloadUpdate", downloadItem)
log.info("下载更新", downloadItem)
}
}
}
function createMainWindow() {
mainWindow = new BrowserWindow({
width: 1280,
height: 800,
center: true,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
contextIsolation: false
}
})
mainWindow.webContents.setUserAgent(mainWindow.webContents.getUserAgent() + " MainTaskWindow/" + process.platform + "/" + os.arch() + "/1.0");
if (devloadUrl) {
mainWindow.loadURL(devloadUrl).then(r => {
})
} else {
mainWindow.loadFile('./public/index.html').then(r => {
})
}
mainWindow.on('page-title-updated', (event, title) => {
if (title == "index.html") {
event.preventDefault()
}
})
mainWindow.on('close', (e) => {
if (!willQuitApp) {
e.preventDefault();
if (inheritClose) {
mainWindow.webContents.send("windowClose", {})
} else {
if (process.platform === 'darwin') {
app.hide();
} else {
app.quit();
}
}
}
})
mainWindow.webContents.session.on('will-download', (event, item) => {
item.setSavePath(path.join(app.getPath('cache'), config.name, item.getFilename()));
item.on('updated', () => {
downloadUpdate(item)
})
item.on('done', () => {
downloadUpdate(item)
})
})
}
function createSubWindow(args) {
if (!args) {
return;
}
if (typeof args !== "object") {
args = {
path: args,
config: {},
}
}
let name = args.name || "auto_" + utils.randomString(6);
let item = subWindow.find(item => item.name == name);
let browser = item ? item.browser : null;
if (browser) {
browser.focus();
if (args.force === false) {
return;
}
} else {
let config = args.config || {};
if (typeof args.title !== "undefined") {
config.title = args.title;
}
browser = new BrowserWindow(Object.assign({
width: 1280,
height: 800,
center: true,
parent: mainWindow,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
devTools: args.devTools !== false,
nodeIntegration: true,
contextIsolation: false
}
}, config))
browser.on('page-title-updated', (event, title) => {
if (title == "index.html" || args.titleFixed === true) {
event.preventDefault()
}
})
browser.on('close', () => {
let index = subWindow.findIndex(item => item.name == name);
if (index > -1) {
subWindow.splice(index, 1)
}
})
subWindow.push({ name, browser })
}
browser.webContents.setUserAgent(browser.webContents.getUserAgent() + " SubTaskWindow/" + process.platform + "/" + os.arch() + "/1.0" + (args.userAgent ? (" " + args.userAgent) : ""));
if (devloadUrl) {
browser.loadURL(devloadUrl + '#' + (args.hash || args.path)).then(r => {
})
} else {
browser.loadFile('./public/index.html', {
hash: args.hash || args.path
}).then(r => {
})
}
}
app.whenReady().then(() => {
createMainWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createMainWindow()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('before-quit', () => {
willQuitApp = true
})
/**
* 继承关闭窗口事件
*/
ipcMain.on('inheritClose', (event) => {
inheritClose = true
event.returnValue = "ok"
})
/**
* 下载文件
* @param args {url}
*/
ipcMain.on('downloadFile', (event, args) => {
event.returnValue = "ok"
//
let appendJson = {state: "progressing", startTime: utils.Time()}
let downloadItem = downloadList.find(({url}) => url == args.url)
if (downloadItem) {
switch (downloadItem.state) {
case "completed":
if (fs.existsSync(downloadItem.result.savePath)) { // 下载完成,文件存在
log.info("下载已完成", downloadItem)
mainWindow.webContents.send("downloadDone", downloadItem)
return
}
break;
case "progressing":
if (downloadItem.startTime + 480 > utils.Time()) { // 下载中未超时超时时间8分钟
log.info("下载已存在", downloadItem)
return;
}
break;
}
downloadItem = Object.assign(downloadItem, appendJson)
} else {
downloadList.push(downloadItem = Object.assign(args, appendJson))
}
fs.writeFileSync(downloadCacheFile, utils.jsonStringify(downloadList), 'utf8');
mainWindow.webContents.downloadURL(downloadItem.url);
log.info("下载开始", downloadItem)
})
/**
* 打开文件
* @param args {path}
*/
ipcMain.on('openFile', (event, args) => {
utils.openFile(args.path)
event.returnValue = "ok"
})
/**
* 退出客户端
*/
ipcMain.on('windowQuit', (event) => {
event.returnValue = "ok"
app.quit();
})
/**
* 创建路由窗口
* @param args {path, ?}
*/
ipcMain.on('windowRouter', (event, args) => {
createSubWindow(args)
event.returnValue = "ok"
})
/**
* 隐藏窗口mac隐藏其他关闭
*/
ipcMain.on('windowHidden', (event) => {
if (process.platform === 'darwin') {
app.hide();
} else {
app.quit();
}
event.returnValue = "ok"
})
/**
* 关闭窗口
*/
ipcMain.on('windowClose', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
win.close()
event.returnValue = "ok"
})
/**
* 设置窗口尺寸
* @param args {width, height, autoZoom, minWidth, minHeight, maxWidth, maxHeight}
*/
ipcMain.on('windowSize', (event, args) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win) {
if (args.width || args.height) {
let [w, h] = win.getSize()
const width = args.width || w
const height = args.height || h
win.setSize(width, height, args.animate === true)
//
if (args.autoZoom === true) {
let move = false
let [x, y] = win.getPosition()
if (Math.abs(width - w) > 10) {
move = true
x -= (width - w) / 2
}
if (Math.abs(height - h) > 10) {
move = true
y -= (height - h) / 2
}
if (move) {
win.setPosition(Math.max(0, Math.floor(x)), Math.max(0, Math.floor(y)))
}
}
}
if (args.minWidth || args.minHeight) {
win.setMinimumSize(args.minWidth || win.getMinimumSize()[0], args.minHeight || win.getMinimumSize()[1])
}
if (args.maxWidth || args.maxHeight) {
win.setMaximumSize(args.maxWidth || win.getMaximumSize()[0], args.maxHeight || win.getMaximumSize()[1])
}
}
event.returnValue = "ok"
})
/**
* 设置窗口最小尺寸
* @param args {minWidth, minHeight}
*/
ipcMain.on('windowMinSize', (event, args) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win) {
win.setMinimumSize(args.minWidth || win.getMinimumSize()[0], args.minHeight || win.getMinimumSize()[1])
}
event.returnValue = "ok"
})
/**
* 设置窗口最大尺寸
* @param args {maxWidth, maxHeight}
*/
ipcMain.on('windowMaxSize', (event, args) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win) {
win.setMaximumSize(args.maxWidth || win.getMaximumSize()[0], args.maxHeight || win.getMaximumSize()[1])
}
event.returnValue = "ok"
})
/**
* 窗口居中
*/
ipcMain.on('windowCenter', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win) {
win.center();
}
event.returnValue = "ok"
})
/**
* 窗口最大化或恢复
*/
ipcMain.on('windowMax', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win.isMaximized()) {
win.restore();
} else {
win.maximize();
}
event.returnValue = "ok"
})
/**
* 给主窗口发送信息
* @param args {channel, data}
*/
ipcMain.on('sendForwardMain', (event, args) => {
if (mainWindow) {
mainWindow.webContents.send(args.channel, args.data)
}
event.returnValue = "ok"
})
/**
* 设置Dock标记
* @param args
*/
ipcMain.on('setDockBadge', (event, args) => {
if(process.platform !== 'darwin'){
// Mac only
return;
}
if (utils.runNum(args) > 0) {
app.dock.setBadge(String(args))
} else {
app.dock.setBadge("")
}
event.returnValue = "ok"
})
/**
* 保存sheets
*/
ipcMain.on('saveSheet', (event, data, filename, opts) => {
const EXTENSIONS = "xls|xlsx|xlsm|xlsb|xml|csv|txt|dif|sylk|slk|prn|ods|fods|htm|html".split("|");
dialog.showSaveDialog({
title: 'Save file as',
defaultPath: filename,
filters: [{
name: "Spreadsheets",
extensions: EXTENSIONS
}]
}).then(o => {
XLSX.writeFile(data, o.filePath, opts);
});
event.returnValue = "ok"
})

View File

@ -1,8 +1,8 @@
{ {
"name": "DooTask", "name": "DooTask",
"version": "0.7.94", "version": "0.10.5",
"description": "DooTask is task management system.", "description": "DooTask is task management system.",
"main": "main.js", "main": "electron.js",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"start": "electron-forge start", "start": "electron-forge start",
@ -28,30 +28,31 @@
"url": "https://github.com/kuaifan/dootask.git" "url": "https://github.com/kuaifan/dootask.git"
}, },
"devDependencies": { "devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.61", "@electron-forge/cli": "^6.0.0-beta.63",
"@electron-forge/maker-deb": "^6.0.0-beta.61", "@electron-forge/maker-deb": "^6.0.0-beta.63",
"@electron-forge/maker-rpm": "^6.0.0-beta.61", "@electron-forge/maker-rpm": "^6.0.0-beta.63",
"@electron-forge/maker-squirrel": "^6.0.0-beta.61", "@electron-forge/maker-squirrel": "^6.0.0-beta.63",
"@electron-forge/maker-zip": "^6.0.0-beta.61", "@electron-forge/maker-zip": "^6.0.0-beta.63",
"dmg-license": "^1.0.10", "dmg-license": "^1.0.10",
"dotenv": "^10.0.0", "dotenv": "^16.0.0",
"electron": "^16.0.5", "electron": "^17.0.1",
"electron-builder": "^22.14.5" "electron-builder": "^22.14.13"
}, },
"dependencies": { "dependencies": {
"axios": "^0.24.0", "axios": "^0.26.0",
"crc": "^3.8.0",
"electron-squirrel-startup": "^1.0.0", "electron-squirrel-startup": "^1.0.0",
"electron-log": "^4.4.3", "electron-log": "^4.4.6",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"xlsx": "^0.17.2" "pdf-lib": "^1.16.0"
}, },
"build": { "build": {
"appId": "com.dootask.task", "appId": "com.dootask.task",
"artifactName": "${productName}-v${version}-${os}-${arch}.${ext}", "artifactName": "${productName}-v${version}-${os}-${arch}.${ext}",
"files": [ "files": [
"public/**/*", "public/**/*",
"main.js", "electron-preload.js",
"preload.js", "electron.js",
"utils.js" "utils.js"
], ],
"mac": { "mac": {

14
electron/preload.js vendored
View File

@ -1,14 +0,0 @@
// preload.js
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const dependency of ['chrome', 'node', 'electron']) {
replaceText(`${dependency}-version`, process.versions[dependency])
}
})

37
electron/utils.js vendored
View File

@ -1,5 +1,5 @@
const fs = require("fs"); const fs = require("fs");
const {shell} = require("electron"); const {shell, dialog} = require("electron");
module.exports = { module.exports = {
/** /**
@ -269,4 +269,39 @@ module.exports = {
} }
return Math.round(time / 1000) return Math.round(time / 1000)
}, },
/**
* 窗口关闭事件
* @param event
* @param app
*/
onBeforeUnload(event, app) {
const sender = event.sender
const contents = sender.webContents
if (contents != null) {
const destroy = () => {
if (typeof app === "undefined") {
sender.destroy()
} else {
if (process.platform === 'darwin') {
app.hide()
} else {
app.quit()
}
}
}
contents.executeJavaScript('if(typeof window.__onBeforeUnload === \'function\'){window.__onBeforeUnload()}', true).then(options => {
if (this.isJson(options)) {
let choice = dialog.showMessageBoxSync(sender, options)
if (choice === 1) {
contents.executeJavaScript('if(typeof window.__removeBeforeUnload === \'function\'){window.__removeBeforeUnload()}', true).catch(() => {});
destroy()
}
} else if (options !== true) {
destroy()
}
})
event.preventDefault()
}
},
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "DooTask", "name": "DooTask",
"version": "0.7.94", "version": "0.10.5",
"description": "DooTask is task management system.", "description": "DooTask is task management system.",
"scripts": { "scripts": {
"start": "./cmd dev", "start": "./cmd dev",
@ -41,7 +41,6 @@
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^6.5.1", "css-loader": "^6.5.1",
"echarts": "^5.2.2", "echarts": "^5.2.2",
"electron": "^16.0.5",
"element-ui": "^2.15.6", "element-ui": "^2.15.6",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"inquirer": "^8.2.0", "inquirer": "^8.2.0",
@ -54,7 +53,7 @@
"less-loader": "^10.2.0", "less-loader": "^10.2.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.29.1", "moment": "^2.29.1",
"node-sass": "^4.11.0", "node-sass": "^6.0.1",
"notification-koro1": "^1.1.1", "notification-koro1": "^1.1.1",
"postcss": "^8.4.5", "postcss": "^8.4.5",
"resolve-url-loader": "^4.0.0", "resolve-url-loader": "^4.0.0",
@ -62,9 +61,9 @@
"sass-loader": "^12.4.0", "sass-loader": "^12.4.0",
"stylus": "^0.56.0", "stylus": "^0.56.0",
"stylus-loader": "^6.2.0", "stylus-loader": "^6.2.0",
"tinymce": "^5.10.2", "tinymce": "^5.10.3",
"tui-calendar-hi": "^1.15.1-5", "tui-calendar-hi": "^1.15.1-5",
"view-design-hi": "^4.7.0-12", "view-design-hi": "^4.7.0-16",
"vue": "^2.6.14", "vue": "^2.6.14",
"vue-clipboard2": "^0.3.3", "vue-clipboard2": "^0.3.3",
"vue-emoji-picker": "^1.0.3", "vue-emoji-picker": "^1.0.3",
@ -75,8 +74,7 @@
"vue-template-compiler": "^2.6.14", "vue-template-compiler": "^2.6.14",
"vuedraggable": "^2.24.3", "vuedraggable": "^2.24.3",
"vuex": "^3.6.2", "vuex": "^3.6.2",
"webpack": "^5.65.0", "webpack": "^5.69.1",
"webpack-cli": "^4.9.1", "webpack-cli": "^4.9.2"
"xlsx": "^0.17.4"
} }
} }

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

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