Compare commits

...

1516 Commits

Author SHA1 Message Date
kuaifan
b2b2efc9a8 no message 2022-09-06 16:52:07 +08:00
kuaifan
2347d7c7b7 fix: 聊天、任务中的md文件预览无法滚动 2022-09-06 16:50:13 +08:00
kuaifan
03feb01af4 fix: 修改工作报告弹出多次成功提示的问题 2022-09-06 16:20:16 +08:00
kuaifan
eb6fa0cf63 fix: 安装数据库初始化失败 2022-09-06 16:04:22 +08:00
kuaifan
8c1cea6e6e fix: 消息已读 2022-09-06 16:04:01 +08:00
kuaifan
473eaa040f feat: window客户端任务栏闪烁 2022-09-06 16:03:26 +08:00
kuaifan
b47b3b4b3a perf: 升级框架内核 2022-09-06 16:02:36 +08:00
kuaifan
c185c8c22c no message 2022-07-21 11:08:35 +08:00
kuaifan
86c19ce355 perf: 优化消息发送失败 2022-07-21 10:44:35 +08:00
kuaifan
17e8dbb4d9 build 2022-07-20 14:57:11 +08:00
kuaifan
90fd2deba0 feat: 消息粘贴excel内容自动转成图片 2022-07-20 14:48:20 +08:00
kuaifan
6b00aad904 no message 2022-07-20 12:21:25 +08:00
kuaifan
d013ab1e3c perf: 优化发送图片出现空白的情况 2022-07-20 12:21:17 +08:00
kuaifan
9d14123a66 no message 2022-07-20 11:42:47 +08:00
kuaifan
49744a3671 fix: 无法添加任务的问题 2022-07-20 11:35:19 +08:00
kuaifan
b73c931b8b build 2022-07-19 20:49:33 +08:00
kuaifan
f474f111dc no message 2022-07-19 20:42:31 +08:00
kuaifan
c2fec1953b perf: 消息发送失败支持再次编辑 2022-07-19 16:41:34 +08:00
kuaifan
f7061d1a8d no message 2022-07-19 16:16:22 +08:00
kuaifan
67e2ec4093 perf: 对话支持拼音搜索 2022-07-19 16:14:44 +08:00
kuaifan
5f50fc7b3b perf: 新增注册自动进入全员群开关 2022-07-19 16:02:23 +08:00
kuaifan
3fa2814531 no message 2022-07-19 16:01:35 +08:00
kuaifan
4c6b58cbd5 perf: 移动客户端群消息通知加上群名称 2022-07-19 15:50:08 +08:00
kuaifan
444afc30e4 build 2022-07-19 09:47:17 +08:00
kuaifan
51763d7857 perf: 消息菜单新增复制图片、链接功能 2022-07-19 09:44:21 +08:00
kuaifan
108c82b353 build 2022-07-18 07:53:20 +08:00
kuaifan
ec1226a8ce 搜索文件菜单新增打开文件所在目录 2022-07-18 07:51:16 +08:00
kuaifan
127aaa30f4 仅定位消息位置才显示超时spinner 2022-07-18 07:48:16 +08:00
kuaifan
2462d457a7 build 2022-07-17 18:10:29 +08:00
kuaifan
73ecfc50aa 分类消息新增查看完整对话 2022-07-17 14:48:30 +08:00
kuaifan
95c11f0504 no message 2022-07-17 14:13:04 +08:00
kuaifan
3055eccb82 优化定位消息 2022-07-17 13:59:58 +08:00
kuaifan
aedae961ff 将消息设置新任务时图片使用原图 2022-07-17 13:16:48 +08:00
kuaifan
d38032bb2b perf: 按录音时停止正在播放的 2022-07-17 11:44:02 +08:00
kuaifan
bfd2917043 perf: 优化消息列表 2022-07-17 11:40:03 +08:00
kuaifan
b4085fd1d3 perf: 优化移除群成员与打开成员对话冲突的情况 2022-07-17 08:28:52 +08:00
kuaifan
40e258928e build 2022-07-16 22:13:57 +08:00
kuaifan
4b13e98d43 优化国际化 2022-07-16 21:54:09 +08:00
kuaifan
cfd3996af1 no message 2022-07-16 21:53:53 +08:00
kuaifan
d565501e04 perf: 优化国际化 2022-07-16 19:23:12 +08:00
kuaifan
c7dafa6199 build 2022-07-16 18:06:24 +08:00
kuaifan
1c3de2e889 no message 2022-07-16 17:37:12 +08:00
kuaifan
fa1c917b97 perf: 优化删除成员 2022-07-16 17:22:34 +08:00
kuaifan
29ba6713f0 build 2022-07-16 15:54:49 +08:00
kuaifan
10041b1a30 perf: 优化编辑带有图片的消息 2022-07-16 14:38:38 +08:00
kuaifan
3fe6ab0e79 前端文案“会员”改成“成员” 2022-07-15 18:46:18 +08:00
kuaifan
0a99ecdd9b perf: 支持搜索共享文件 2022-07-15 18:29:23 +08:00
kuaifan
ed9b93c22f build 2022-07-15 16:07:33 +08:00
kuaifan
c8e518b4e6 fix: 无法下载转发文件的问题 2022-07-15 14:59:10 +08:00
kuaifan
8f42e2924d fix: 无法操作离职的问题 2022-07-15 14:47:43 +08:00
kuaifan
d23bff5a48 fix: 编辑@消息的问题 2022-07-15 14:38:08 +08:00
kuaifan
1a1c03b355 优化复制消息 2022-07-15 14:21:01 +08:00
kuaifan
b20506cfa0 no message 2022-07-14 14:45:31 +08:00
kuaifan
0835e5b069 perf: 优化发消息时有时候出现空白需要滚动才出现内容的情况 2022-07-14 14:41:49 +08:00
kuaifan
26d0d07850 调整代码逻辑 2022-07-14 14:41:48 +08:00
韦荣超
b24eca0c5b build 2022-07-14 14:12:38 +08:00
韦荣超
aba04e63a1 fix: 删除账号-提示文案修改 2022-07-14 14:10:36 +08:00
韦荣超
999f22d8ff fix: 删除账号-提示文案修改 2022-07-14 14:07:15 +08:00
韦荣超
438d0344eb fix: 修改邮箱-”发送验证码“倒计时未结束修改 2022-07-14 14:03:55 +08:00
韦荣超
c99edc20a2 build: 删除账户必填加星号;邮箱验证码可以多发送 2022-07-14 12:26:45 +08:00
韦荣超
132dd8134a fix: 删除账户必填加星号;邮箱验证码可以多发送 2022-07-14 12:24:01 +08:00
韦荣超
16ac8d6b06 fix: 修改/删除账号接口无权限问题修改;根据env文件'SYSTEM_SETTING'变量判断是否能修改/删除账号 2022-07-14 11:01:09 +08:00
kuaifan
1a23fff2ce no message 2022-07-13 22:42:25 +08:00
kuaifan
774353c5fe no message 2022-07-13 22:14:55 +08:00
kuaifan
b6598a2b40 Merge branch 'develop' of ssh://git.gezi.vip:6006/gx/dootask
# Conflicts:
#	public/css/app.css
#	public/js/hash
2022-07-13 21:35:45 +08:00
kuaifan
0273fe76ca build 2022-07-13 21:12:29 +08:00
kuaifan
e0ec15653c fix: 音频/视频都不选时无法进入会议的情况 2022-07-13 20:49:56 +08:00
kuaifan
ed9c4f1413 文案统一:账号改成帐号 2022-07-13 20:40:40 +08:00
kuaifan
cda9be878b 调整代码 2022-07-13 20:34:42 +08:00
韦荣超
64d34deb6b build: 新增删除账户功能 2022-07-13 18:44:05 +08:00
韦荣超
1ea5bfa807 feat: 新增删除账户功能 2022-07-13 18:42:01 +08:00
韦荣超
5b9f85f442 build: 修改邮箱-校验邮箱去掉前后空格 2022-07-13 10:12:05 +08:00
韦荣超
8089085bb4 fix: 修改邮箱-校验邮箱去掉前后空格 2022-07-13 10:09:58 +08:00
韦荣超
18b8ba5c5f build: 新增修改邮箱功能 2022-07-12 18:25:46 +08:00
韦荣超
d80943ab6b feat: 新增修改邮箱功能 2022-07-12 18:23:04 +08:00
kuaifan
77ebb225c4 no message 2022-07-12 13:46:33 +08:00
kuaifan
28850338dd no message 2022-07-12 13:38:21 +08:00
kuaifan
1daba51286 build 2022-07-12 13:25:39 +08:00
kuaifan
2a2033ba21 fix: 待办数量与实际的数量不一致 2022-07-12 13:13:32 +08:00
kuaifan
367d7a0ae8 perf: 角标提示待办跟@一起 2022-07-12 13:13:10 +08:00
kuaifan
1ef33700ee perf: 移动端任务打开聊天按钮优化 2022-07-12 12:30:54 +08:00
kuaifan
0bf98ec9f8 perf: 支持转发给最近聊天 2022-07-12 12:15:19 +08:00
kuaifan
fcbbd2b64c no message 2022-07-12 10:37:46 +08:00
kuaifan
4844cc1ea1 perf: 可以通过群成员点击打开对话 2022-07-11 18:09:39 +08:00
kuaifan
0f076b4a16 perf: 展示消息回应详情 2022-07-11 17:42:11 +08:00
kuaifan
8e9cd253e4 搜索关键词去除  2022-07-11 07:48:47 +08:00
kuaifan
43a4e5741a perf: 优化通知类消息字符长度 2022-07-11 07:30:22 +08:00
kuaifan
4561efc3ce perf: 去除通知里的  2022-07-10 21:16:46 +08:00
kuaifan
d0d6965c52 Merge remote-tracking branch 'A_github_pro/master' 2022-07-09 01:24:08 +08:00
kuaifan
07d395e7b2 no msg 2022-07-09 01:22:17 +08:00
kuaifan
f61ad6ca5a no message 2022-07-08 23:55:03 +08:00
kuaifan
82010e7225 no message 2022-07-08 23:11:07 +08:00
kuaifan
36ea882869 no message 2022-07-08 22:59:35 +08:00
kuaifan
a6a0133d37 no message 2022-07-08 22:57:12 +08:00
kuaifan
bd6f6a7106 no message 2022-07-08 22:55:52 +08:00
kuaifan
c3f7ea8ac7 no message 2022-07-08 21:20:02 +08:00
kuaifan
c0f7d042c6 客户端点击版本号查看服务器地址 2022-07-08 21:17:04 +08:00
kuaifan
c3588b4ac3 调整接口版本限制 2022-07-08 20:09:30 +08:00
kuaifan
13049f63c1 去除HitoseaTask,统一使用DooTask 2022-07-08 20:09:08 +08:00
kuaifan
b26cea1aa3 优化头像地址 2022-07-08 19:20:07 +08:00
kuaifan
7fb6135680 build 2022-07-08 18:44:15 +08:00
kuaifan
e60dd0a93e 修复浏览器ua错误的问题 2022-07-08 18:44:06 +08:00
kuaifan
7c11e32b71 fix: 任务列表无法修改优先级的问题 2022-07-08 18:35:40 +08:00
kuaifan
49a361bf66 no message 2022-07-08 17:06:29 +08:00
kuaifan
e4e4224716 升级electron 2022-07-08 17:06:06 +08:00
kuaifan
1542720bc4 优化推送筛选 2022-07-08 17:01:35 +08:00
kuaifan
a5d0cae884 build 2022-07-08 16:34:42 +08:00
kuaifan
6e03415847 no message 2022-07-08 16:23:24 +08:00
kuaifan
f6b7eca0f5 perf: 客户端窗口激活自动获取聊天焦点 2022-07-08 16:23:14 +08:00
kuaifan
82fad2c17d perf: 个人对话窗口支持拨打电话 2022-07-08 16:06:18 +08:00
kuaifan
c13f8bbcc0 perf: 新增联系电话 2022-07-08 15:24:24 +08:00
kuaifan
97d041076d perf: [notice|tag|todo]类型的消息静默推送 2022-07-08 14:45:42 +08:00
kuaifan
d9696d2398 perf: 只给一个月内登录App的帐号推送 2022-07-08 14:08:25 +08:00
kuaifan
883e5c26d9 perf: 显示待办消息数量 2022-07-08 14:00:16 +08:00
kuaifan
6412efd031 no message 2022-07-08 11:50:58 +08:00
kuaifan
ef7400e9d2 build 2022-07-08 09:27:53 +08:00
kuaifan
c76e8d2677 no message 2022-07-08 09:14:56 +08:00
kuaifan
e97f2271fa perf: 待办消息支持指定成员 2022-07-08 09:14:40 +08:00
kuaifan
488259d442 优化待办、回复抽屉 2022-07-08 07:43:34 +08:00
kuaifan
5bbbbb0b02 build 2022-07-07 20:30:42 +08:00
kuaifan
6e186e9186 perf: 支持查看待办完成情况 2022-07-07 20:26:24 +08:00
kuaifan
8ca6b53224 no message 2022-07-07 18:05:07 +08:00
kuaifan
2af899cfea 仅支持设此待办人员取消 2022-07-07 14:45:22 +08:00
kuaifan
72a38f4b45 no message 2022-07-07 14:37:25 +08:00
kuaifan
f24a8230c1 build 2022-07-07 13:43:52 +08:00
kuaifan
5651713032 待办完成功能 2022-07-07 13:24:09 +08:00
kuaifan
3829ec1308 feat: 新增待办消息功能 2022-07-07 09:50:32 +08:00
kuaifan
d085da07cd no message 2022-07-07 09:49:06 +08:00
kuaifan
937d996458 perf: 优化抖动提示 2022-07-07 00:05:55 +08:00
kuaifan
6a58f07a91 perf: 消息新增#我协助的任务 2022-07-06 23:27:12 +08:00
kuaifan
2366e4df17 fix: 任务详情不出现聊天的情况 2022-07-06 23:13:43 +08:00
kuaifan
ed937f5f31 no message 2022-07-06 12:02:24 +08:00
kuaifan
ca65741392 build 2022-07-05 14:41:16 +08:00
kuaifan
ef2b1dd321 no message 2022-07-05 14:34:23 +08:00
kuaifan
8eb956f831 fix: 移动文件夹内文件所有者不变的问题 2022-07-05 12:59:50 +08:00
kuaifan
c3cccbb2c8 perf: 回复或修改消息发送时立即隐藏引用显示 2022-07-05 12:17:36 +08:00
kuaifan
e861e8d878 perf: 搜索对话可以搜索远程的对话 2022-07-05 12:06:58 +08:00
kuaifan
1fb70a4456 no message 2022-07-05 11:34:17 +08:00
kuaifan
277869c9d1 fix: 通知消息一直未读的情况 2022-07-05 11:33:46 +08:00
kuaifan
a13da976ae build 2022-07-05 00:25:33 +08:00
kuaifan
0f6886656e fix: 移动文件所有者错误 2022-07-05 00:14:47 +08:00
kuaifan
63cd4649dd no message 2022-07-05 00:03:56 +08:00
kuaifan
673038bf63 no message 2022-07-04 23:07:04 +08:00
kuaifan
625bb97c35 no message 2022-07-04 16:29:43 +08:00
kuaifan
1f71cc827c no message 2022-07-04 16:24:25 +08:00
kuaifan
50057894e8 build 2022-07-04 16:17:06 +08:00
kuaifan
824399bbf1 perf: 所有项目列表支持筛选个人项目 2022-07-04 16:13:57 +08:00
kuaifan
fb74ef4843 feat: 新增全员群组 2022-07-04 16:01:03 +08:00
kuaifan
e5cbeb032e perf: 调整消息标签位置 2022-07-04 14:41:59 +08:00
kuaifan
2cf68e96c3 build 2022-07-04 00:24:27 +08:00
kuaifan
92ea7eaab7 fix: 无法通过项目点击聊天的情况 2022-07-04 00:20:14 +08:00
kuaifan
8ca22fc209 build 2022-07-03 21:46:10 +08:00
kuaifan
e80a38fa78 no message 2022-07-03 21:42:59 +08:00
kuaifan
9c861ec58c feat: 支持编辑已发送的消息 2022-07-03 21:42:49 +08:00
kuaifan
6b9c20bf93 perf: 添加邮件忽略功能 2022-07-03 18:00:51 +08:00
kuaifan
6cf9c1f4f2 no message 2022-07-03 17:25:51 +08:00
kuaifan
b73e1c27eb 解决消息筛选时计算错误的情况 2022-07-03 10:06:26 +08:00
kuaifan
69fb875421 优化消息滚动 2022-07-03 00:12:32 +08:00
kuaifan
aa7d93e97e no message 2022-07-01 18:09:45 +08:00
kuaifan
bf136c9b2f build 2022-07-01 17:07:21 +08:00
kuaifan
256143fd21 no message 2022-07-01 16:57:03 +08:00
kuaifan
26628b1c89 no message 2022-07-01 16:28:19 +08:00
kuaifan
08883ce558 no message 2022-07-01 16:27:13 +08:00
kuaifan
a9950a4365 点击文件直接下载 2022-07-01 16:17:11 +08:00
kuaifan
42bc25381c no message 2022-07-01 16:11:37 +08:00
kuaifan
830552c121 no message 2022-07-01 15:23:47 +08:00
kuaifan
2728d3c093 build 2022-07-01 11:38:05 +08:00
kuaifan
81b07ea6e8 fix: 任务聊天出现空白的情况 2022-07-01 11:33:34 +08:00
kuaifan
27441eb82e 消息新增链接类型筛选 2022-07-01 11:22:58 +08:00
kuaifan
49f8e23aae feat: 新增消息类型筛选 2022-06-30 19:54:48 +08:00
kuaifan
a368e00b2f no message 2022-06-30 16:07:50 +08:00
kuaifan
500ed3a4d7 feat: 新增标注消息功能 2022-06-30 15:56:09 +08:00
kuaifan
83765c9d2d no message 2022-06-30 11:05:36 +08:00
kuaifan
4c6a20601c perf: 优化协助任务的更新 2022-06-29 19:17:39 +08:00
kuaifan
d6b4cd78d0 build 2022-06-29 13:40:58 +08:00
kuaifan
460ebeb8c5 no message 2022-06-29 13:40:26 +08:00
kuaifan
513bc28686 perf: 管理员新增修改成员邮箱功能 2022-06-29 13:40:03 +08:00
kuaifan
ef6b4e8149 fix: 新建文件不显示的问题 2022-06-29 13:39:33 +08:00
kuaifan
644f415d46 优化确认弹窗 2022-06-29 13:30:40 +08:00
kuaifan
575e569048 优化确认输入框 2022-06-29 11:28:15 +08:00
kuaifan
1a31939eaa no message 2022-06-28 15:17:56 +08:00
kuaifan
d3dcce96fa no message 2022-06-28 15:03:29 +08:00
kuaifan
8669acc663 no message 2022-06-28 14:51:04 +08:00
kuaifan
a9c37cc5a6 优化协助任务列表 2022-06-28 14:49:31 +08:00
kuaifan
d1f22ead93 build 2022-06-28 11:36:17 +08:00
kuaifan
305e67c454 App显示隐私政策 2022-06-28 10:58:21 +08:00
kuaifan
86ceae9a1e build 2022-06-28 10:22:50 +08:00
kuaifan
f1dd5249ae no message 2022-06-28 10:21:33 +08:00
kuaifan
47f3dadfd4 fix: 输入框粘贴后出错的问题 2022-06-28 10:09:56 +08:00
kuaifan
66b7a00b5c perf: 支持查看回复列表 2022-06-28 10:01:09 +08:00
kuaifan
13ca1b18d6 no message 2022-06-28 08:24:14 +08:00
kuaifan
688fcd3c77 no message 2022-06-27 20:31:35 +08:00
kuaifan
58856c6620 perf: 优化消息分页加载 2022-06-27 20:17:59 +08:00
kuaifan
17ce620abf no message 2022-06-27 13:31:17 +08:00
kuaifan
5bb80b5f50 no message 2022-06-27 10:36:15 +08:00
kuaifan
625908dc1b build 2022-06-27 10:18:23 +08:00
kuaifan
ad2b456f07 fix: 任务重复周期 2022-06-27 10:17:23 +08:00
kuaifan
c6402c72f9 显示回复数量 2022-06-27 09:19:36 +08:00
kuaifan
f34133f63e perf: 添加消息回复量 2022-06-24 21:08:53 +08:00
kuaifan
dfb6f23b60 no message 2022-06-24 20:42:46 +08:00
kuaifan
0d158d2403 修复弹窗的消息菜单位置不对 2022-06-24 20:42:24 +08:00
kuaifan
7a63f6d68a no message 2022-06-24 17:21:05 +08:00
kuaifan
16bbab366e build 2022-06-24 15:45:40 +08:00
kuaifan
e63e025bda feat: 新增任务重复周期 2022-06-24 15:40:53 +08:00
kuaifan
d4d3f1245b perf: 仪表盘列表新增显示协助的任务 2022-06-24 10:48:47 +08:00
kuaifan
707c74264b no message 2022-06-23 20:34:05 +08:00
kuaifan
23b91d37ab build 2022-06-23 19:42:24 +08:00
kuaifan
adf9225180 feat: 支持通过拼音搜索联系人 2022-06-23 19:36:10 +08:00
kuaifan
d386b4e7ac perf: 优化@其他成员在线状态 2022-06-23 19:14:45 +08:00
kuaifan
f13472f088 fix: 任务成员应该禁止退出任务群聊 2022-06-23 19:03:39 +08:00
kuaifan
5ec56b4a9e perf: 仅(群聊)且(是群主或没有群主)才可以@成员以外的人 2022-06-23 18:53:57 +08:00
kuaifan
567a77b0be no message 2022-06-23 15:08:49 +08:00
kuaifan
a39802ea48 build 2022-06-23 14:57:41 +08:00
kuaifan
045d585df4 perf: 优化pdf浏览方式 2022-06-23 14:37:54 +08:00
kuaifan
d0bc85346e no message 2022-06-23 11:37:05 +08:00
kuaifan
dee12941c0 fix: 撤回消息导致未读数错误的问题 2022-06-23 10:22:22 +08:00
kuaifan
65018aab5b build 2022-06-23 00:06:31 +08:00
kuaifan
77761d9ec4 no message 2022-06-22 23:39:54 +08:00
kuaifan
68eace11de fix: 部分长按菜单移位的问题 2022-06-22 23:39:43 +08:00
kuaifan
db4678770d no message 2022-06-22 19:31:50 +08:00
kuaifan
a798c61ded build 2022-06-22 19:23:57 +08:00
kuaifan
822bc3ea69 perf: 支持@群聊以外成员 2022-06-22 19:22:54 +08:00
kuaifan
3aefe99bd9 perf: 项目群、任务群可添加成员 2022-06-22 16:21:55 +08:00
kuaifan
bebef77e5c no message 2022-06-21 22:38:58 +08:00
kuaifan
b5ae9c6a8a no message 2022-06-21 22:28:09 +08:00
kuaifan
5f98cc93c7 fix: 无法点击图片预览的问题 2022-06-21 17:30:39 +08:00
kuaifan
c1d27399d9 no message 2022-06-21 15:38:52 +08:00
kuaifan
25f244c5f1 no message 2022-06-21 10:51:30 +08:00
kuaifan
561e205481 no message 2022-06-21 09:59:08 +08:00
kuaifan
9dd22dba19 no message 2022-06-20 15:21:26 +08:00
kuaifan
43b2b4dc3d no message 2022-06-20 07:49:09 +08:00
kuaifan
ce5948d87a no message 2022-06-20 07:31:59 +08:00
kuaifan
3a0d967800 no message 2022-06-20 07:28:25 +08:00
kuaifan
093de52230 no message 2022-06-20 07:15:34 +08:00
kuaifan
f54f86f2b5 no message 2022-06-20 02:25:54 +08:00
kuaifan
d167a91a07 feat: 支持搜索历史消息 2022-06-20 02:23:20 +08:00
kuaifan
4384a2dd91 build 2022-06-19 12:17:01 +08:00
kuaifan
573a5c03e4 no message 2022-06-19 12:14:52 +08:00
kuaifan
7c885cad5b perf: 优化文件操作菜单样式 2022-06-19 11:04:17 +08:00
kuaifan
ba65c21a0b perf: 文件浏览支持滑动返回上一个文件夹 2022-06-19 10:42:39 +08:00
kuaifan
073b1937f0 no msg 2022-06-19 10:07:19 +08:00
kuaifan
793f6152ff 阻止长按图片 2022-06-19 09:26:17 +08:00
kuaifan
cf0a431fff perf: 桌面客户端出现无法关闭窗口的情况 2022-06-19 09:21:08 +08:00
kuaifan
ba2d81da50 build 2022-06-19 02:16:34 +08:00
kuaifan
c278806548 perf: 优化触发回复页面滚动 2022-06-19 02:13:36 +08:00
kuaifan
21ad0632b4 优化文件上传前 2022-06-19 02:06:10 +08:00
kuaifan
8365f69de6 优化全局加载器 2022-06-19 02:05:48 +08:00
kuaifan
db28811027 修改文件最大上传1G 2022-06-19 01:19:11 +08:00
kuaifan
a69a4befc5 no message 2022-06-19 01:03:22 +08:00
kuaifan
d7cc0064b2 build 2022-06-19 00:17:04 +08:00
kuaifan
b3c227d3cb perf: 优化对话详情页 2022-06-19 00:15:31 +08:00
kuaifan
09fd8aa1b0 build 2022-06-18 00:30:51 +08:00
kuaifan
58e8b7b0a0 头像缓存机制 2022-06-18 00:22:58 +08:00
kuaifan
9ea250278d 解决消息滚动出现白屏的情况 2022-06-18 00:22:41 +08:00
kuaifan
17c9a8b3fa no message 2022-06-17 17:57:39 +08:00
kuaifan
9ff2f24529 no message 2022-06-17 17:48:31 +08:00
kuaifan
0ab321f184 build 2022-06-17 16:13:59 +08:00
kuaifan
500e666416 消息列表滚动到底部 2022-06-17 16:04:30 +08:00
kuaifan
88316fda1c 定位回复消息 2022-06-17 15:25:13 +08:00
kuaifan
3bd2e40a98 表情描述 2022-06-17 11:56:03 +08:00
kuaifan
d5c8b33678 定位回复消息位置 2022-06-17 11:50:07 +08:00
kuaifan
f3e9ac2b56 优化等待 2022-06-17 11:19:06 +08:00
kuaifan
3cc6a3317e 优化回复消息 2022-06-17 10:42:06 +08:00
kuaifan
3728b1d3a0 feat: 新增回复消息功能 2022-06-17 08:33:47 +08:00
kuaifan
4c163a8092 perf: 取消置顶标签 2022-06-16 23:32:38 +08:00
kuaifan
bacbf2e3cb perf: 优化移动客户端滚动穿透 2022-06-16 23:14:00 +08:00
kuaifan
f211fe0ae4 build 2022-06-16 20:27:29 +08:00
kuaifan
e67dc0a637 处理消息阅读不消数的问题 2022-06-16 18:33:27 +08:00
kuaifan
3f959918af 优化消息列表 2022-06-16 17:51:47 +08:00
kuaifan
d9cd7a93cc perf: 优化消息列表 2022-06-16 12:45:10 +08:00
kuaifan
4fcdeb0313 头像缓存 2022-06-15 18:40:23 +08:00
kuaifan
8839fb0738 no message 2022-06-14 19:21:20 +08:00
kuaifan
26de1b1745 no message 2022-06-14 18:44:08 +08:00
kuaifan
b1700ca4e6 build 2022-06-14 18:17:31 +08:00
kuaifan
f7f668a032 perf: 默认使用文字头像 2022-06-14 18:11:54 +08:00
kuaifan
d8d707175c 文件链接新增点击复制按钮 2022-06-14 14:48:29 +08:00
kuaifan
de45332543 优化窗口激活机制 2022-06-14 14:19:51 +08:00
kuaifan
dbba2a1b8f no message 2022-06-14 11:56:05 +08:00
kuaifan
8b394819c4 Mac 触发升级时跳动Dock图标 2022-06-14 10:19:36 +08:00
kuaifan
74b1f65e0f perf: 使用系统浏览器打开新窗口链接 2022-06-14 10:13:49 +08:00
kuaifan
8110930458 离线时更新会员最后在线时间 2022-06-14 10:04:21 +08:00
kuaifan
02b2e8cffb 优化消息加载 2022-06-14 09:58:45 +08:00
kuaifan
643ccc4880 取消会员信息加载覆盖层 2022-06-14 09:58:08 +08:00
kuaifan
4deed559c9 build 2022-06-13 16:19:07 +08:00
kuaifan
be3fcb55fb perf: 优化通讯录刷新机制 2022-06-13 16:17:47 +08:00
kuaifan
11308829a6 fix: 员工删除后对话还存在的问题 2022-06-13 15:53:58 +08:00
kuaifan
e2b2da8b46 取消 Content-Security-Policy 2022-06-13 13:51:13 +08:00
kuaifan
b4bd6d17bf no message 2022-06-13 12:31:40 +08:00
kuaifan
08f3925af4 no message 2022-06-13 12:25:31 +08:00
kuaifan
c739448e3c no message 2022-06-13 12:04:11 +08:00
kuaifan
7a3f817469 build 2022-06-13 12:03:12 +08:00
kuaifan
9d522d3c3e perf: 通知自动关闭 2022-06-13 12:01:36 +08:00
kuaifan
c46567782e no message 2022-06-13 11:56:12 +08:00
kuaifan
e071209bb8 修复因为共享文件出现的文件目录层级的问题 2022-06-13 11:15:07 +08:00
kuaifan
2efedf225f no message 2022-06-13 10:39:55 +08:00
kuaifan
5a08c3dec4 perf: 优化excel菜单 2022-06-13 10:12:37 +08:00
kuaifan
734abe59ee no message 2022-06-13 08:47:42 +08:00
kuaifan
6759e2b30c no message 2022-06-12 20:43:59 +08:00
kuaifan
324d7ea487 build 2022-06-12 20:34:51 +08:00
kuaifan
4fd07e4c6e perf: 优化文件重名的问题 2022-06-12 20:32:34 +08:00
kuaifan
afea981a42 no message 2022-06-12 19:16:14 +08:00
kuaifan
df9eaf6e0a no message 2022-06-12 19:07:47 +08:00
kuaifan
caa0fc0a8a Merge branch 'master' of github.com:kuaifan/dootask-pro 2022-06-12 18:39:07 +08:00
kuaifan
6bd8c590ab perf: 优化图片预览缩放 2022-06-12 18:30:11 +08:00
kuaifan
219fbc7fb5 build 2022-06-12 00:31:57 +08:00
kuaifan
cbaa5c3464 build 2022-06-11 23:50:42 +08:00
kuaifan
76e66ab433 移动客户端文件使用新窗口打开 2022-06-11 23:48:43 +08:00
kuaifan
0150b41e17 no message 2022-06-11 22:55:47 +08:00
kuaifan
2dbdc3b780 no message 2022-06-11 22:28:37 +08:00
kuaifan
e7cb48f0b1 整理iframe事件 2022-06-11 21:19:52 +08:00
kuaifan
cae7184a7e no message 2022-06-11 14:50:38 +08:00
kuaifan
5ba63d951c Android取消文件打开动画 2022-06-11 14:41:52 +08:00
kuaifan
b758133f63 perf: 优化预览文件 2022-06-11 14:36:48 +08:00
kuaifan
4024aca002 perf: 优化同时加载同个任务 2022-06-11 11:13:19 +08:00
kuaifan
7826cc8f68 no message 2022-06-11 01:46:00 +08:00
kuaifan
bf9ecb78f8 no message 2022-06-11 01:32:52 +08:00
kuaifan
8783b85b00 no message 2022-06-11 01:27:50 +08:00
kuaifan
d72e9f678f no message 2022-06-10 21:23:16 +08:00
kuaifan
fec3dcc65f build 2022-06-10 21:03:26 +08:00
kuaifan
300798981d perf: 优化键盘关闭 2022-06-10 20:59:01 +08:00
kuaifan
58b5843dd9 build 2022-06-10 17:48:20 +08:00
kuaifan
6b97696593 no message 2022-06-10 17:32:30 +08:00
kuaifan
18e41eba4d fix: win客户端升级签名报错的问题 2022-06-10 17:32:23 +08:00
kuaifan
34c6addde3 perf: 优化office右上角菜单按钮重叠的问题 2022-06-10 17:32:03 +08:00
kuaifan
5655c5440f build 2022-06-10 14:36:45 +08:00
kuaifan
dd25561338 no message 2022-06-10 14:28:34 +08:00
kuaifan
eaabc5852c perf: 优化录音效果 2022-06-10 10:30:40 +08:00
kuaifan
dbb1033911 perf: 移动端只读文件 2022-06-10 10:25:06 +08:00
kuaifan
7c287f6ae5 perf: 优化任务窗口输入框草稿 2022-06-10 10:03:45 +08:00
kuaifan
b9ddc0cf1f no message 2022-06-10 09:55:57 +08:00
kuaifan
9a9ff64576 perf: 头像显示已离职效果 2022-06-10 08:58:33 +08:00
kuaifan
02f75726c5 perf: 文件文本编辑支持command+s保存 2022-06-10 08:17:51 +08:00
kuaifan
b4c1d05d2e fix: 文件md、text互转时文件格式没有变的问题 2022-06-09 16:29:04 +08:00
kuaifan
0035789951 perf: 长文本消息的处理 2022-06-09 16:11:15 +08:00
kuaifan
a11eb11166 perf: 客户端新窗口皮肤不统一的问题 2022-06-09 14:50:36 +08:00
kuaifan
020be75f7b fix: 移动客户端访问本站链接出现需要登录的情况 2022-06-09 12:21:56 +08:00
kuaifan
66961197f6 fix: 不是任务负责人不能通过小窗口发送任务消息的问题 2022-06-09 11:44:30 +08:00
kuaifan
f05914f2a1 fix: 桌面客户端任务独立窗口无法操作任务状态的问题 2022-06-09 11:23:19 +08:00
kuaifan
e161912496 perf: 流程图支持搜索远程图标 2022-06-09 10:29:37 +08:00
kuaifan
abad2c2cc9 no message 2022-06-08 13:16:31 +08:00
kuaifan
efac5401bf no message 2022-06-08 11:42:04 +08:00
kuaifan
e463a844ff build 2022-06-08 11:40:10 +08:00
kuaifan
ddb7d1a6db perf: 升级office插件 2022-06-08 11:37:54 +08:00
kuaifan
c4faa0a316 build 2022-06-08 08:20:53 +08:00
kuaifan
eca066cbd0 perf: 优化消息已读未读 2022-06-08 08:17:36 +08:00
kuaifan
e0bd225ac2 no message 2022-06-07 23:37:55 +08:00
kuaifan
a5571ba1d4 build 2022-06-07 20:32:47 +08:00
kuaifan
a87f2261ce 优化客户端升级提示 2022-06-07 20:29:34 +08:00
kuaifan
33db978e36 perf: 预览图片尺寸的优化 2022-06-07 19:25:28 +08:00
kuaifan
4c068f4a62 no message 2022-06-07 17:54:25 +08:00
kuaifan
2e7f2bc316 build 2022-06-07 15:03:41 +08:00
kuaifan
54ebb327bd perf: 新窗口打开任务时保持日志显示状态 2022-06-07 14:17:36 +08:00
kuaifan
6875b1b8af no message 2022-06-07 14:08:53 +08:00
kuaifan
a32faf1a99 perf: 优化首页加载失败的情况 2022-06-07 12:22:00 +08:00
kuaifan
94b3014051 调整首页底部行距 2022-06-07 12:07:22 +08:00
kuaifan
c63fdb04c2 perf: 文字发送太长转成文件发送 2022-06-07 12:03:38 +08:00
kuaifan
07a4196ed5 fix: 修复任务窗口无法发送表情的问题 2022-06-07 10:35:22 +08:00
kuaifan
7f5eb59ac5 perf: 任务详情窗口尺寸 2022-06-07 10:26:25 +08:00
kuaifan
c9de0c2eba perf: 优化全局任务操作菜单 2022-06-07 10:17:41 +08:00
kuaifan
242932735a build 2022-06-06 21:11:10 +08:00
kuaifan
d0b2daa56b 修复对话窗口首次被键盘遮挡的问题 2022-06-06 21:07:42 +08:00
kuaifan
4bc05e258d no message 2022-06-06 19:19:32 +08:00
kuaifan
902c0262f2 build 2022-06-06 16:46:38 +08:00
kuaifan
917f91249e 修复无法转发别人的信息 2022-06-06 16:46:02 +08:00
kuaifan
d75ee02b4a 身份未确认之前不显示网页框架 2022-06-06 16:45:48 +08:00
kuaifan
c08e217fb8 no message 2022-06-06 16:08:55 +08:00
kuaifan
244a2031a7 build 2022-06-06 14:38:56 +08:00
kuaifan
62b8b623ec no message 2022-06-06 14:35:45 +08:00
kuaifan
c5c9e19f76 no message 2022-06-06 14:03:50 +08:00
kuaifan
2f76e932b0 no message 2022-06-06 11:52:20 +08:00
kuaifan
2c373c388c 更新会话路由 2022-06-06 11:32:06 +08:00
kuaifan
34a1c97e6b no message 2022-06-06 08:44:31 +08:00
kuaifan
113c58a057 全局使用windowSmall、windowLarge 2022-06-06 07:16:15 +08:00
kuaifan
ce88fe426b 移动端消息窗口使用全局模式弹窗 2022-06-06 00:49:08 +08:00
kuaifan
ab24af6e79 no msg 2022-06-05 23:15:40 +08:00
kuaifan
b9b45e8e4f 修复移动端任务弹窗关闭之后键盘没有关闭 2022-06-05 21:38:10 +08:00
kuaifan
5c750f7a6f 聊天内容选择后工具小浮窗被遮挡的问题 2022-06-05 21:30:24 +08:00
kuaifan
c2b754d682 修复可以发一个空格消息的问题 2022-06-05 21:09:19 +08:00
kuaifan
2f649c0a9e 修复转发没有选择转发人也可以提交 2022-06-05 20:54:14 +08:00
kuaifan
d456ea68f7 同时播放两个语音时上一个没有停止动画 2022-06-05 20:49:16 +08:00
kuaifan
7fa2c0d6e7 移动端任务窗口聊天loading优化 2022-06-05 20:13:15 +08:00
kuaifan
dace5b92c7 build 2022-06-04 23:25:43 +08:00
kuaifan
8be22e794d no message 2022-06-04 22:58:46 +08:00
kuaifan
90c23a3fd1 git 图片不用生成缩略图 2022-06-04 22:49:40 +08:00
kuaifan
2bd3ba189e no message 2022-06-04 22:41:02 +08:00
kuaifan
8a49eb742e 移动端内通知手势上滑动关闭 2022-06-04 22:22:48 +08:00
kuaifan
8358a10ce8 修复点击图片无法预览 2022-06-04 22:01:19 +08:00
kuaifan
4ecb1995b8 修复点击消息的菜单无效的情况 2022-06-04 20:14:05 +08:00
kuaifan
ddbef0172f 修复切换图片导致的js报错 2022-06-04 20:06:51 +08:00
kuaifan
7a9ad7163b 修复无法emiji标记别人的消息 2022-06-04 20:03:17 +08:00
kuaifan
50f5b7fcf7 解决语音播放不释放的问题 2022-06-04 20:01:21 +08:00
kuaifan
a494709177 no message 2022-06-04 13:05:51 +08:00
kuaifan
7c40441f41 build 2022-06-04 12:44:35 +08:00
kuaifan
d735dbc72c no message 2022-06-04 12:42:10 +08:00
kuaifan
3042ed2628 no message 2022-06-04 10:01:16 +08:00
kuaifan
8a60916776 build 2022-06-04 09:55:55 +08:00
kuaifan
2ac7d0bc01 feat: 新增消息回复表情功能 2022-06-04 09:52:45 +08:00
kuaifan
b8234adbc2 调整滚动到view 2022-06-03 19:27:39 +08:00
kuaifan
6eeeb733e2 no message 2022-06-03 18:06:34 +08:00
kuaifan
d900355bfd build 2022-06-03 17:21:58 +08:00
kuaifan
e71c13e9ac no message 2022-06-03 17:15:49 +08:00
kuaifan
4c2bf56cea no message 2022-06-03 11:50:36 +08:00
kuaifan
47c815d966 build 2022-06-03 11:48:05 +08:00
kuaifan
6c7c17cd61 优化返回手势抖动问题 2022-06-03 11:41:50 +08:00
kuaifan
b855312cf9 优化返回手势抖动问题 2022-06-03 11:24:07 +08:00
kuaifan
b4c8a45bbf 切换项目显示状态错误的问题 2022-06-03 11:13:47 +08:00
kuaifan
b79daedb94 调整项目列表数据 2022-06-03 11:02:18 +08:00
kuaifan
8b4b5382b5 loading 效果 2022-06-03 11:00:05 +08:00
kuaifan
c08240db42 消息转发功能 2022-06-03 10:43:46 +08:00
kuaifan
a2dcd69efd 整理右键、长按 2022-06-03 10:02:04 +08:00
kuaifan
e15f7f6377 统一长按/右键指令 2022-06-02 23:21:48 +08:00
kuaifan
e6aa7de922 对话框自动获取焦点 2022-06-02 16:46:27 +08:00
kuaifan
856f554a41 build 2022-06-02 16:06:12 +08:00
kuaifan
2e153a658f no message 2022-06-02 15:51:05 +08:00
kuaifan
34e4d9f68c no message 2022-06-02 10:01:04 +08:00
kuaifan
49082bc200 no message 2022-06-02 09:32:00 +08:00
kuaifan
a10e9053f9 no message 2022-06-02 09:30:19 +08:00
kuaifan
777b329181 build 2022-06-02 09:27:27 +08:00
kuaifan
fe3f6d2101 移动端隐藏日历预览弹窗箭头 2022-06-02 08:49:50 +08:00
kuaifan
a1b6dc9e84 获取焦点时防止滑动返回抖动 2022-06-02 08:45:03 +08:00
kuaifan
b4453cacc0 build 2022-06-02 00:38:58 +08:00
kuaifan
4cda99a704 no message 2022-06-02 00:12:41 +08:00
kuaifan
f2a618d0c9 no message 2022-06-02 00:04:52 +08:00
kuaifan
dbee06179c no message 2022-06-01 18:54:37 +08:00
kuaifan
2b8e642aad no message 2022-06-01 18:09:22 +08:00
kuaifan
b42721a2a3 no message 2022-06-01 17:33:51 +08:00
kuaifan
e43123b9b4 build 2022-06-01 17:26:30 +08:00
kuaifan
357f0e4df4 完善会议功能 2022-06-01 17:23:32 +08:00
kuaifan
bf59fae173 完善会议功能 2022-06-01 16:46:27 +08:00
kuaifan
0f8cfa72b6 feat: 添加会议功能 2022-06-01 01:48:19 +08:00
kuaifan
24ed87cc82 no message 2022-05-31 17:37:26 +08:00
kuaifan
d47a514b09 no message 2022-05-31 15:41:47 +08:00
kuaifan
1f8198b36b perf: 优化移动端图片预览 2022-05-31 15:15:54 +08:00
kuaifan
022499300e build 2022-05-30 19:53:58 +08:00
kuaifan
db077c8c9f build 2022-05-30 19:46:08 +08:00
kuaifan
e5aa387ef4 no message 2022-05-30 19:40:23 +08:00
kuaifan
04ead7177a no message 2022-05-30 19:28:11 +08:00
kuaifan
13501eb066 统一调整颜色值 2022-05-30 19:23:31 +08:00
kuaifan
13a36fc471 no message 2022-05-30 19:19:14 +08:00
kuaifan
afff590fa5 no message 2022-05-30 17:06:39 +08:00
kuaifan
dea04429de perf: 移动端长按菜单 2022-05-30 15:34:59 +08:00
kuaifan
cb2ad20e36 no message 2022-05-30 15:14:34 +08:00
kuaifan
72f7cc927c fix: 文件共享人数太多内容溢出 2022-05-29 23:47:00 +08:00
kuaifan
9c3eeb59c0 no message 2022-05-29 22:30:06 +08:00
kuaifan
77f6381eff no message 2022-05-29 22:27:43 +08:00
kuaifan
7c1834107e no message 2022-05-29 19:54:05 +08:00
kuaifan
6132c3fa02 no message 2022-05-29 19:47:11 +08:00
kuaifan
9470ebb1ae build 2022-05-29 19:10:43 +08:00
kuaifan
062f0303b4 fix: 聊天内容加载中刷新导致无法再继续加载的情况 2022-05-29 19:09:27 +08:00
kuaifan
c0ca50edf6 调整路由 2022-05-29 18:44:55 +08:00
kuaifan
461546e914 no message 2022-05-29 16:31:39 +08:00
kuaifan
ba95f33592 检查接口版本 2022-05-29 13:39:06 +08:00
kuaifan
cc1d535311 fix: 对话列表点击任务状态标签无法打开对话 2022-05-29 13:38:31 +08:00
kuaifan
4853d5227b fix: 任务弹窗无法发送语音 2022-05-29 13:06:04 +08:00
kuaifan
e750d2ae80 perf: 触摸返回中禁止滚动消息列表 2022-05-29 12:32:37 +08:00
kuaifan
178b511a24 fix: 焦点会超出输入框的情况 2022-05-29 12:03:31 +08:00
kuaifan
2dc5790a46 最长录音3分钟 2022-05-29 11:36:35 +08:00
kuaifan
38b6a74e3d no message 2022-05-29 11:27:32 +08:00
kuaifan
fb3bf48bbc 录音时长不增加则取消录音 2022-05-29 11:13:17 +08:00
kuaifan
c6ab79db30 优化聊天气泡 2022-05-29 10:58:35 +08:00
kuaifan
06951bbc29 perf: 撤回语音消息时停止正在播放 2022-05-29 10:36:46 +08:00
kuaifan
939da1278f no message 2022-05-29 10:19:16 +08:00
kuaifan
75bc7f2a77 no message 2022-05-29 09:05:50 +08:00
kuaifan
82d6f0b533 no message 2022-05-29 07:52:50 +08:00
kuaifan
834bb28100 no message 2022-05-29 00:45:31 +08:00
kuaifan
d4f3124054 no message 2022-05-28 17:08:25 +08:00
kuaifan
85f95ff89c no message 2022-05-28 17:05:45 +08:00
kuaifan
ca74ba1c15 build 2022-05-27 21:51:42 +08:00
kuaifan
44ff21ffcd feat: 支持发送录音 2022-05-27 21:49:00 +08:00
kuaifan
4dd8658aff no message 2022-05-26 21:12:42 +08:00
kuaifan
31b490916d no message 2022-05-26 20:48:44 +08:00
kuaifan
66a5428ec5 perf: 保留粘贴的a标签 2022-05-26 20:48:09 +08:00
kuaifan
965895de5c no message 2022-05-26 19:49:56 +08:00
kuaifan
ac45ba633b build 2022-05-26 15:57:58 +08:00
kuaifan
173f5c84db no message 2022-05-26 10:24:59 +08:00
kuaifan
1d8b4ba83f perf: 支持会话自己 2022-05-25 23:06:42 +08:00
kuaifan
3ce2ec0a46 no message 2022-05-25 22:14:21 +08:00
kuaifan
f9c01f0a7d perf: 聊天内容链接可点击 2022-05-25 22:14:08 +08:00
kuaifan
ca860d551e feat: 对话窗口新增会员最后在线时间 2022-05-25 22:13:27 +08:00
kuaifan
675edef0d1 no message 2022-05-25 17:00:13 +08:00
kuaifan
3f3e4a94ff no message 2022-05-25 16:45:26 +08:00
kuaifan
6732cca479 build 2022-05-25 16:27:36 +08:00
kuaifan
dc8e5a370f build 2022-05-25 16:07:19 +08:00
kuaifan
0476bdea13 no message 2022-05-25 15:57:28 +08:00
kuaifan
0911bc2dc9 perf: 优化搜索加载提示 2022-05-25 15:06:42 +08:00
kuaifan
c5a0e04242 no message 2022-05-25 11:35:04 +08:00
kuaifan
844cdd734e perf: 项目-任务状态的数量,实时更新数据 2022-05-25 10:40:47 +08:00
kuaifan
fc5ce7c5db perf: 优化聊天窗口样式 2022-05-25 10:40:40 +08:00
kuaifan
85a9e33de1 no message 2022-05-24 12:21:18 +08:00
kuaifan
403462b204 no message 2022-05-24 11:33:45 +08:00
kuaifan
12dda85ca5 no message 2022-05-24 09:42:02 +08:00
kuaifan
e1ea5db49d no message 2022-05-24 01:05:27 +08:00
kuaifan
edd8988961 perf: 移动端聊天窗口返回按钮显示未读信息数 2022-05-24 01:01:11 +08:00
kuaifan
a6425c2859 perf: 优化加载状态 2022-05-24 00:40:02 +08:00
kuaifan
52464e232b no message 2022-05-23 23:56:04 +08:00
kuaifan
d015118199 no message 2022-05-23 16:27:08 +08:00
kuaifan
95ff6940f2 no message 2022-05-23 15:45:36 +08:00
kuaifan
bd3b9315b2 no message 2022-05-23 15:40:09 +08:00
kuaifan
46555fcfa5 no message 2022-05-23 15:07:41 +08:00
kuaifan
883b13e0f3 perf: 客户端本地通知 2022-05-23 14:32:18 +08:00
kuaifan
82b2ef3a73 perf: 聊天输入框草稿 2022-05-23 13:02:31 +08:00
kuaifan
3331370d14 no message 2022-05-23 11:35:26 +08:00
kuaifan
c53d7a5dd2 perf: ws重连后重新获取会员基本信息 2022-05-23 11:27:31 +08:00
kuaifan
464d7bc635 perf: 聊天窗口样式 2022-05-23 11:04:37 +08:00
kuaifan
4fd2a44742 perf: 优化信息邮件格式 2022-05-23 10:52:04 +08:00
kuaifan
2d805b775c no message 2022-05-22 23:35:45 +08:00
kuaifan
7636420b3c no message 2022-05-22 23:23:23 +08:00
kuaifan
fd52db7f71 perf: 优化移交个人项目 2022-05-22 23:10:08 +08:00
kuaifan
9c72558878 fix: 获取首字母失败的情况 2022-05-22 20:48:37 +08:00
kuaifan
98f4cf3644 no message 2022-05-22 13:57:38 +08:00
kuaifan
2e6eb81648 no message 2022-05-22 13:37:56 +08:00
kuaifan
972c01bd67 no message 2022-05-22 12:15:31 +08:00
kuaifan
2c99033f1a no message 2022-05-22 11:53:25 +08:00
kuaifan
ebf9686a05 no message 2022-05-22 10:27:22 +08:00
kuaifan
b469069e92 vconsole 2022-05-22 10:02:55 +08:00
kuaifan
7033f00a0e perf: 优化适配ipad 2022-05-22 02:06:31 +08:00
kuaifan
09ed329967 no message 2022-05-21 20:25:28 +08:00
kuaifan
c23234964f no message 2022-05-21 20:23:30 +08:00
kuaifan
df1d5d97c4 no message 2022-05-21 20:13:29 +08:00
kuaifan
2ab12f74ee perf: 优化客户端生命周期重连ws机制 2022-05-21 19:49:36 +08:00
kuaifan
6a2f1f80f5 perf: 优化更新对话列表机制 2022-05-21 19:22:29 +08:00
kuaifan
d0d6b8ce97 perf: 7天内显示时间m-d H:i 2022-05-21 19:21:31 +08:00
kuaifan
b749426389 no message 2022-05-21 18:39:38 +08:00
kuaifan
a33d095296 no message 2022-05-21 08:10:46 +08:00
kuaifan
44732f5849 no message 2022-05-21 00:04:33 +08:00
kuaifan
a6150821e7 perf: 消息也推送给在其它地方登录的自己 2022-05-20 23:54:13 +08:00
kuaifan
4f5dce282b fix: 邮件通知消息未读对象可能会出错的情况 2022-05-20 23:51:54 +08:00
kuaifan
68a7b55320 umeng推送参数附上离线推送 2022-05-20 19:33:17 +08:00
kuaifan
40ff85304b no message 2022-05-20 18:18:59 +08:00
kuaifan
02de3a1680 no message 2022-05-20 15:55:54 +08:00
kuaifan
7a8fcad27c perf: 聊天输入框支持粘贴文件 2022-05-20 15:23:10 +08:00
kuaifan
35daeb443c no message 2022-05-20 12:00:14 +08:00
kuaifan
22993782ed build 2022-05-20 11:13:02 +08:00
kuaifan
4c2b88a13f no message 2022-05-20 11:05:02 +08:00
kuaifan
7a168977da perf: 优化UserAvatar组件 2022-05-20 10:23:24 +08:00
kuaifan
2e03cee726 perf: 上传或发送图片太大时压缩显示 2022-05-20 10:22:38 +08:00
kuaifan
74b1194398 perf: 仪表盘任务数量、最近打开的任务 2022-05-20 10:22:08 +08:00
kuaifan
88163499fe perf: 优化消息移动端打开动画效果 2022-05-19 12:18:23 +08:00
kuaifan
c0ead0b891 更新onlyoffice版本 2022-05-19 11:35:27 +08:00
kuaifan
6b6cb7473e 更新onlyoffice样式 2022-05-19 11:27:40 +08:00
kuaifan
e0f41f9330 no message 2022-05-18 17:17:17 +08:00
kuaifan
95dcfc961f no message 2022-05-18 16:57:40 +08:00
kuaifan
0178f4b8b5 添加隐私政策 2022-05-18 15:35:29 +08:00
kuaifan
85c4eae2a8 no message 2022-05-18 15:08:28 +08:00
kuaifan
168e5cea45 no message 2022-05-18 13:40:53 +08:00
kuaifan
0f5e393340 no message 2022-05-18 13:39:28 +08:00
kuaifan
41faf72c37 no message 2022-05-18 13:38:25 +08:00
kuaifan
6cf2279d21 no message 2022-05-17 19:57:48 +08:00
kuaifan
6347454098 no message 2022-05-17 19:55:47 +08:00
kuaifan
3f17c0689c perf: 未读消息邮件提醒,提醒时把所有未读消息都加上,而不是只提示指定时间的 2022-05-17 19:30:20 +08:00
kuaifan
e4524ff2c5 fix: 未读消息邮件头像不显示的问题 2022-05-17 18:45:59 +08:00
kuaifan
94e4054cd4 no message 2022-05-17 18:27:20 +08:00
kuaifan
a4a7939eae perf: 优化modal内滚动会传播给其他组件的问题 2022-05-17 18:26:17 +08:00
kuaifan
63fa9eb9e4 perf: 优化任务过多加载卡的情况 2022-05-17 15:43:20 +08:00
kuaifan
9e449fa4ee no message 2022-05-17 14:57:48 +08:00
kuaifan
ca7cd2372a perf: 点击聊天输入框窗口跳动 2022-05-17 14:57:18 +08:00
kuaifan
f4502a5e9d no message 2022-05-17 13:44:14 +08:00
kuaifan
040f1e8c86 移动端取消Tooltip提示 2022-05-17 13:44:07 +08:00
kuaifan
a9c10a3e0b 优化移动客户端登录逻辑 2022-05-17 11:52:34 +08:00
kuaifan
8522b4610d 群聊每条信息显示发送人位置给默认高度 2022-05-17 11:52:01 +08:00
kuaifan
f9d7d4faec build 2022-05-16 20:21:22 +08:00
kuaifan
bc4477cd86 no message 2022-05-16 19:30:51 +08:00
kuaifan
4d6fb6f2ca no message 2022-05-16 17:16:38 +08:00
kuaifan
dafa00c895 build 2022-05-16 14:19:21 +08:00
kuaifan
e8dc71c71f no message 2022-05-16 14:01:06 +08:00
kuaifan
3755402f37 build 2022-05-15 23:02:13 +08:00
kuaifan
8d876c60b2 fix: 修复手机客户端无法预览文件的问题 2022-05-15 22:58:35 +08:00
kuaifan
157e89b5ea perf: 支持上传plist格式文件 2022-05-15 22:58:14 +08:00
kuaifan
5fcc4c730f no message 2022-05-15 22:46:56 +08:00
kuaifan
6e8ee60df0 perf: DrawerOverlay 使用 Model 2022-05-15 22:46:45 +08:00
kuaifan
6610a948c5 no message 2022-05-15 18:20:43 +08:00
kuaifan
5ff1f57017 fix: 客户端选择sso登录输入相同地址时提交无反应的问题 2022-05-15 18:13:19 +08:00
kuaifan
8a870a361d perf: 手机客户端登录页优化sso登录样式 2022-05-15 18:12:42 +08:00
kuaifan
72e0f9b798 perf: 优化手机客户端登录页切换主题提示 2022-05-15 18:11:52 +08:00
kuaifan
04bc5d7d17 fix: 推送收到的群组名称为空的情况 2022-05-14 22:14:05 +08:00
kuaifan
3e19f3991c build 2022-05-13 22:48:45 +08:00
kuaifan
5e324c69b6 no message 2022-05-13 20:38:47 +08:00
kuaifan
bba34ef55e no message 2022-05-13 19:35:26 +08:00
kuaifan
99f9431794 catch Exception 改为 Throwable 2022-05-13 15:58:48 +08:00
kuaifan
279f05fd11 fix: 任务开始邮件提醒错误的问题 2022-05-13 15:57:28 +08:00
kuaifan
8f58797256 no message 2022-05-13 15:25:29 +08:00
kuaifan
6dbbcff0b6 no message 2022-05-12 17:51:33 +08:00
kuaifan
37578cf4a0 no message 2022-05-12 15:10:28 +08:00
kuaifan
a13bc3f0bd perf: 优化消息列表 2022-05-12 15:10:21 +08:00
kuaifan
1d4602db0b 优化移动端滑动返回 2022-05-12 15:08:58 +08:00
kuaifan
ece17b2aee fix: ios键盘遮挡输入框的问题 2022-05-12 08:31:51 +08:00
kuaifan
117fd62c44 优化移动端 2022-05-12 00:15:09 +08:00
kuaifan
4da73dd144 优化移动端 2022-05-11 19:14:01 +08:00
kuaifan
05df135229 no message 2022-05-11 09:59:32 +08:00
kuaifan
8e2a649d57 优化移动端 2022-05-10 18:56:43 +08:00
kuaifan
d2428e43b2 perf: 优化移动端 2022-05-09 08:33:09 +08:00
kuaifan
d03a6f7b00 no message 2022-05-06 16:34:48 +08:00
kuaifan
c1b218c133 no message 2022-05-06 16:32:44 +08:00
kuaifan
b3286c8f74 no message 2022-05-06 12:14:11 +08:00
kuaifan
44b46c396b no message 2022-05-06 07:41:11 +08:00
kuaifan
042fb28ccc no message 2022-05-05 22:18:06 +08:00
kuaifan
2206e0f1e6 no message 2022-05-05 22:15:23 +08:00
kuaifan
632f68660b feat: 邮件通知未读消息 2022-05-05 19:24:17 +08:00
kuaifan
43531d9b8d no message 2022-05-05 08:20:04 +08:00
kuaifan
d28fb9a27a no message 2022-05-04 18:16:03 +08:00
kuaifan
c0fadde527 perf: 优化聊天输入框计算样式 2022-04-29 16:06:55 +08:00
kuaifan
663661c73c perf: 优化正则表达式 2022-04-29 16:06:38 +08:00
kuaifan
4304b66b06 Merge remote-tracking branch 'A_github_pro/master' 2022-04-28 14:52:29 +08:00
kuaifan
99b789bc11 增加php backtrack_limit,解决聊天粘贴大图发送失败的问题 2022-04-28 14:52:10 +08:00
kuaifan
4bbd20b8c0 build 2022-04-28 08:50:27 +08:00
kuaifan
43c77726bb no message 2022-04-28 07:46:32 +08:00
kuaifan
3ceb183d8b no message 2022-04-28 07:37:54 +08:00
kuaifan
46787b187f no message 2022-04-27 16:15:03 +08:00
kuaifan
7b8a6c2a4c perf: 移交项目和任务时记录被移交对象 2022-04-27 16:00:55 +08:00
kuaifan
7b757d68b4 no message 2022-04-27 15:48:09 +08:00
kuaifan
092506e9ab perf: 共享的文件禁止移动到另一个共享文件夹内 2022-04-27 15:47:53 +08:00
kuaifan
529b4bec31 no message 2022-04-27 08:06:38 +08:00
kuaifan
2e88e933c3 no message 2022-04-27 08:03:36 +08:00
kuaifan
031dac36e6 fix: 修复共享文件移动到共享文件夹内共享属性错乱的问题 2022-04-27 08:03:17 +08:00
kuaifan
dc3b666635 调整office映射文件 2022-04-27 07:20:11 +08:00
kuaifan
e45bcf34a3 perf: 优化自定义sso登录 2022-04-27 07:05:50 +08:00
kuaifan
0aa7679a71 build 2022-04-26 11:18:27 +08:00
kuaifan
a46ac38561 fix: 同时删除多个任务负责人或协助人员任务动态显示错误的问题 2022-04-26 11:12:52 +08:00
kuaifan
b1395377a1 fix: 修复 ETooltip 组件 disabled 取消后错位的问题 2022-04-26 10:27:50 +08:00
kuaifan
41e60ee990 perf: 优化文件历史查看 2022-04-26 09:11:01 +08:00
kuaifan
00f80e8db8 no message 2022-04-26 08:45:04 +08:00
kuaifan
f70da2c4a2 perf: 查看文件修改历史时文本编辑器、图表点击编辑历史窗口不隐藏 2022-04-25 22:04:58 +08:00
kuaifan
ee485020d1 fix: 添加任务窗口选择其他项目无效的问题 2022-04-25 21:54:12 +08:00
kuaifan
8cdbb969ff 一键编译所有generic 2022-04-25 21:39:53 +08:00
kuaifan
687b9ca8b1 no message 2022-04-25 09:53:28 +08:00
kuaifan
f486477a41 no message 2022-04-25 08:15:12 +08:00
kuaifan
6f7acc60b5 no message 2022-04-25 08:12:27 +08:00
kuaifan
c748d8f5f6 perf: 优化任务详情右键预览图片 2022-04-25 07:58:17 +08:00
kuaifan
5d1d108811 fix: 修复上传文件夹不立即显示的问题 2022-04-25 07:19:16 +08:00
kuaifan
d8d49d6b5a no message 2022-04-25 05:10:27 +08:00
kuaifan
ebb3ec6784 feat: 新增查看文件历史版本 2022-04-25 05:04:21 +08:00
kuaifan
ab66b70485 no message 2022-04-25 05:03:52 +08:00
kuaifan
a723c2a44a perf: 文件打开保存机制 2022-04-24 14:38:38 +08:00
kuaifan
d0c7ee5e47 fix: 修复已打开文件需刷新网页才显示最新内容的情况 2022-04-22 15:55:56 +08:00
kuaifan
3c386eeaa9 perf: 客户端升级日志 2022-04-22 15:18:40 +08:00
kuaifan
e5c4faf6ef no message 2022-04-22 09:11:15 +08:00
kuaifan
0274a7f6e6 no message 2022-04-22 08:38:30 +08:00
kuaifan
92632cf294 no message 2022-04-22 08:23:14 +08:00
kuaifan
864af174f7 office开启强制保存按钮 2022-04-22 08:23:03 +08:00
kuaifan
b1cbf6f893 优化头像上传 2022-04-22 07:43:47 +08:00
kuaifan
0b0e6951e5 优化高度计算 2022-04-22 07:05:20 +08:00
kuaifan
52392e2e0a perf: 发送消息未设置昵称的优化 2022-04-21 21:18:48 +08:00
kuaifan
493d05f807 build 2022-04-21 20:56:35 +08:00
kuaifan
4ae7f22293 Merge remote-tracking branch 'A_github_pro/master' 2022-04-21 20:55:00 +08:00
kuaifan
204bdcd2a9 新增manifest预取 URL声明 2022-04-21 20:10:33 +08:00
kuaifan
c185b4c9c2 perf: 优化共享文件夹图标 2022-04-21 19:50:23 +08:00
kuaifan
7d48f9bde5 fix: 修复打开pdf因为文件名内容出错的问题 2022-04-21 17:25:00 +08:00
kuaifan
a48a1210c8 perf: 优化重复共享提示 2022-04-21 16:31:28 +08:00
kuaifan
6b2b072e16 no message 2022-04-20 18:44:11 +08:00
kuaifan
8acf3b21c7 perf: 优化聊天窗口群聊已读列表 2022-04-20 16:13:12 +08:00
kuaifan
0ad32ff63d perf: 优化任务窗口 2022-04-20 16:12:19 +08:00
kuaifan
4c595affa5 feat: 新增聊天选择内容粗体、斜体、删除线、序号等工具 2022-04-20 08:42:19 +08:00
kuaifan
4f267c6d89 no message 2022-04-20 08:35:30 +08:00
kuaifan
fae46bc2b9 no message 2022-04-20 06:56:32 +08:00
kuaifan
81f8a01cc1 no message 2022-04-20 06:39:30 +08:00
kuaifan
a6be0dc079 no message 2022-04-19 20:39:41 +08:00
kuaifan
0e4017f69a build 2022-04-19 08:07:00 +08:00
kuaifan
505f3b8b05 perf: 优化暗黑模式 2022-04-19 08:03:21 +08:00
kuaifan
65f53e264c no message 2022-04-19 00:40:35 +08:00
kuaifan
7c5e9f9b75 优化聊天表情样式 2022-04-19 00:36:30 +08:00
kuaifan
87933c821f no message 2022-04-18 22:18:28 +08:00
kuaifan
b590bce3e1 no message 2022-04-18 22:02:25 +08:00
kuaifan
cb3174f7d8 no message 2022-04-18 21:09:36 +08:00
kuaifan
7b3d9d9402 perf: 客户端填写周报后保存关闭窗口 2022-04-18 21:07:50 +08:00
kuaifan
5b66d32f25 perf: 文件浏览保存排序 2022-04-18 20:54:48 +08:00
kuaifan
2686bfbf5e no message 2022-04-18 18:36:38 +08:00
kuaifan
9553e43fa8 no message 2022-04-18 17:37:03 +08:00
kuaifan
b5a46ca63a build 2022-04-18 17:11:07 +08:00
kuaifan
7435fa69f2 no message 2022-04-18 17:06:01 +08:00
kuaifan
ea7d889d99 no message 2022-04-18 08:18:26 +08:00
kuaifan
b4bd635dbb no message 2022-04-18 08:12:07 +08:00
kuaifan
a066fc7eae no message 2022-04-18 00:16:21 +08:00
kuaifan
11ae7b091a no message 2022-04-17 22:37:38 +08:00
kuaifan
8cbc3a9667 feat: 新增聊天表情 2022-04-17 22:14:33 +08:00
kuaifan
f5b663b900 no message 2022-04-16 15:45:31 +08:00
kuaifan
572415d089 no message 2022-04-16 12:57:31 +08:00
kuaifan
8893254664 perf: 优化@提醒 2022-04-16 09:57:09 +08:00
kuaifan
d5ac6fc0c7 no message 2022-04-15 23:23:46 +08:00
kuaifan
64e53b3dd0 perf: 群聊信息预览显示发言人昵称 2022-04-15 22:09:26 +08:00
kuaifan
1de42f5c72 no message 2022-04-15 21:12:39 +08:00
kuaifan
5c3d8727e0 no message 2022-04-15 17:08:55 +08:00
kuaifan
c6e693392c no message 2022-04-15 13:13:34 +08:00
kuaifan
522ca02b36 perf: 优化聊天输入框 2022-04-15 09:22:52 +08:00
kuaifan
8c05d8791d no message 2022-04-15 00:17:59 +08:00
kuaifan
8df3611e7d no message 2022-04-14 23:59:16 +08:00
kuaifan
428d2eb795 no message 2022-04-14 23:52:16 +08:00
kuaifan
74c20fc57e 优化群动作通知 2022-04-14 23:32:58 +08:00
kuaifan
6ca13bf263 优化群聊操作 2022-04-14 23:00:28 +08:00
kuaifan
fcd6b1ddec feat: 新增自定义创建群聊功能 2022-04-14 22:00:45 +08:00
kuaifan
ac1e7bc186 no message 2022-04-14 07:58:53 +08:00
kuaifan
8a0f53aaaf perf: 优化任务窗口标题偶尔出现显示不全的情况 2022-04-14 07:16:31 +08:00
kuaifan
473dbb8361 no message 2022-04-14 07:05:15 +08:00
kuaifan
7aa3989b11 perf: 调整操作自己的规则 2022-04-14 07:05:04 +08:00
kuaifan
04d121367a no message 2022-04-13 20:45:23 +08:00
kuaifan
7f79795c55 feat: 新增离职操作 2022-04-13 20:06:17 +08:00
kuaifan
f76016860f no message 2022-04-13 14:57:30 +08:00
kuaifan
6cd3449571 fix: 修复导出统计数据不准确的情况 2022-04-13 14:57:19 +08:00
kuaifan
c480be4e1d perf: 统一指定标签样式 2022-04-13 14:14:50 +08:00
kuaifan
e3583df260 perf: 群聊显示发件人昵称 2022-04-13 13:55:23 +08:00
kuaifan
fd40ae6b67 perf: 任务详情窗口通过附件传图片保存到附件列表 2022-04-13 12:01:31 +08:00
kuaifan
003ddb4016 no message 2022-04-12 15:59:28 +08:00
kuaifan
be755a6f4e no message 2022-04-12 15:45:57 +08:00
kuaifan
91c62ee55e no message 2022-04-12 14:50:19 +08:00
kuaifan
17dc434350 no message 2022-04-12 12:04:54 +08:00
kuaifan
ad46fd1aae perf: 客户端隐藏顶部加载进度条 2022-04-12 11:53:33 +08:00
kuaifan
9ffa46a878 fix: 修复文件协作不提醒的问题 2022-04-12 11:36:44 +08:00
kuaifan
b1ee3fe3cd perf: 优化文件浏览查看路径 2022-04-12 10:52:35 +08:00
kuaifan
86886ded16 no message 2022-04-08 08:05:52 +08:00
kuaifan
6c44abded9 build 2022-04-08 00:29:12 +08:00
kuaifan
f3fb777924 perf: 搜索后支持快速取消筛选 2022-04-07 23:23:04 +08:00
kuaifan
1323bba420 fix: 工作包括编辑内容不正确的问题 2022-04-07 22:51:43 +08:00
kuaifan
e55a1d8713 fix: 会员选择框偶尔出现默认值错误的情况 2022-04-07 22:51:12 +08:00
kuaifan
d951a6057d perf: 优化工作汇报的搜索 2022-04-07 19:45:03 +08:00
kuaifan
9755c59687 perf: 新增邮件发送测试 2022-04-07 14:27:54 +08:00
kuaifan
2918c55fa9 build 2022-04-07 11:15:32 +08:00
kuaifan
f01f5d7837 win版本更新提示文案不是重启,是立即安装 2022-04-07 10:19:40 +08:00
kuaifan
01313b16e1 perf: 优化用户邮箱验证 2022-04-07 09:05:32 +08:00
kuaifan
601d037201 perf: 优化任务到期前后邮件提醒 2022-04-07 08:29:49 +08:00
kuaifan
9bc56f5d17 no message 2022-04-07 07:44:56 +08:00
kuaifan
8237cd21ed no message 2022-04-02 22:45:58 +08:00
kuaifan
bb3f1f2c10 no message 2022-04-02 22:45:10 +08:00
kuaifan
f8463823d9 perf: 消息已完成图标布局优化 2022-04-02 08:45:04 +08:00
kuaifan
08c46fd66e fix: 部分客户端登录页面报错的问题 2022-04-02 08:44:28 +08:00
kuaifan
1939d42d09 fix: 退出登录仍出现未读数的情况 2022-04-02 08:43:55 +08:00
kuaifan
9a0a04ed76 perf: 优化websocket连接机制 2022-04-02 08:19:13 +08:00
kuaifan
813a49bf67 no message 2022-04-01 16:22:13 +08:00
kuaifan
77924ff248 no message 2022-04-01 15:22:48 +08:00
kuaifan
9dad51fa0b no message 2022-04-01 14:43:22 +08:00
kuaifan
8c39a81644 no message 2022-04-01 13:10:59 +08:00
kuaifan
7749fac683 优化更新提示 2022-04-01 12:27:12 +08:00
kuaifan
91f4b2fd9d no message 2022-04-01 11:40:35 +08:00
kuaifan
e0a3259765 no message 2022-04-01 10:13:41 +08:00
kuaifan
38c1a768fc feat: 客户端新增系统托盘图标 2022-04-01 09:18:48 +08:00
kuaifan
3c71af064c no message 2022-04-01 07:14:57 +08:00
kuaifan
7841f54db8 防止多开 2022-04-01 07:14:45 +08:00
kuaifan
936dee9da5 perf: 文件权限提示点击确定返回主目录 2022-04-01 06:26:52 +08:00
kuaifan
a10d5ee43b no message 2022-04-01 06:17:42 +08:00
kuaifan
a0b8529606 no message 2022-04-01 05:53:16 +08:00
kuaifan
6bf1eb5bde 优化暗黑样式 2022-03-31 23:08:31 +08:00
kuaifan
fb1b5969f5 build 2022-03-31 22:42:58 +08:00
kuaifan
8eca06fd5f perf: 优化聊天窗口显示头像 2022-03-31 22:26:47 +08:00
kuaifan
3c8642f30f perf: 优化聊天窗口滚动 2022-03-31 21:48:23 +08:00
kuaifan
3796a3dbde perf: 取消任务群聊发送图片同步到任务附件 2022-03-31 20:43:55 +08:00
kuaifan
637104643c 优化样式 2022-03-31 20:23:53 +08:00
kuaifan
9364cfe1cf no message 2022-03-31 19:13:56 +08:00
kuaifan
4ae66ee3aa no message 2022-03-31 19:11:30 +08:00
kuaifan
a6534518f8 perf: 优化项目页面聊天窗口 2022-03-31 19:10:03 +08:00
kuaifan
e327311477 perf: 优化网络重连聊天机制 2022-03-31 19:09:33 +08:00
kuaifan
78c766c52e fix: 已完成任务还可以拖动排序的问题 2022-03-31 18:47:24 +08:00
kuaifan
3c89ecb2c7 fix: 移动端不显示甘特图的问题 2022-03-31 17:58:28 +08:00
kuaifan
948972184e no message 2022-03-31 15:17:48 +08:00
kuaifan
8874a3fec7 no message 2022-03-31 15:15:23 +08:00
kuaifan
dde28136e1 perf: 文件查看图片直接弹窗浏览 2022-03-31 15:12:45 +08:00
kuaifan
ce03296078 整理下载文件跳转 2022-03-31 13:55:57 +08:00
kuaifan
457efa1c79 整理下载文件跳转 2022-03-31 13:53:15 +08:00
kuaifan
cfd1fc275b no message 2022-03-31 09:59:49 +08:00
kuaifan
9d56dd122f no message 2022-03-31 09:57:03 +08:00
kuaifan
b1e35e6824 build 2022-03-31 09:46:38 +08:00
kuaifan
775fdec0b8 perf: 文件共享成员支持分享链接 2022-03-31 09:44:52 +08:00
kuaifan
f403014ef6 no message 2022-03-31 09:42:41 +08:00
kuaifan
5982424864 no message 2022-03-30 18:01:00 +08:00
kuaifan
2ad5ccabec no msg 2022-03-30 17:53:55 +08:00
kuaifan
9ffc3520e7 优化下载提示 2022-03-30 17:29:12 +08:00
kuaifan
7ba28a9770 build 2022-03-30 08:28:12 +08:00
kuaifan
bec72dfd5f no msg 2022-03-30 08:20:41 +08:00
kuaifan
cf6b4f5432 perf: 优化提示此文件夹内已有共享文件夹 2022-03-30 08:20:24 +08:00
kuaifan
fe82c44687 perf: 支持上传golang文件 2022-03-30 07:40:13 +08:00
kuaifan
49e4f15bd1 perf: 文件新增pids(上级ID递归)字段 2022-03-30 07:35:25 +08:00
kuaifan
7dd85b2ba6 recovery appId 2022-03-30 06:25:20 +08:00
kuaifan
7b64793449 no message 2022-03-30 00:14:03 +08:00
kuaifan
5485f2013e no message 2022-03-29 22:41:48 +08:00
kuaifan
6ed24ec310 no message 2022-03-29 21:00:50 +08:00
kuaifan
e39335864d no message 2022-03-29 19:16:27 +08:00
kuaifan
27ec2c276a no message 2022-03-29 18:42:54 +08:00
kuaifan
b2f8da500b no message 2022-03-29 14:48:36 +08:00
kuaifan
80a21dbf4a no message 2022-03-25 10:48:31 +08:00
kuaifan
33bd39859d no msg 2022-03-25 10:47:15 +08:00
kuaifan
7367a769b1 fix: 设置分页10条每页无效的问题 2022-03-25 10:04:10 +08:00
kuaifan
1ca4c67f7e vue-kityminder-gg 更换 vue-kityminder-ggg 2022-03-25 10:03:28 +08:00
kuaifan
120a67159d no message 2022-03-24 18:32:36 +08:00
kuaifan
aa808587df no message 2022-03-24 11:56:45 +08:00
kuaifan
57df991dd3 优化 goForward 2022-03-24 11:55:54 +08:00
kuaifan
665184bfa2 fix: 客户端打开不自动登录的问题 2022-03-24 11:55:43 +08:00
kuaifan
ceee696443 no message 2022-03-24 11:48:58 +08:00
kuaifan
7fda376f19 perf: 优化一些前端 2022-03-24 11:48:04 +08:00
kuaifan
bd60cb3a18 build 2022-03-23 15:28:49 +08:00
kuaifan
534ed6ae6c no message 2022-03-23 15:27:37 +08:00
kuaifan
acac93bbd1 fix: 修复新增项目成员无法通过邮箱搜索的问题 2022-03-23 15:09:57 +08:00
kuaifan
070e4b8527 build 2022-03-23 12:00:25 +08:00
kuaifan
a4e8761add 客户端不需要显示首页 2022-03-23 11:59:19 +08:00
kuaifan
590b76a884 perf: 优化工作量配色 2022-03-23 11:52:03 +08:00
kuaifan
98729cfa7b fix: Public客户端打开空白的情况 2022-03-23 10:40:02 +08:00
kuaifan
2bd077136f perf: 优化路由重复提示的报错 2022-03-23 10:21:17 +08:00
kuaifan
6fba94594b fix: 所有可搜索列表在非第1页搜索时不返回第1页的问题 2022-03-23 10:12:37 +08:00
kuaifan
24e9ff4c86 fix: 查看已发送的工作汇报,汇报对象需横向显示 2022-03-23 10:11:31 +08:00
kuaifan
379357ee8e no message 2022-03-18 10:59:44 +08:00
kuaifan
ef7a64644b no msg 2022-03-18 10:06:11 +08:00
kuaifan
c163c20c3d no message 2022-03-17 23:06:45 +08:00
kuaifan
3ddbeac419 build 2022-03-17 22:06:59 +08:00
kuaifan
ef7a2ec123 no msg 2022-03-17 22:00:44 +08:00
kuaifan
8dd4cfa6b2 优化团队管理搜索功能 2022-03-17 22:00:36 +08:00
kuaifan
7491a6faac 优化字段 2022-03-17 21:41:17 +08:00
kuaifan
eaf1a59e89 优化预览图片 2022-03-17 21:15:53 +08:00
kuaifan
4cbc5040a2 perf: 任务详细描述右键新增预览图片 2022-03-17 21:15:18 +08:00
kuaifan
b90b129d98 no msg 2022-03-17 17:52:07 +08:00
kuaifan
64c0ae2734 优化首页 2022-03-17 17:51:56 +08:00
kuaifan
1c2a7a219a 优化路径命名 2022-03-17 14:03:34 +08:00
kuaifan
868b8e7206 perf: 优化图片预览,优化与弹窗esc按键冲突的问题 2022-03-17 10:53:30 +08:00
kuaifan
6115828dfe build 2022-03-16 17:18:19 +08:00
kuaifan
af9b5ab014 perf: 图片预览使用当前页组件,支持多图 2022-03-16 17:15:39 +08:00
kuaifan
5497bd97b2 优化甘特图 2022-03-16 16:13:01 +08:00
kuaifan
c53db63cc7 perf: 优化工作报告前端 2022-03-16 15:46:47 +08:00
kuaifan
1263603c7b 优化客户端工作报告 2022-03-16 10:24:36 +08:00
kuaifan
d1036c3be7 合并整理 2022-03-16 09:04:00 +08:00
kuaifan
5adbd6e8f1 perf: 优化甘特图 2022-03-16 07:21:21 +08:00
kuaifan
b83ce02849 perf: 优化任务列表切换显示 2022-03-15 17:38:02 +08:00
韦荣超
735a9c8a5d build: bf46881e3b 2022-03-15 16:46:32 +08:00
韦荣超
bf46881e3b fix: 项目-任务列表tab优化 2022-03-15 16:44:35 +08:00
韦荣超
f68226f4b4 fix: 项目-任务列表tab修改,甘特图数据优化 2022-03-15 15:46:45 +08:00
韦荣超
07d864e48b build: 0326864510 2022-03-15 10:45:07 +08:00
韦荣超
0326864510 fix: 项目--任务列表删除主任务后,子任务仍显示问题 2022-03-15 10:42:40 +08:00
韦荣超
2a97060b96 build: 6cffd81540 2022-03-14 18:19:29 +08:00
韦荣超
6cffd81540 fix: 项目任务甘特图图标及优化移动逻辑 2022-03-14 18:17:23 +08:00
Xuronglong
eb2b11ed3e perf: 更新icon图标库
perf: 更新icon图标库
2022-03-14 17:29:06 +08:00
Xuronglong
7cb52bebf5 perf: 更新icon图标库
perf: 更新icon图标库
2022-03-14 17:16:47 +08:00
韦荣超
3731f5d32c build: 7161b14631 2022-03-14 14:43:22 +08:00
韦荣超
7161b14631 fix: 项目任务列表甘特图鼠标悬停图标或文案时样式变为手指 2022-03-14 14:41:04 +08:00
韦荣超
2e7ff88be6 fix: 项目任务列表甘特图去掉'已完成'任务及样式优化 2022-03-14 14:34:29 +08:00
韦荣超
6368ee5c12 build: 42106e907d 2022-03-14 12:16:46 +08:00
韦荣超
42106e907d fix: 项目--删除任务详情删除前有聊天记录显示异常修改 2022-03-14 12:14:49 +08:00
韦荣超
3f71162e9d build: 008d8de24e 2022-03-14 09:36:01 +08:00
韦荣超
008d8de24e fix: 修改项目任务列表甘特图时长及颜色显示不对问题 2022-03-14 09:34:30 +08:00
韦荣超
e3000dcc7c build: 15ecd8d592 2022-03-11 19:13:16 +08:00
韦荣超
f98cf4cbfc Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	public/css/app.css
#	public/js/app.js
#	public/js/build/341.js.LICENSE.txt
#	public/js/build/712.js.LICENSE.txt
#	public/js/build/904.js.LICENSE.txt
2022-03-11 19:11:27 +08:00
韦荣超
807f885f92 build: 15ecd8d592 2022-03-11 19:06:27 +08:00
韦荣超
15ecd8d592 feat: 项目任务新增一个甘特图展示选项 2022-03-11 19:04:51 +08:00
mmppkka
588a6f1bf4 build:f633bc81cf874c71f45c7d6d0c3b10aa59316087 2022-03-11 18:34:19 +08:00
mmppkka
f633bc81cf fix:官网首页修改 2022-03-11 18:31:49 +08:00
韦荣超
3006dc6584 build: a7c63e22e5 2022-03-11 18:23:58 +08:00
韦荣超
a7c63e22e5 perf: 已删除任务详情任务描述改为只读 2022-03-11 18:21:52 +08:00
韦荣超
4304158088 build: c1fb67f143 2022-03-11 17:48:32 +08:00
韦荣超
0f446a1f77 Merge remote-tracking branch 'origin/develop' into develop 2022-03-11 17:44:22 +08:00
mmppkka
71639a91b9 build:b0395a6ed22e87064e3e4458b9573e8acaf5679d 2022-03-11 17:44:08 +08:00
韦荣超
c1fb67f143 perf: 已删除任务操作文案及显示优化 2022-03-11 17:42:47 +08:00
mmppkka
b0395a6ed2 fix:修改图片 2022-03-11 17:40:08 +08:00
mmppkka
7d0e6ac50b build:c6ea52482133f98312f6e82bbc624088e3096e03 2022-03-11 17:36:33 +08:00
mmppkka
c6ea524821 fix:首页重写 2022-03-11 17:34:53 +08:00
韦荣超
7c82769448 build: a4b69c3911 2022-03-11 12:08:12 +08:00
韦荣超
a4b69c3911 feat: 客户端登录,新增工作报告、修改工作报告、查看工作报告,全部直接在新窗口打开 2022-03-11 12:06:36 +08:00
韦荣超
e301abe9e3 build: d38ccba618 2022-03-11 10:00:49 +08:00
韦荣超
d38ccba618 perf: 项目--删除任务查看详情页功能 2022-03-11 09:59:29 +08:00
韦荣超
849ac56296 build: 67bb821a0e 2022-03-10 18:59:48 +08:00
韦荣超
67bb821a0e perf: 首页兼容暗黑模式及文案和查询优化 2022-03-10 18:57:53 +08:00
kuaifan
43c51d48d9 合并优化 2022-03-10 17:45:17 +08:00
kuaifan
48b7f4924e perf: 优化消息标记已读/未读 2022-03-10 14:41:29 +08:00
kuaifan
30c149be31 Merge branch 'develop' of ssh://git.gezi.vip:6006/gx/dootask
# Conflicts:
#	electron/package.json
#	package.json
#	public/css/app.css
#	public/js/app.js
#	public/js/build/189.js.LICENSE.txt
#	public/js/build/206.js
#	public/js/build/400.js.LICENSE.txt
#	public/js/build/486.js
#	public/js/build/496.js.LICENSE.txt
#	public/js/build/528.js
#	public/js/build/535.js
#	public/js/build/535.js.LICENSE.txt
#	public/js/build/578.js
#	public/js/build/78.js.LICENSE.txt
#	public/js/build/817.js
#	public/js/build/856.js
#	public/js/build/862.js
#	public/js/build/889.js
#	public/js/build/91.js.LICENSE.txt
#	public/js/build/954.js
#	public/js/build/956.js
#	resources/assets/js/pages/manage/components/TaskDetail.vue
#	resources/assets/js/pages/manage/dashboard.vue
#	resources/assets/sass/pages/common.scss
#	resources/assets/sass/pages/page-dashboard.scss
2022-03-10 11:27:00 +08:00
kuaifan
42d9271ea0 build 2022-03-09 21:26:05 +08:00
韦荣超
00e666e423 build: 6b29b686c7 2022-03-09 18:05:47 +08:00
韦荣超
6b29b686c7 feat: 在项目设置里新增一个“已删除任务”菜单 2022-03-09 18:02:49 +08:00
韦荣超
db99a21514 perf: 修改验证邮件有效期改回24小时 2022-03-09 15:04:04 +08:00
韦荣超
6fb1c474d2 build: f1c7c35e48 2022-03-09 12:07:04 +08:00
韦荣超
f1c7c35e48 perf: 优化邮件设置页面样式 2022-03-09 12:04:24 +08:00
韦荣超
923549bb5e perf: 修改验证邮件有效期为10分钟方便测试(验证完后改为24小时) 2022-03-09 11:51:42 +08:00
韦荣超
0aa18ded72 perf: 【邮箱验证】链接过期的提示文案为:链接已失效,请重新登录/注册 2022-03-09 11:45:11 +08:00
韦荣超
ee08d8d740 perf: 任务修改计划时间需要重置任务邮件提醒日志 2022-03-09 10:45:05 +08:00
韦荣超
0c23fa7c9d build: fe91765ab0 2022-03-09 09:53:34 +08:00
韦荣超
fe91765ab0 fix: 优化验证邮箱页面文案 2022-03-09 09:52:00 +08:00
韦荣超
8541e180cc fix: 修正验证邮箱页面文案 2022-03-09 09:39:22 +08:00
kuaifan
a3d950e2a3 fix: 修复自定义SSO自动升级版本出错的问题 2022-03-09 00:42:00 +08:00
kuaifan
e1c80636ba 显示最后一个加入的成员 2022-03-08 19:32:32 +08:00
kuaifan
4fb971a935 fix: 添加任务时开始时间和结束时间为同一天可能发生报错 2022-03-08 18:42:14 +08:00
kuaifan
117d0fbcef perf: 任务附件过多时仅显示最新50个 2022-03-08 18:34:20 +08:00
kuaifan
389cafc240 优化有时候任务详情标题未显示完全 2022-03-08 18:22:10 +08:00
kuaifan
4591b59465 fix: 拖动任务列表排序后会自动还原的情况 2022-03-08 18:05:16 +08:00
kuaifan
d4a082382d perf: 消息已读/未读人员优化 2022-03-08 16:24:52 +08:00
kuaifan
885437de8d feat: 优化表格分页样式 2022-03-08 16:05:25 +08:00
kuaifan
39cd9f4a44 feat: 优化TableAction组件 2022-03-08 14:36:20 +08:00
kuaifan
01a2244fed feat: 头像加载失败时显示名称首字 2022-03-08 14:11:07 +08:00
韦荣超
e0e3dd128e perf: 消息添加'未读标记'字段 2022-03-08 13:09:12 +08:00
韦荣超
0f6408d7f6 fix: 任务第二次邮件提醒判断错误修改 2022-03-08 12:25:36 +08:00
韦荣超
b610dc4969 build: bc6fa7fd27 2022-03-08 09:08:40 +08:00
韦荣超
bc6fa7fd27 feat: 消息右键对话新增:标记已读、标记未读 2022-03-08 09:06:38 +08:00
kuaifan
0e34cc49df no message 2022-03-08 08:15:34 +08:00
kuaifan
629881d16b no message 2022-03-08 07:05:18 +08:00
kuaifan
92569f5b3a no message 2022-03-08 00:37:17 +08:00
kuaifan
4ff32511b9 perf: 添加任务默认选中自己,如果不选则添加无负责人任务 2022-03-08 00:29:10 +08:00
kuaifan
5afed4b85e perf: 优化消息未读数 2022-03-07 18:07:02 +08:00
kuaifan
2aa687a40b perf: 优化置顶后数据请求 2022-03-07 17:48:34 +08:00
kuaifan
57567ebb1b perf: 优化未读信息数 2022-03-07 17:48:17 +08:00
kuaifan
6448169caa perf: 优化首页仪表盘样式 2022-03-07 17:47:59 +08:00
韦荣超
e5901f28e3 build: 2d3f0dd95a 2022-03-07 17:04:47 +08:00
韦荣超
2d3f0dd95a fix: '最近打开任务'数据没有根据用户区分问题修改 2022-03-07 17:03:29 +08:00
kuaifan
2e4e827887 新增focus方法 2022-03-07 15:24:48 +08:00
kuaifan
22704b32d6 perf: 优化关闭任务独立窗口点击取消后没有自动获取焦点 2022-03-07 15:24:19 +08:00
韦荣超
2839e595cc build: a9f28dfb47 2022-03-07 14:40:23 +08:00
韦荣超
a9f28dfb47 perf: 邮箱验证优化流程提示 2022-03-07 14:38:40 +08:00
韦荣超
c1bdd079cf build: 467d05a358 2022-03-07 11:56:37 +08:00
韦荣超
467d05a358 fix: 去掉调试信息 2022-03-07 11:54:30 +08:00
韦荣超
05c8a6e664 build: 59b2ecebda 2022-03-07 11:51:21 +08:00
韦荣超
59b2ecebda feat: 添加“最近打开的任务” 2022-03-07 11:49:29 +08:00
韦荣超
fe8cf38a7f perf: 任务提醒缩短邮件通知时间区间 2022-03-07 10:22:14 +08:00
kuaifan
e754ceace8 build 2022-03-07 01:42:30 +08:00
kuaifan
41ceb9bd82 perf: 项目、消息对话置顶后要滚动到可以看到它的位置 2022-03-07 01:35:12 +08:00
kuaifan
b0b7c63561 perf: 再次点击滚动到未读条目 2022-03-07 01:19:36 +08:00
kuaifan
c13f65611f perf: 优化新窗口打开的任务保存机制 2022-03-07 01:15:10 +08:00
kuaifan
b9feeabfb3 perf: 优化新消息等列表滚动 2022-03-07 00:35:00 +08:00
kuaifan
8e9ba7a2d2 no msg 2022-03-06 16:10:47 +08:00
kuaifan
b3e9b1c2be perf: 完成任务时任务暂时继续显示,直到路由发生改变 2022-03-06 13:57:56 +08:00
kuaifan
e969b5b7e4 perf: 优化仪表盘使用sticky方式 2022-03-06 13:56:18 +08:00
kuaifan
8e0a684a9a build 2022-03-04 19:45:10 +08:00
kuaifan
152aa0f92b fix: 清空子任务的时间报错闪现的问题 2022-03-04 19:43:54 +08:00
韦荣超
97fb0ba95a perf: 邮箱发送失败提示优化 2022-03-04 18:39:30 +08:00
韦荣超
01355b9a28 去掉查看符合发邮件数据(测试) 2022-03-04 17:55:24 +08:00
韦荣超
772922cefb 查看符合发邮件数据(测试) 2022-03-04 17:51:34 +08:00
kuaifan
95a3aa7290 no message 2022-03-04 17:40:15 +08:00
韦荣超
6f8a4af5eb fix: 任务提醒时间格式错误修改 2022-03-04 17:27:35 +08:00
韦荣超
b332c4e23d perf: 任务提醒时间区间放大,防止定时器出现意外发送不到问题 2022-03-04 16:57:13 +08:00
韦荣超
47c0c3fa3f fix: 任务统计导出完成时间为空时,不应出现实际完成用时 2022-03-04 16:38:08 +08:00
kuaifan
71cb8612d8 fix: 客户端任务独立窗口修改详情后没有同步到主窗口的问题 2022-03-04 16:35:59 +08:00
kuaifan
af12aecd36 perf: 网络不好连续按回车导致重复添加子任务 2022-03-04 16:18:06 +08:00
kuaifan
fec116f131 fix: 客户端任务独立窗口无法按command+s保存任务的问题 2022-03-04 16:17:18 +08:00
kuaifan
d1fdec0970 no message 2022-03-04 16:06:22 +08:00
kuaifan
77b6c53a42 perf: 优化任务详情数据结构 2022-03-04 15:50:38 +08:00
韦荣超
f2eaac50a6 fix: 任务提醒时间区间错误修改 2022-03-04 14:38:20 +08:00
韦荣超
ec8b0ada14 fix: 任务列表缺少参数报错修改 2022-03-04 14:27:45 +08:00
韦荣超
1e28be9671 build: 2975d550eb 2022-03-04 14:02:04 +08:00
韦荣超
8a0e813734 build: 2975d550eb 2022-03-04 14:01:55 +08:00
韦荣超
2975d550eb fix: 导出任务统计修改 2022-03-04 14:00:38 +08:00
韦荣超
248a9c4070 perf: 补全系统设置中的未翻译地方 2022-03-04 10:25:06 +08:00
韦荣超
327d0d20a9 fix: 登录账号密码错误时提示修改 2022-03-04 09:59:55 +08:00
韦荣超
1ffd2812dc perf: 修改导出报表文案 2022-03-04 09:57:54 +08:00
韦荣超
29da4d209d build: 35a369a953 2022-03-04 09:43:41 +08:00
韦荣超
35a369a953 feat: 任务到期提醒开启邮件通知 2022-03-04 09:41:36 +08:00
kuaifan
4785cae8f0 Merge branch 'master' into develop
# Conflicts:
#	electron/package.json
#	package.json
#	public/js/app.js
#	public/js/build/221.js
#	public/js/build/501.js
#	public/js/build/747.js
#	public/js/build/862.js
#	public/js/build/984.js
2022-03-04 08:59:28 +08:00
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
60086ead7c 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/153.js
#	public/js/build/189.js
#	public/js/build/189.js.LICENSE.txt
#	public/js/build/206.js
#	public/js/build/486.js
#	public/js/build/501.js
#	public/js/build/528.js
#	public/js/build/544.js
#	public/js/build/601.js
#	public/js/build/747.js
#	public/js/build/756.js.LICENSE.txt
#	public/js/build/762.js
#	public/js/build/836.js
#	public/js/build/889.js
#	public/js/build/954.js
#	public/js/build/956.js.LICENSE.txt
2022-03-03 16:32:39 +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
韦荣超
63ac99e906 perf: 报表导出列及剩余天数优化 2022-03-03 15:03:41 +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
韦荣超
57c4f935e2 fix: 邮箱验证模板页'你'改为'您' 2022-03-03 14:11:31 +08:00
kuaifan
f4e4252227 perf: 优化已读回执 2022-03-03 11:41:54 +08:00
韦荣超
84bbcf7753 fix: 邮箱设置异常报错修改 2022-03-03 11:20:09 +08:00
韦荣超
df129f0d20 build: a76da167eb 2022-03-03 10:49:23 +08:00
韦荣超
a76da167eb feat: 完成邮箱验证 2022-03-03 10:47:40 +08:00
韦荣超
1547b3f53b build: 68f5b30f7d 2022-03-02 18:13:29 +08:00
韦荣超
68f5b30f7d feat: 邮箱验证部分 2022-03-02 18:11:19 +08:00
韦荣超
d9b9c0f42c build: aa0597c27e 2022-03-02 15:46:23 +08:00
韦荣超
aa0597c27e perf: 补全任务'测试'状态样式 2022-03-02 15:45:05 +08:00
韦荣超
4d59cd1521 fix: 报表导出任务没有流程日志判断优化 2022-03-02 15:36:11 +08:00
韦荣超
6ba4170f08 build: 827bc97e8e 2022-03-02 15:21:36 +08:00
韦荣超
827bc97e8e perf: 导出报表调整 2022-03-02 15:19:48 +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
54d215b8d1 Merge branch 'develop' of ssh://git.gezi.vip:6006/gx/dootask 2022-03-02 08:30:33 +08:00
kuaifan
4cd47a5c77 Merge branch 'master' into develop 2022-03-02 08:28:24 +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
韦荣超
420245c630 build: 20cf83907d 2022-03-01 15:54:39 +08:00
韦荣超
20cf83907d fix: 处理回滚后异常代码 2022-03-01 15:52:46 +08:00
韦荣超
0a04d2ee83 fix: 处理回滚后异常代码 2022-03-01 15:47:36 +08:00
韦荣超
bc05781edd build: ce9d91e990 2022-03-01 15:08:02 +08:00
韦荣超
ce9d91e990 fix: 【系统设置】邮件设置提前小时数双向绑定无效问题修改 2022-03-01 15:06:39 +08:00
韦荣超
973d13b705 build: 51e4427108 2022-03-01 14:21:47 +08:00
韦荣超
51e4427108 build: 【系统设置】邮件设置任务提醒时间只能是0.5的倍数 2022-03-01 14:19:44 +08:00
韦荣超
d09ab541ab build: a9e1dc3cd5 2022-03-01 11:04:20 +08:00
韦荣超
a9e1dc3cd5 perf: 仪表盘当前激活的卡片不明显优化 2022-03-01 11:02:28 +08:00
韦荣超
1551e6c640 build: 03a1bdfc7f 2022-03-01 09:39:12 +08:00
韦荣超
03a1bdfc7f perf: 置顶样式优化 2022-03-01 09:37:25 +08:00
韦荣超
24545aa9cb build: cf7e7c1a06 2022-02-28 18:17:10 +08:00
韦荣超
c11426419c build: cf7e7c1a06 2022-02-28 18:17:01 +08:00
韦荣超
cf7e7c1a06 feat: 管理员系统设置新增:新增邮件设置 2022-02-28 18:15:17 +08:00
kuaifan
ab388f1ef5 Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	public/css/app.css
#	public/js/app.js
#	public/js/build/248.js
#	public/js/build/248.js.LICENSE.txt
#	public/js/build/501.js
#	public/js/build/510.js.LICENSE.txt
#	public/js/build/581.js.LICENSE.txt
#	public/js/build/601.js
#	public/js/build/712.js
#	public/js/build/747.js
#	resources/assets/js/pages/manage/file.vue
#	resources/assets/sass/pages/page-manage.scss
2022-02-28 10:26:12 +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
韦荣超
ff59f31e84 build: 011fc8cc9e 2022-02-25 15:58:28 +08:00
韦荣超
011fc8cc9e perf: 首页判断是否登录优先于判断是否需要启动首页 2022-02-25 15:57:03 +08:00
韦荣超
7428022b2f build: 3815eb1983 2022-02-25 15:41:32 +08:00
韦荣超
3815eb1983 perf: 优化首页判断时间一闪而过问题 2022-02-25 15:40:17 +08:00
韦荣超
3778f10edb perf: 添加首页文字翻译 2022-02-25 15:31:26 +08:00
韦荣超
00a4b867ea build: 3a1024e848 2022-02-25 15:18:58 +08:00
韦荣超
3a1024e848 fix: 首页360浏览器图片函数摆错修改 2022-02-25 15:17:33 +08:00
kuaifan
ea8e1e9c57 优化样式 2022-02-25 15:14:20 +08:00
kuaifan
478d63893b fix: 无法浏览聊天图片的问题 2022-02-25 14:38:40 +08:00
韦荣超
3066631301 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	app/Http/Controllers/Api/SystemController.php
#	resources/assets/sass/pages/components/dialog-wrapper.scss
2022-02-25 10:57:28 +08:00
韦荣超
b0998d5f95 build: a074cb9664 2022-02-25 10:56:20 +08:00
韦荣超
a074cb9664 fix: 回滚代码后功能完善 2022-02-25 10:54:20 +08:00
韦荣超
43a3d1e5ac build: 24c8a4a9d1 2022-02-25 09:57:25 +08:00
韦荣超
24c8a4a9d1 fix: 项目列表处于置顶选中状态显示选中样式 2022-02-25 09:55:16 +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
韦荣超
313fc09c8d build: 5610486a89 2022-02-24 18:16:15 +08:00
韦荣超
5610486a89 perf: 调整消息置顶标识位置 2022-02-24 18:14:45 +08:00
韦荣超
9f0231405a build: e650dd0503 2022-02-24 18:12:38 +08:00
韦荣超
e650dd0503 perf: 消息列表详情增加'置顶'标识 2022-02-24 18:10:50 +08:00
韦荣超
6fc6cb75e4 build: 184efc6f27 2022-02-24 17:38:00 +08:00
韦荣超
184efc6f27 fix: 合并异常代码修改 2022-02-24 17:35:55 +08:00
韦荣超
1117b57101 Merge remote-tracking branch 'origin/dev' into develop
# Conflicts:
#	app/Http/Controllers/Api/SystemController.php
#	public/js/language.all.js
#	resources/assets/js/pages/login.vue
#	resources/assets/js/pages/manage.vue
#	resources/assets/js/pages/manage/calendar.vue
#	resources/assets/js/pages/manage/components/Calendar.vue
#	resources/assets/js/pages/manage/setting/system/setting.vue
#	resources/assets/js/store/state.js
#	resources/assets/statics/public/js/language.all.js
2022-02-24 17:18:33 +08:00
韦荣超
0928ba71f2 feat: 首页启动设置 2022-02-24 17:11:05 +08:00
韦荣超
7170eded68 build: 32e891d6ba 2022-02-24 14:38:24 +08:00
韦荣超
32e891d6ba perf: 项目列表置顶优化 2022-02-24 14:37:17 +08:00
韦荣超
5a273676fb build: c3f6edf7c5 2022-02-24 14:03:29 +08:00
韦荣超
c3f6edf7c5 perf: 项目列表置顶优化 2022-02-24 14:02:12 +08:00
韦荣超
e0c0cc8613 build: fbeb3dd81e 2022-02-24 11:50:34 +08:00
韦荣超
fbeb3dd81e perf: 【文件】剪切后加'取消剪切'按钮 2022-02-24 11:48:59 +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: e7efaed08a 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: f9cc2ceb11 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: d0a4473e2b 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: 96587a4e45 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: 695fb60aa4 2022-02-22 17:43:08 +08:00
韦荣超
695fb60aa4 perf: 任务聊天中发送图片时,回车可确定发送 2022-02-22 17:39:22 +08:00
韦荣超
da20fafa39 build: d6a9ecd912 2022-02-22 17:20:47 +08:00
韦荣超
d6a9ecd912 fix: 项目列表滚动'置顶'框隐藏 2022-02-22 17:19:08 +08:00
韦荣超
f95d721c9c build: 69d6417985 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: ccb889233c 2022-02-18 19:20:42 +08:00
韦荣超
9d1a9f3134 build: ccb889233c 2022-02-18 19:20:35 +08:00
韦荣超
ccb889233c fix: 【文件】右键多选所有文件复选框显示,取消所有选中消失 2022-02-18 19:19:05 +08:00
韦荣超
939f2cbf97 build: 4bd8835f23 2022-02-18 18:50:38 +08:00
韦荣超
580e0cb36a build: 4bd8835f23 2022-02-18 18:50:29 +08:00
韦荣超
4bd8835f23 fix: 消息:列表滚动隐藏'置顶'文案 2022-02-18 18:48:43 +08:00
韦荣超
22f32da0c5 build: aa8a094383 2022-02-18 18:42:49 +08:00
韦荣超
aa8a094383 fix: 文件:列表模式右键后已选内容会错乱修复 2022-02-18 18:40:39 +08:00
韦荣超
4a72b7f089 build: a33e4905cf 2022-02-18 17:03:18 +08:00
韦荣超
a33e4905cf fix: 消息:列表滚动右键Y轴值判断错误修复 2022-02-18 17:01:33 +08:00
韦荣超
40bd2f0742 build: 1fd73fe79e 2022-02-18 16:45:55 +08:00
韦荣超
1fd73fe79e fix: 消息:列表滚动在任意位置右键菜单错位问题修复 2022-02-18 16:43:27 +08:00
韦荣超
fd7d3e06f4 build: 7a4d27da69 2022-02-18 16:17:41 +08:00
韦荣超
7a4d27da69 perf: 【文件】多个选择剪切功能与右键剪切重复,数据处理应该合拼;方格列表默认不显示复选框,右键菜单新增一个多选菜单 2022-02-18 16:14:54 +08:00
韦荣超
afbadf7d81 build: 4f2c0e94d9 2022-02-18 14:02:22 +08:00
韦荣超
7b65c64431 build: 4f2c0e94d9 2022-02-18 14:02:00 +08:00
韦荣超
4f2c0e94d9 perf: 优化注册提示 2022-02-18 13:59:40 +08:00
韦荣超
7593d7a3e9 build: 3400d1e803 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: 1521d1e883 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: 1521d1e883 2022-02-18 11:23:38 +08:00
韦荣超
1521d1e883 fix: 【文件】流程图只读接入新组件及删除旧组件引入代码 2022-02-18 11:21:56 +08:00
韦荣超
0412615f6e build: 2bd666efe7 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: 2dcbba63cb 2022-02-17 15:22:31 +08:00
韦荣超
2dcbba63cb fix: 【消息】置顶会话在子类tab中排序错误修改 2022-02-17 15:20:47 +08:00
韦荣超
5e86bdfda7 build: 4f74a0440c 2022-02-17 14:43:21 +08:00
韦荣超
4f74a0440c perf: 【注册】校验参数是否合法顺序优化 2022-02-17 14:41:49 +08:00
韦荣超
858709f610 build: 6689d48fad 2022-02-17 10:44:23 +08:00
韦荣超
6689d48fad perf: 【消息】列表置顶会话加背景颜色 2022-02-17 10:42:28 +08:00
韦荣超
8581a7c308 build: 9878efb198 2022-02-17 09:37:08 +08:00
韦荣超
9878efb198 perf: 【消息】列表取消置顶 2022-02-17 09:35:32 +08:00
韦荣超
9855c50367 build: 04c59041e0 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: 2c1b944b7c 2022-02-14 13:42:45 +08:00
韦荣超
2c1b944b7c fix: 【文件】新版流程图右侧及底部被隐藏问题修改 2022-02-14 13:40:37 +08:00
kuaifan
0e66ca148d build 2022-02-13 14:10:06 +08:00
kuaifan
ce70c1ca3a perf: 单条消息最长2000个字符,超过自动分割发送,总最长20000 2022-02-12 17:01:12 +08:00
韦荣超
56b91a59a2 Merge remote-tracking branch 'origin/develop' into develop 2022-02-12 16:02:04 +08:00
kuaifan
8f1fbcb19e no message 2022-02-12 15:10:30 +08:00
韦荣超
a4799a1bc0 “新版本流程图文件” 2022-02-12 15:04:11 +08:00
韦荣超
cc24ff22e9 perf: 去掉刷新提示及前端报错 2022-02-12 15:02:10 +08:00
kuaifan
72ca335c4c perf: 下载、查看任务文件权限改为所有项目成员 2022-02-12 14:24:36 +08:00
kuaifan
0ef6476e58 perf: 任务详细描述取消文件上传 2022-02-12 14:15:23 +08:00
kuaifan
6ba63d1466 fix: 修复聊天mp4文件无法预览的问题 2022-02-12 14:14:18 +08:00
kuaifan
f9d28e1b6b perf: 优化任务详情拖动发送文件 2022-02-12 13:49:30 +08:00
韦荣超
63534d3eb5 feat: 更新流程图组件 2022-02-12 11:54:56 +08:00
韦荣超
2ab2cf01db “新版本流程图文件” 2022-02-12 11:51:37 +08:00
kuaifan
2fc039dd70 perf: 优化通知 2022-02-12 09:50:02 +08:00
韦荣超
230d1a1f86 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	app/Http/Controllers/Api/FileController.php
#	public/js/build/208.js
#	public/js/build/252.js
#	public/js/build/400.js
#	public/js/build/76.js
2022-02-11 11:01:05 +08:00
kuaifan
f7921bf388 build 2022-02-10 21:18:18 +08:00
kuaifan
feed984ba8 perf: 兼容iPad端 2022-02-10 21:03:36 +08:00
kuaifan
5324861f16 perf: 手动切换账号提示“项目不存在或不在成员列表内”的情况 2022-02-10 20:34:13 +08:00
kuaifan
ae80939d2e perf: 任务详情 2022-02-10 20:33:35 +08:00
kuaifan
c117e4b087 fix: 修复文件上传一直出现loading的情况 2022-02-10 20:33:14 +08:00
kuaifan
96580e2284 perf: 该文件版本已经改变了。该页面将被重新加载 2022-02-10 20:12:17 +08:00
kuaifan
bc3932c8b8 perf: 点击切换语言一级菜单出现的兼容问题 2022-02-10 17:10:18 +08:00
kuaifan
f7cd4f34d3 fix: 已存在的任务新添加负责人不出现在任务群聊里 2022-02-10 17:09:44 +08:00
kuaifan
3a76f51707 fix: 初次安装失败的情况 2022-02-10 16:41:07 +08:00
kuaifan
041ba8f2ed 优化onlyoffice样式 2022-02-09 21:52:04 +08:00
kuaifan
17e5d15b1b build 2022-02-09 21:50:12 +08:00
kuaifan
a6ba59eac0 perf: 上传文件夹 2022-02-09 21:04:18 +08:00
kuaifan
e89ff02b59 fix: 客户端偶尔出现无法打开文件的情况 2022-02-09 21:04:03 +08:00
kuaifan
b28be29dc8 build 2022-02-09 17:29:36 +08:00
kuaifan
9ad85e01de perf: 该文件版本已经改变了。该页面将被重新加载 2022-02-09 17:27:59 +08:00
kuaifan
d49790ba78 perf: 团队管理新增身份筛选项 2022-02-09 17:09:40 +08:00
kuaifan
598f01de95 no message 2022-02-09 16:59:48 +08:00
kuaifan
992e137339 perf: 任务文件支持更多格式上传 2022-02-09 16:59:42 +08:00
kuaifan
124666cca6 优化系统设置路由菜单 2022-02-09 16:13:13 +08:00
kuaifan
848359bf7b feat: 添加项目支持默认工作流 2022-02-09 16:05:25 +08:00
kuaifan
fb24af1900 feat: 新增自定义添加项目时的项目模板 2022-02-09 16:05:02 +08:00
韦荣超
3d6df3cc09 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	app/Http/Controllers/Api/DialogController.php
#	app/Http/Controllers/Api/ProjectController.php
#	app/Models/Project.php
#	electron/package.json
#	package.json
#	public/css/app.css
#	public/js/app.js
#	public/js/build/146.js
#	public/js/build/188.js
#	public/js/build/188.js.LICENSE.txt
#	public/js/build/244.js
#	public/js/build/244.js.LICENSE.txt
#	public/js/build/278.js
#	public/js/build/278.js.LICENSE.txt
#	public/js/build/30.js
#	public/js/build/423.js
#	public/js/build/43.js
#	public/js/build/525.js
#	public/js/build/644.js.LICENSE.txt
#	public/js/build/660.js
#	public/js/build/766.js
#	public/js/build/831.js
#	public/js/build/893.js
#	public/js/build/919.js
#	public/js/build/919.js.LICENSE.txt
#	public/js/build/934.js
#	public/js/build/934.js.LICENSE.txt
#	resources/assets/js/pages/manage/components/DialogList.vue
#	resources/assets/js/pages/manage/components/DialogWrapper.vue
#	resources/assets/js/pages/manage/components/TaskAdd.vue
#	resources/assets/sass/components/report.scss
2022-02-07 10:50:38 +08:00
kuaifan
ea58ed46f2 doc 2022-01-29 16:10:31 +08:00
kuaifan
32817a4275 build 2022-01-29 16:04:28 +08:00
kuaifan
95033cd5b7 perf: 优化项目筛选工作流 2022-01-29 16:02:49 +08:00
kuaifan
9999548bc2 perf: 优化撤回消息 2022-01-29 13:39:16 +08:00
kuaifan
3d04bd4444 build 2022-01-28 18:29:15 +08:00
kuaifan
21e618cca2 优化了一些代码 2022-01-28 18:27:47 +08:00
kuaifan
02eb386155 撤回消息优化 2022-01-28 15:09:57 +08:00
kuaifan
35bd038802 Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	app/Http/Controllers/Api/DialogController.php
#	app/Http/Controllers/Api/ProjectController.php
#	electron/package.json
#	package.json
#	public/css/app.css
#	public/docs/assets/main.bundle.js
#	public/js/app.js
#	public/js/build/146.js.LICENSE.txt
#	public/js/build/161.js
#	public/js/build/199.js
#	public/js/build/199.js.LICENSE.txt
#	public/js/build/218.js
#	public/js/build/218.js.LICENSE.txt
#	public/js/build/244.js
#	public/js/build/244.js.LICENSE.txt
#	public/js/build/309.js
#	public/js/build/423.js
#	public/js/build/43.js
#	public/js/build/693.js
#	public/js/build/717.js
#	public/js/build/717.js.LICENSE.txt
#	public/js/build/79.js.LICENSE.txt
#	resources/assets/js/pages/manage/components/TaskAdd.vue
2022-01-28 14:39:43 +08:00
kuaifan
1ff59aee56 perf: 优化修改工作流未保存关闭提示 2022-01-28 13:25:51 +08:00
kuaifan
d64fae1832 perf: 查看工作报告图片显示不全的问题 2022-01-28 13:12:11 +08:00
kuaifan
ae169810d0 perf: 优化UserInput组件 2022-01-28 13:04:54 +08:00
kuaifan
3c7619098a build 2022-01-28 01:20:36 +08:00
kuaifan
a881bfd63b fix: 客户端编辑文件不显示协助成员 2022-01-28 01:12:22 +08:00
kuaifan
09d3131d46 fix: 添加任务时选择任务组无效 2022-01-28 00:12:51 +08:00
kuaifan
53550b8975 fix: 工作报告弹窗被遮挡的问题 2022-01-27 23:55:13 +08:00
kuaifan
569164ed56 fix: 客户端任务窗口首次聊天失败的问题 2022-01-27 23:44:06 +08:00
kuaifan
f61d79d53e 客户端office查看修改窗口隐藏自带的标题 2022-01-27 23:28:38 +08:00
kuaifan
bba28d6b57 fix: 文件右键打开失效 2022-01-27 23:17:41 +08:00
Mr.Huan
469f30044a build: 0993d87799 2022-01-27 17:12:58 +08:00
Mr.Huan
0993d87799 fix: 修复工作汇报正文被图片撑开页面的问题 2022-01-27 17:11:48 +08:00
Mr.Huan
09fa33236f build: 4e9abd6512 2022-01-27 16:33:25 +08:00
Mr.Huan
4e9abd6512 perf: 开放文件夹移动功能 2022-01-27 16:31:58 +08:00
Mr.Huan
2996c0b38e build: 36e366abe0 2022-01-27 15:58:26 +08:00
Mr.Huan
36e366abe0 perf: 优化文件模块用户体验 2022-01-27 15:55:44 +08:00
Mr.Huan
f9c6c6c127 build: 34772ef2bf 2022-01-27 14:35:17 +08:00
Mr.Huan
34772ef2bf perf: 文件增加取消选择按钮 2022-01-27 14:33:08 +08:00
Mr.Huan
305af935a7 feat: 文件支持批量移动 2022-01-27 14:24:01 +08:00
kuaifan
33f3c9acbf build 2022-01-27 12:25:10 +08:00
kuaifan
9e560c79ae 优化聊天消息附件支持预览 2022-01-27 12:17:30 +08:00
kuaifan
705d7f3da0 perf: 聊天消息附件支持预览 2022-01-27 00:44:49 +08:00
kuaifan
08372facd7 fix: 移动端无法上传任务文件的问题 2022-01-26 19:11:16 +08:00
kuaifan
886baa427b perf: 仪表盘鼠标滑过时间显示完整时间 2022-01-26 18:12:21 +08:00
kuaifan
8ff94bc138 fix: 任务中没有聊天记录时,发送图片无法成功 2022-01-26 18:05:53 +08:00
Mr.Huan
d0438390cc build: d22266a947 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: 4516bce0ee 2022-01-26 17:14:58 +08:00
Mr.Huan
4516bce0ee fix: 修复个人对话为空时无法重复打开该对话的问题 2022-01-26 17:13:20 +08:00
Mr.Huan
4853fbcec3 build: 72e5f9a83e 2022-01-26 15:04:03 +08:00
Mr.Huan
72e5f9a83e fix: 修复消息撤回文字提示在第一条时会被顶部遮住的问题 2022-01-26 15:02:56 +08:00
Mr.Huan
1a1ddc34a2 build: 40ebfb676c 2022-01-26 14:46:37 +08:00
Mr.Huan
40ebfb676c perf: 头像上传图片浏览组件增加空提醒 2022-01-26 14:43:29 +08:00
kuaifan
3bb7e958dc perf: macOS角标首次不显示的问题 2022-01-26 11:46:11 +08:00
Mr.Huan
19dd16fcf0 docs: 补充消息撤回功能API文档 2022-01-26 09:48:35 +08:00
kuaifan
f596749645 Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	electron/package.json
#	package.json
#	public/css/app.css
#	public/js/app.js
#	public/js/build/120.js
#	public/js/build/120.js.LICENSE.txt
#	public/js/build/146.js.LICENSE.txt
#	public/js/build/161.js
#	public/js/build/284.js
#	public/js/build/309.js
#	public/js/build/400.js
#	public/js/build/644.js
#	public/js/build/644.js.LICENSE.txt
#	public/js/build/79.js.LICENSE.txt
#	resources/assets/js/pages/manage/components/TaskAdd.vue
2022-01-26 09:35:16 +08:00
kuaifan
482813ea88 build 2022-01-26 01:33:53 +08:00
kuaifan
60e47e85a3 perf: 添加任务时如果自己不是任务负责人可选择加入协助人员列表 2022-01-26 01:12:00 +08:00
kuaifan
22c06fee5e 剔除模式代码优化 2022-01-26 00:02:15 +08:00
kuaifan
4a42ce87a2 feat: 工作流程负责人新增剔除模式(改变任务负责人并保留操作状态的人员) 2022-01-25 23:46:47 +08:00
kuaifan
a98e4dbcd4 perf: 文件列表模式右键重命名无效 2022-01-25 23:12:42 +08:00
kuaifan
7baa37ccd1 fix: macOS客户端首次不加载角标的问题 2022-01-25 23:06:08 +08:00
kuaifan
417017add9 优化编辑器皮肤 2022-01-25 23:04:51 +08:00
Mr.Huan
b5e3cc2503 build: 2e11fe2b58 2022-01-25 16:51:32 +08:00
Mr.Huan
2e11fe2b58 feat: 文件网格模式支持批量删除文件 2022-01-25 16:49:54 +08:00
Mr.Huan
70f1258bab feat: 文件表格支持批量删除文件 2022-01-25 16:49:54 +08:00
韦荣超
d91fa33330 fix: 工作流列表接口用作筛选时不用传多余参数 2022-01-25 16:28:15 +08:00
kuaifan
8f0b5dc049 Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	app/Http/Controllers/Api/DialogController.php
#	electron/package.json
#	package.json
#	public/css/app.css
#	public/js/app.js
#	public/js/build/146.js
#	public/js/build/146.js.LICENSE.txt
#	public/js/build/178.js
#	public/js/build/178.js.LICENSE.txt
#	public/js/build/199.js
#	public/js/build/199.js.LICENSE.txt
#	public/js/build/309.js
#	public/js/build/328.js.LICENSE.txt
#	public/js/build/388.js
#	public/js/build/43.js
#	public/js/build/46.js.LICENSE.txt
#	public/js/build/857.js
#	public/js/build/857.js.LICENSE.txt
#	public/js/build/893.js
2022-01-25 16:12:53 +08:00
kuaifan
7f14a82053 perf: 任务群聊中拖拽文件或者照片时并未有确认窗口 2022-01-25 16:06:26 +08:00
kuaifan
b05db6d458 fix: 主任务负责人无法修改子任务负责人的问题 2022-01-25 15:49:22 +08:00
kuaifan
ea87092d73 fix: 设置主任务时间跟子任务没有交集时子任务时间改为跟主任务一致 2022-01-25 15:28:42 +08:00
kuaifan
f468f7ae27 build 2022-01-25 15:05:46 +08:00
kuaifan
f12c79d292 pref: 待处理时进入动态中点击重置,重置提示中的参数为空,点击确定后,提示流程不存在或者删除 2022-01-25 15:04:03 +08:00
kuaifan
6e76514a24 pref: 添加子任务时输入框为空,回车时提示的图标错误 2022-01-25 15:03:43 +08:00
kuaifan
88f3d3821e fix: 搜索后,点击查看已归档的任务,仍然显示这个搜索框 2022-01-25 15:03:24 +08:00
kuaifan
bed2e20a91 fix: 修复归档任务无法查看日志的问题 2022-01-25 14:50:39 +08:00
kuaifan
c7700bdfef perf: 上传文件夹应该保持目录结构 2022-01-25 14:46:52 +08:00
Mr.Huan
778f6367f9 fix: 动态中点击重置按钮后流程的字体颜色和背景颜色并未正确显示 2022-01-25 11:00:59 +08:00
Mr.Huan
6115eea401 build: ff62ef729a 2022-01-25 09:39:03 +08:00
Mr.Huan
ff62ef729a perf: 支持xmind,rp格式文件上传 2022-01-25 09:37:45 +08:00
kuaifan
d0a432164d build 2022-01-25 02:06:27 +08:00
kuaifan
0f8d9e64ef no message 2022-01-25 01:30:55 +08:00
kuaifan
d1f5096a16 删除luckysheet 2022-01-25 00:32:51 +08:00
kuaifan
003de399dc 支持在线编辑代码 2022-01-25 00:24:47 +08:00
kuaifan
36989ce5ff feat: 新增文本文件编辑功能 2022-01-24 20:33:27 +08:00
Mr.Huan
fa987b4e30 build: b47c494240 2022-01-24 15:50:32 +08:00
Mr.Huan
b47c494240 fix: 修复任务负责人选择弹窗不随页面滚动的问题 2022-01-24 15:35:04 +08:00
kuaifan
1ab8a19f8e fix: 客户端无法下载聊天文件的问题 2022-01-24 15:18:35 +08:00
Mr.Huan
a31134195b build: 5580a4ee3d 2022-01-24 15:08:38 +08:00
Mr.Huan
5580a4ee3d fix: 修复添加任务变更栏目后无效果的问题 2022-01-24 15:07:01 +08:00
kuaifan
870276fa48 feat: 支持文件下载 2022-01-24 14:42:29 +08:00
Mr.Huan
608660a101 build: 25cb8015d0 2022-01-24 11:56:07 +08:00
Mr.Huan
25cb8015d0 perf: 注册页面增加密码格式提醒 2022-01-24 11:54:47 +08:00
Mr.Huan
e686e8f58c build: 14b699b5b1 2022-01-24 10:39:13 +08:00
Mr.Huan
14b699b5b1 perf: 消息撤回后直接删除消息 2022-01-24 10:37:18 +08:00
韦荣超
ce0c86c8e5 build: 11d1f26724 2022-01-24 10:22:45 +08:00
韦荣超
11d1f26724 feat: 加入【项目管理】任务列表流程筛选被回滚功能 2022-01-24 10:20:59 +08:00
Mr.Huan
a8c0978f0d build: 0a0227cca4 2022-01-24 09:31:52 +08:00
Mr.Huan
f643911014 feat: 聊天消息撤回-后端实现 2022-01-24 09:30:10 +08:00
Mr.Huan
0a0227cca4 feat: 聊天消息撤回-前端实现 2022-01-24 09:30:10 +08:00
kuaifan
8bacc3b6ba Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	electron/package.json
#	package.json
#	public/js/app.js
#	public/js/build/161.js
#	public/js/build/400.js
#	public/js/build/616.js
#	public/js/build/845.js
2022-01-24 01:45:08 +08:00
kuaifan
d516330a41 feat: 聊天粘贴发送文件、图片时预览确认 2022-01-24 01:30:10 +08:00
kuaifan
2d83faf144 build 2022-01-24 00:42:58 +08:00
kuaifan
c3fd6bf88f 优化日志显示 2022-01-24 00:40:14 +08:00
kuaifan
2a646becfd 优化项目简介样式 2022-01-24 00:33:31 +08:00
kuaifan
a825657516 fix: 修改主任务时间时未设置时间的子任务没有同步修改 2022-01-24 00:10:47 +08:00
kuaifan
b71dbe9832 perf: 任务详情窗口截止时间24小时倒计时 2022-01-24 00:05:36 +08:00
kuaifan
f06b4040bc 支持查看已归档任务 2022-01-23 23:55:56 +08:00
kuaifan
563cd4b843 优化已归档任务列表 2022-01-23 23:44:39 +08:00
kuaifan
834dc9bec9 支持查看已归档任务 2022-01-23 23:33:16 +08:00
kuaifan
e6e58a03a6 perf: 修改任务或修改项目后同步对话信息 2022-01-23 17:58:32 +08:00
kuaifan
d9f4adbe26 支持查看归档任务 2022-01-23 17:43:15 +08:00
kuaifan
1777153411 perf: 支持查看已归档任务详情 2022-01-23 17:36:19 +08:00
kuaifan
9b0ca581f1 perf: 消息列表显示任务基本状态 2022-01-23 16:17:11 +08:00
kuaifan
493cf7d46a perf: 优化消息对话排序 2022-01-23 15:01:58 +08:00
kuaifan
004bf36dc1 perf: 隐藏无聊天内容的对话 2022-01-23 14:51:32 +08:00
kuaifan
fcf7fb4b9f perf: 消息页再次点击类型定位到未读消息 2022-01-23 14:48:10 +08:00
kuaifan
130a85a5fc fix: flow文件路径引用错误导致无法编译的问题 2022-01-23 13:16:25 +08:00
Mr.Huan
d46eec568c build: 6529e45868 2022-01-22 15:39:25 +08:00
Mr.Huan
7dc6d0ffb2 build: 6529e45868 2022-01-22 15:28:37 +08:00
Mr.Huan
6529e45868 fix: 修复工作汇报选择接收人组件在调整窗体大小时发生位移的问题 2022-01-22 15:26:42 +08:00
Mr.Huan
3ba6ea9c7e perf: 调整周报签名的生成方法 2022-01-22 15:07:37 +08:00
kuaifan
b6ff6f5453 Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	public/js/app.js
#	public/js/build/657.js
#	public/js/build/782.js
#	public/js/build/845.js
2022-01-22 14:18:32 +08:00
Mr.Huan
cfa529b742 build: 2eabc76c1a 2022-01-22 13:53:13 +08:00
Mr.Huan
2eabc76c1a fix: 修复周报点击编辑关闭后再点击"新建汇报"按钮导致用户列表无法重新加载的问题 2022-01-22 13:51:03 +08:00
韦荣超
463e67d64c fix:【工作汇报】修复新增今天的周报,提示要覆盖昨天日报的问题 2022-01-22 12:06:21 +08:00
韦荣超
1f96af1024 build: 563aa92958 2022-01-22 10:37:27 +08:00
韦荣超
563aa92958 fix: 【工作汇报】收到的汇报中,未读时发送人删除掉汇报对象,汇报对象的未读数量仍然未减少 2022-01-22 10:35:45 +08:00
kuaifan
f09b864e30 perf: 仅客户端或Chrome浏览器支持主题功能 2022-01-22 09:07:13 +08:00
kuaifan
7ce15d5a71 优化代码 2022-01-21 23:31:01 +08:00
kuaifan
cc767b164e 优化工作报告 2022-01-21 23:16:50 +08:00
kuaifan
230ff75c0b 优化搜索栏前端 2022-01-21 21:56:27 +08:00
kuaifan
da39739fb5 优化工作报告前端 2022-01-21 21:49:27 +08:00
kuaifan
c33be22057 Merge branch 'develop' of ssh://git.gezi.vip:6006/gx/dootask into develop
# Conflicts:
#	resources/assets/js/pages/manage/components/ReportDetail.vue
#	resources/assets/js/pages/manage/components/ReportEdit.vue
2022-01-21 21:04:28 +08:00
kuaifan
902f35d21b 优化前端代码 2022-01-21 21:02:17 +08:00
韦荣超
d8426780d2 build: a1aaf90d2e 2022-01-21 21:01:08 +08:00
韦荣超
a1aaf90d2e fix: 【工作报告】新增报告初始化数据不导入已有时间点数据 2022-01-21 20:59:28 +08:00
韦荣超
53ed4d4072 build: da75554447 2022-01-21 19:59:44 +08:00
韦荣超
da75554447 fix: 【工作报告】详情页面前端报错修改 2022-01-21 19:58:03 +08:00
韦荣超
04998d6a60 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	public/js/app.js
2022-01-21 18:10:53 +08:00
韦荣超
3d88a28465 build: 2dd8f75d52 2022-01-21 18:10:24 +08:00
韦荣超
2dd8f75d52 fix: 【工作报告】填写内容--汇报类型切换提交提示错误问题 2022-01-21 18:08:48 +08:00
kuaifan
7a4bdd0ada no message 2022-01-21 17:59:08 +08:00
韦荣超
0ed0b5ee43 build: 2475ee90ee 2022-01-21 17:41:09 +08:00
韦荣超
2475ee90ee perf: 工作报告优化 2022-01-21 17:39:10 +08:00
kuaifan
511c19d5aa 优化前端代码 2022-01-21 14:49:20 +08:00
韦荣超
c4fed86f1e Merge remote-tracking branch 'origin/develop' into develop 2022-01-21 14:26:32 +08:00
韦荣超
18904ebbaa perf: 规范代码 2022-01-21 14:26:17 +08:00
kuaifan
da3672e6be 优化前端代码 2022-01-21 14:24:31 +08:00
kuaifan
52e9836bbf build 2022-01-21 14:07:14 +08:00
kuaifan
3201d90a53 格式化代码 2022-01-21 14:07:02 +08:00
kuaifan
cd9c1d9660 Merge branch 'master' of github.com:kuaifan/dootask into develop 2022-01-21 13:54:58 +08:00
kuaifan
0f54a1f638 no message 2022-01-21 13:50:41 +08:00
kuaifan
713316f87c 优化暗黑皮肤 2022-01-21 13:50:23 +08:00
kuaifan
88d38ba8d1 fix: 发送图片显示错误 2022-01-21 13:45:33 +08:00
kuaifan
164f3275f4 优化暗黑皮肤 2022-01-21 13:13:07 +08:00
韦荣超
a44d6b8b79 build: 942cf57c36 2022-01-21 12:10:48 +08:00
韦荣超
942cf57c36 perf: 工作报告优化 2022-01-21 12:06:26 +08:00
kuaifan
18bc75242b fix: 项目负责人还原已经回档的自己不是任务负责人的任务时,无法还原,且会提示错误 2022-01-21 11:10:29 +08:00
kuaifan
3b38c8b408 perf: 共享文件删除、移动改为仅限所有者或创建者操作 2022-01-21 10:00:31 +08:00
kuaifan
22e718423e 优化暗黑模式 2022-01-21 01:38:05 +08:00
kuaifan
0dc0e7226e 调整主题色 2022-01-20 22:59:51 +08:00
kuaifan
8069784198 no message 2022-01-20 00:38:30 +08:00
kuaifan
a9e71567fe Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	electron/package.json
#	package.json
#	public/css/app.css
#	public/js/app.js
#	public/js/build/146.js
#	public/js/build/169.js
#	public/js/build/631.js
#	public/js/build/679.js
#	public/js/build/700.js
#	public/js/build/728.js
#	public/js/build/802.js
#	public/js/build/823.js
#	public/js/build/954.js
#	resources/assets/js/pages/manage.vue
2022-01-20 00:33:05 +08:00
kuaifan
0fae151731 build 2022-01-20 00:20:06 +08:00
kuaifan
3bb0b55955 feat: 添加暗黑模式 2022-01-20 00:19:00 +08:00
kuaifan
071ca80bae fix: 无法读取 /www/.env:没有那个文件或目录 2022-01-18 22:52:43 +08:00
kuaifan
1abd78305f Merge branch 'master' of github.com:kuaifan/dootask into develop
# Conflicts:
#	public/css/app.css
#	public/js/app.js
#	public/js/build/120.js
#	public/js/build/120.js.LICENSE.txt
#	public/js/build/198.js
#	public/js/build/494.js
#	public/js/build/79.js
#	public/js/build/793.js
#	public/js/build/875.js
#	public/js/build/890.js
#	resources/assets/js/components/DrawerOverlay.vue
2022-01-17 17:13:12 +08:00
kuaifan
e1ef9a94af no message 2022-01-17 00:36:19 +08:00
kuaifan
9f62023175 build 2022-01-17 00:12:34 +08:00
kuaifan
c9a6d2f5a8 perf: 优化安装脚本 2022-01-16 22:03:50 +08:00
kuaifan
07fca8b895 fix: 最大只能上传64M的问题 2022-01-16 14:53:35 +08:00
kuaifan
1a65c4a579 修复客户端 2022-01-16 01:11:19 +08:00
kuaifan
7a28a8950c 修复前端编译 2022-01-16 00:43:05 +08:00
kuaifan
89e8b0d8b9 perf: 新增工作流初始化数据 2022-01-16 00:05:49 +08:00
kuaifan
74803df5bd no message 2022-01-15 23:37:29 +08:00
kuaifan
2a8e030fb4 perf: 优化客户端自动更新 2022-01-15 23:36:21 +08:00
kuaifan
c6ebe994cc build 2022-01-15 12:53:21 +08:00
kuaifan
be3677cfa8 优化DrawerOverlay组件支持底部显示动态改变尺寸 2022-01-15 12:16:50 +08:00
kuaifan
0c2e56271b 优化DrawerOverlay组件 2022-01-15 00:06:40 +08:00
kuaifan
b019d40009 fix: websocket获取链接失败 2022-01-14 23:15:59 +08:00
kuaifan
361484be95 build 2022-01-14 18:12:33 +08:00
kuaifan
f0ce6cc28f build 2022-01-14 17:59:07 +08:00
kuaifan
ede298a142 perf: 工作汇报样式 2022-01-14 17:57:51 +08:00
kuaifan
aca1a4d34c 优化ws返回的地址 2022-01-14 16:56:45 +08:00
韦荣超
e63c5c074c build: 0d0b5dd552 2022-01-14 16:46:30 +08:00
韦荣超
0d0b5dd552 fix: 【工作报告】填写'提交'按钮位置被挤压修改 2022-01-14 16:43:13 +08:00
kuaifan
08704e7f60 优化报错 2022-01-14 16:35:00 +08:00
韦荣超
d48ed18102 feat: 【工作报告】功能 2022-01-14 15:55:26 +08:00
kuaifan
3602acd187 build 2022-01-14 14:05:09 +08:00
kuaifan
ad477eb608 优化文案 2022-01-14 14:01:56 +08:00
kuaifan
0a393845e9 优化任务详情窗口聊天 2022-01-14 13:52:10 +08:00
kuaifan
59b98209ac 优化websocket重连后的逻辑 2022-01-14 13:46:22 +08:00
kuaifan
ac26713f86 优化查询代码 2022-01-14 12:19:50 +08:00
kuaifan
9250ef6f65 fix: 查看日历页面部分任务会不见 2022-01-14 10:23:43 +08:00
kuaifan
cabbeb07d0 优化websocket重连后的逻辑 2022-01-14 10:23:23 +08:00
kuaifan
43171645c0 build 2022-01-14 00:04:28 +08:00
kuaifan
79c1484288 新增优化websocket重连后刷新项目、对话、仪表盘数据 2022-01-13 23:59:38 +08:00
kuaifan
e730875db3 perf: 客户端更新提示 2022-01-13 23:55:07 +08:00
kuaifan
18e9fb99b5 更新nginx持续握手时间 2022-01-13 23:53:29 +08:00
kuaifan
fd82b9b555 build 2022-01-13 19:42:41 +08:00
kuaifan
5c3bf0067e 优化日志显示 2022-01-13 19:19:58 +08:00
kuaifan
ac6b90c986 fix: 任务流转会出错 2022-01-13 18:39:57 +08:00
kuaifan
7dcd8b29fc build 2022-01-13 11:50:54 +08:00
kuaifan
d951da2c02 fix: 修复客户端旧版本更新任务出现已完成列表中有状态为“进行中”的情况 2022-01-13 11:48:33 +08:00
kuaifan
605aee35c4 perf: 发送文件ws返回错误地址 2022-01-13 11:42:01 +08:00
kuaifan
dc1f1985e8 访问接口header加上version、platform 2022-01-13 11:22:46 +08:00
kuaifan
cebcc26baf 优化项目日志 2022-01-13 11:11:29 +08:00
kuaifan
75a12fc6f5 优化日志 2022-01-13 09:00:39 +08:00
kuaifan
a3e0d89eb0 修改任务状态判断是否项目负责人直接查表 2022-01-13 00:32:35 +08:00
kuaifan
9594cc674f no message 2022-01-13 00:30:36 +08:00
kuaifan
87cdee4fe8 pref: 优化任务日志的显示 2022-01-13 00:30:36 +08:00
kuaifan
1df55b78c2 perf: 项目页面点击搜索按钮再点击弹出的搜索框窗会隐藏 2022-01-13 00:30:36 +08:00
kuaifan
f7820a23be no message 2022-01-12 16:34:16 +08:00
kuaifan
f80b9cdca5 feat: 工作流程状态支持仅限状态负责人修改 2022-01-12 16:16:04 +08:00
kuaifan
f2180b22c7 build 2022-01-11 23:23:44 +08:00
kuaifan
a745088213 perf: 任务日志刷新 2022-01-11 23:17:59 +08:00
kuaifan
14be7bd2b1 no msg 2022-01-11 22:58:30 +08:00
kuaifan
d453ea66da 优化登录页按钮 2022-01-11 21:31:23 +08:00
kuaifan
e7ae86e261 feat: 任务流转自动负责人支持转让模式 2022-01-11 20:07:58 +08:00
kuaifan
b7417f41c5 perf: action中错误提醒弱化 2022-01-11 17:54:42 +08:00
kuaifan
9e7aa381ed perf: 手机版无法选择日期范围的问题 2022-01-11 17:47:34 +08:00
kuaifan
b02453e9f4 perf: 手机版无法聊天,输入页面跳动 2022-01-11 17:35:53 +08:00
kuaifan
6b7f8fd31c 优化完成任务标记 2022-01-11 14:18:49 +08:00
kuaifan
4652242d6b build 2022-01-11 12:16:01 +08:00
kuaifan
a47ca2f357 perf: 个人设置显示版本号 2022-01-11 12:13:26 +08:00
kuaifan
e5a1e58159 perf: 完成任务暂时继续显示在我的列表 2022-01-11 12:13:06 +08:00
kuaifan
01a1e34e99 no message 2022-01-11 10:59:47 +08:00
kuaifan
e711220a66 优化自定义服务器 2022-01-11 10:31:37 +08:00
kuaifan
1d20f529a0 no message 2022-01-11 00:28:58 +08:00
kuaifan
79a94d25bd perf: 客户端自动下载新版本更新 2022-01-10 19:27:43 +08:00
kuaifan
92793b8ff8 no message 2022-01-10 00:25:36 +08:00
kuaifan
982024f359 子任务时间在主任务时间内 2022-01-09 20:13:59 +08:00
kuaifan
e851bd4d61 子任务时间大于主任务时主任务自动修改 2022-01-09 18:56:30 +08:00
kuaifan
e4b9383e96 perf: 设置子任务时间时,如果主任务没有时间则自动设置 2022-01-09 18:20:36 +08:00
kuaifan
95ac9aac14 添加任务自动上工作流状态 2022-01-09 17:59:08 +08:00
kuaifan
b895eec69c 初步完成工作流 2022-01-09 17:52:46 +08:00
kuaifan
1fe4e80f82 no msg 2022-01-09 12:56:55 +08:00
kuaifan
eb1f5f2632 perf: 任务操作菜单组件化 2022-01-09 00:09:43 +08:00
kuaifan
114b792300 no message 2022-01-08 21:23:47 +08:00
kuaifan
56a352a9d2 perf: 调整仅项目负责人可以删除任务列表 2022-01-08 17:35:25 +08:00
kuaifan
4c4d60dd83 no message 2022-01-08 17:32:03 +08:00
kuaifan
134ab0fe98 no message 2022-01-08 17:27:30 +08:00
kuaifan
bbd394272f feat: 添加工作流 2022-01-08 17:24:48 +08:00
kuaifan
8a2571f514 no msg 2022-01-08 08:56:29 +08:00
kuaifan
bf15be8144 perf: 领取任务需要设置计划时间 2022-01-08 08:51:34 +08:00
kuaifan
bf6f8de7fa no msg 2022-01-08 00:35:01 +08:00
kuaifan
2e75e6ffb6 后台限制任务负责人、协助人员每项最多不能超过10个 2022-01-07 23:36:05 +08:00
kuaifan
b987be54bf 优化前端变量 2022-01-07 23:28:07 +08:00
kuaifan
533e8a3742 build 2022-01-07 19:12:38 +08:00
kuaifan
9d007e64f6 优化部分相同代码 2022-01-07 18:42:04 +08:00
kuaifan
8ec1578f50 左边底部新增添加任务选项 2022-01-07 18:23:30 +08:00
kuaifan
4e8cc36d3a 显示完成时间 2022-01-07 18:09:22 +08:00
kuaifan
5a64cb2323 替换Tooltip改成ETooltip 2022-01-07 18:09:05 +08:00
kuaifan
b2aba82a1b perf: 任务版本如果只有一个负责人时显示负责人的名字 2022-01-07 17:46:03 +08:00
kuaifan
9d9500ba1b fix: 其他人员添加任务会临时出现在自己的列表中 2022-01-07 17:26:22 +08:00
kuaifan
674c5a11c1 客户端pkg包安装前必须关闭已打开的应用 2022-01-07 10:56:07 +08:00
kuaifan
8e2b2947ae 版本号基于master分支生成 2022-01-07 10:16:35 +08:00
kuaifan
0413ca7cba 新增pkg安装包 2022-01-07 10:14:49 +08:00
kuaifan
15cf7800a4 日历选时间快速添加任务 2022-01-07 01:07:41 +08:00
kuaifan
db6114a4ee pref: 日历任务缓存 2022-01-07 00:56:03 +08:00
kuaifan
818495a697 build 2022-01-06 23:25:04 +08:00
kuaifan
7aa41c4050 no msg 2022-01-06 23:22:03 +08:00
kuaifan
1975b8af1d perf: 日历选择时间添加任务 2022-01-06 23:15:04 +08:00
kuaifan
b870f5f4d1 no msg 2022-01-06 22:24:45 +08:00
kuaifan
3ebc720934 no message 2022-01-06 20:24:31 +08:00
kuaifan
748976f393 feat: 创建任务窗口添加小时钟 2022-01-06 20:12:13 +08:00
kuaifan
83e5c7fadb 优化注释 2022-01-06 19:22:29 +08:00
kuaifan
f919a34166 优化任务、列表数量限制 2022-01-06 19:12:49 +08:00
kuaifan
77a9eca634 no msg 2022-01-06 18:56:50 +08:00
kuaifan
72b732a55d perf: 归档任务可以搜索 2022-01-06 18:53:34 +08:00
kuaifan
0236897d1f no msg 2022-01-06 17:17:23 +08:00
kuaifan
46f95e5e13 no msg 2022-01-06 16:57:13 +08:00
kuaifan
24287c0857 fix: 主任务被删除或归档时子任务应该也同步 2022-01-06 16:51:32 +08:00
kuaifan
c930e4dd92 perf: 归档任务列表显示完成时间 2022-01-06 16:30:45 +08:00
kuaifan
7d8856e4bc no msg 2022-01-06 15:41:58 +08:00
kuaifan
6d026bbf42 no message 2022-01-06 15:24:54 +08:00
kuaifan
713770a448 no message 2022-01-06 15:09:41 +08:00
kuaifan
ac873fa757 no msg 2022-01-06 14:40:25 +08:00
kuaifan
6b3513c1c4 feat: 限制个人最多500个未完成任务 2022-01-06 14:40:19 +08:00
kuaifan
840374c48c feat: 限制项目最多100个人参与 2022-01-06 14:39:56 +08:00
kuaifan
7f454f279c perf: 项目版面成员显示 2022-01-06 14:28:23 +08:00
kuaifan
267c09f20c no msg 2022-01-06 13:38:49 +08:00
kuaifan
e44e77a3a6 pref: 子任务默认起始时间与主任务一致 2022-01-06 13:28:47 +08:00
kuaifan
4d9dd13ffb pref: 禁用会员记录禁用时间 2022-01-06 13:28:10 +08:00
kuaifan
369e75cb7e perf: 安装项目时支持自定义端口 2022-01-06 11:33:51 +08:00
kuaifan
0a6fa2431e perf: 任务详情窗口光标在任务描述可使用ctrl+s(command+s)保存 2022-01-06 08:27:32 +08:00
kuaifan
0bc40d1748 perf: 文件快捷键保存 2022-01-06 08:17:37 +08:00
kuaifan
919dc3cdea feat: 思维导图支持无极缩放 2022-01-06 08:08:40 +08:00
kuaifan
7c64b27ef4 feat: 自动归档已完成任务 2022-01-06 01:19:44 +08:00
kuaifan
369577a2c8 no message 2022-01-06 00:32:27 +08:00
kuaifan
fbc25e5134 no message 2022-01-05 23:50:29 +08:00
kuaifan
aba1628d36 no message 2022-01-05 22:51:02 +08:00
kuaifan
90336e1edf 整理$A 2022-01-05 20:19:27 +08:00
kuaifan
dde32fcaee no message 2022-01-05 19:49:47 +08:00
kuaifan
6324e79aba no message 2022-01-05 15:04:55 +08:00
kuaifan
7a22f4b20f no message 2022-01-05 14:56:11 +08:00
kuaifan
aad621bd84 no message 2022-01-05 14:17:45 +08:00
kuaifan
0b4e1f3dee no message 2022-01-05 13:41:05 +08:00
kuaifan
78f88db560 将所有任务都取出全部显示,子任务判断显示主任务 2022-01-05 09:30:00 +08:00
kuaifan
cc1c425ecf no message 2022-01-04 23:42:29 +08:00
kuaifan
186290e355 未完成项目概况 2022-01-04 20:23:01 +08:00
kuaifan
f1f1d784ff 修复我的任务数据 2022-01-01 15:03:33 +08:00
623585344@qq.com
320f183b49 add:新增暗黑皮肤样式 2021-12-31 16:59:42 +08:00
kuaifan
a1c7efeb85 任务数据逻辑 2021-12-31 14:53:57 +08:00
kuaifan
ba5f635687 子任务默认时间与主任务相同 2021-12-31 11:07:44 +08:00
kuaifan
bae5afc0da no message 2021-12-31 09:59:11 +08:00
kuaifan
befd5c3b08 去除taskSubs 2021-12-31 07:48:55 +08:00
kuaifan
0b4e96a90f build 2021-12-30 15:35:16 +08:00
kuaifan
57edf38c1a fix: 任务有负责人后仅限项目或任务负责人修改 2021-12-30 15:32:36 +08:00
kuaifan
502dd1ec1f perf: 未被领取的任务标记完成之前先领取 2021-12-30 14:39:12 +08:00
kuaifan
02361ddfb2 perf: 快速添加任务0天的描述 2021-12-30 14:31:23 +08:00
kuaifan
bcf6cc1019 perf: 客户端标题优化 2021-12-30 14:18:25 +08:00
kuaifan
b97a2e7cf3 build 2021-12-30 11:04:16 +08:00
kuaifan
27158e1ee7 perf: office文件预览 2021-12-30 10:58:51 +08:00
kuaifan
28abad0276 no msg 2021-12-30 10:36:46 +08:00
kuaifan
b59549ebe9 perf: 文件预览不能滚动的问题 2021-12-30 10:34:52 +08:00
kuaifan
b9f788fbe8 build 2021-12-30 00:58:23 +08:00
kuaifan
9d89334cc5 perf: 优化文件权限 2021-12-30 00:52:34 +08:00
kuaifan
6c67ff3fe8 fix: 只读文件也能修改文件 2021-12-29 22:11:18 +08:00
kuaifan
6ef59f703a perf: 初始化数据 2021-12-29 21:40:13 +08:00
623585344@qq.com
c8f11578d6 mod:修改首页内容部分图片自适应 2021-12-29 17:47:45 +08:00
623585344@qq.com
f33be18be3 fix:修复首页内容部分间距问题 2021-12-29 16:28:30 +08:00
623585344@qq.com
513c7dbd85 add:新增自适应隐藏语言选择框文本 2021-12-29 16:20:59 +08:00
623585344@qq.com
02a9623310 fix:修复英文版本登录按钮无法点击 2021-12-29 14:13:51 +08:00
623585344@qq.com
58c1d52f52 fix:修复首页代码格式化 2021-12-29 13:58:12 +08:00
623585344@qq.com
b71c881b43 add:首页自适应 2021-12-29 13:48:52 +08:00
test
ffc2c7dea3 add:首页功能开发 2021-12-28 15:38:40 +08:00
test
d7652a7a32 add:首页功能开发 2021-12-28 15:34:54 +08:00
2778 changed files with 119539 additions and 374213 deletions

View File

@@ -1,4 +1,4 @@
APP_NAME=Dootask
APP_NAME=DooTask
APP_ENV=local
APP_KEY=
APP_DEBUG=true

85
.github/workflows/electron-generic.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: Build Generic
on:
push:
tags:
- 'v*'
jobs:
build-mac:
strategy:
fail-fast: false
matrix:
id: [ "com.dootask.task", "com.hitosea.task" ]
runs-on: macos-latest
environment: build
if: startsWith(github.event.ref, 'refs/tags/v')
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Create changelog text
id: changelog
uses: loopwerk/tag-changelog@v1
with:
token: ${{ secrets.GH_PAT }}
exclude_types: other,chore,build
- name: Use Node.js 16.x
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Build for MacOS
env:
APPID: ${{ matrix.id }}
PROVIDER: "generic"
APPLEID: ${{ secrets.APPLEID }}
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
CSC_LINK: ${{ secrets.CSC_LINK }}
RELEASE_BODY: ${{ steps.changelog.outputs.changes }}
run: ./cmd electron build-mac
build-win:
strategy:
fail-fast: false
matrix:
id: [ "com.dootask.task", "com.hitosea.task" ]
runs-on: windows-latest
environment: build
if: startsWith(github.event.ref, 'refs/tags/v')
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Create changelog text
id: changelog
uses: loopwerk/tag-changelog@v1
with:
token: ${{ secrets.GH_PAT }}
exclude_types: other,chore,build
- name: Use Node.js 16.x
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Build for Windows
shell: powershell
env:
APPID: ${{ matrix.id }}
PROVIDER: "generic"
RELEASE_BODY: ${{ steps.changelog.outputs.changes }}
run: |
npm install
cd electron
npm install
cd ../
mkdir -p ./electron/public
cp ./electron/index.html ./electron/public/index.html
npx mix --production -- --env --electron
node ./electron/build.js build-win

97
.github/workflows/electron-github.yml vendored Normal file
View File

@@ -0,0 +1,97 @@
name: Build Github
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
if: ${{ (startsWith(github.event.ref, 'refs/tags/v')) && (github.repository == 'kuaifan/dootask') }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Create changelog text
id: changelog
uses: loopwerk/tag-changelog@v1
with:
token: ${{ secrets.GH_PAT }}
exclude_types: other,chore,build
- name: Create release
uses: actions/create-release@latest
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: ${{ steps.changelog.outputs.changes }}
build-mac:
strategy:
fail-fast: false
matrix:
id: [ "com.dootask.task", "com.hitosea.task" ]
runs-on: macos-latest
environment: build
if: ${{ (startsWith(github.event.ref, 'refs/tags/v')) && (github.repository == 'kuaifan/dootask') }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js 16.x
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Build for MacOS
env:
APPID: ${{ matrix.id }}
PROVIDER: "github"
APPLEID: ${{ secrets.APPLEID }}
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
CSC_LINK: ${{ secrets.CSC_LINK }}
GH_TOKEN: ${{ secrets.GH_PAT }}
EP_PRE_RELEASE: true
run: ./cmd electron build-mac
build-win:
strategy:
fail-fast: false
matrix:
id: [ "com.dootask.task", "com.hitosea.task" ]
runs-on: windows-latest
environment: build
if: ${{ (startsWith(github.event.ref, 'refs/tags/v')) && (github.repository == 'kuaifan/dootask') }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js 16.x
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Build for Windows
shell: powershell
env:
APPID: ${{ matrix.id }}
PROVIDER: "github"
GH_TOKEN: ${{ secrets.GH_PAT }}
EP_PRE_RELEASE: true
run: |
npm install
cd electron
npm install
cd ../
mkdir -p ./electron/public
cp ./electron/index.html ./electron/public/index.html
npx mix --production -- --env --electron
node ./electron/build.js build-win

View File

@@ -1,60 +0,0 @@
name: Build
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
if: startsWith(github.event.ref, 'refs/tags/v')
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Create changelog text
id: changelog
uses: loopwerk/tag-changelog@v1
with:
token: ${{ secrets.GH_PAT }}
- name: Create release
uses: actions/create-release@latest
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: ${{ steps.changelog.outputs.changes }}
build:
runs-on: ${{ matrix.os }}
environment: build
strategy:
matrix:
os: [macos-11]
platform: [
build-mac,
build-mac-arm,
build-win
]
if: startsWith(github.event.ref, 'refs/tags/v')
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: Build
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
EP_PRE_RELEASE: true
run: ./cmd electron ${{ matrix.platform }}

2
.gitignore vendored
View File

@@ -8,6 +8,7 @@
/vendor
/build
/tmp
/CHANGELOG.md
._*
.env
.idea
@@ -19,7 +20,6 @@ Homestead.yaml
npm-debug.log
yarn-error.log
test.*
composer.lock
package-lock.json
laravels-timer-process.pid
.DS_Store

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "resources/drawio"]
path = resources/drawio
url = https://github.com/jgraph/drawio.git
[submodule "resources/mobile"]
path = resources/mobile
url = git@github.com:kuaifan/dootask-app.git

View File

@@ -5,10 +5,15 @@ English | **[中文文档](./README_CN.md)**
- [Screenshot Preview](README_PREVIEW.md)
- [Demo site](http://www.dootask.com/)
**QQ Group**
Group No.: `546574618`
## Setup
> `Docker` & `Docker Compose v2.0+` must be installed
- `Docker` & `Docker Compose v2.0+` must be installed
- System: `Centos/Debian/Ubuntu/macOS`
- Hardware suggestion: 2 cores and above 4G memory
### Deployment project
@@ -16,14 +21,14 @@ English | **[中文文档](./README_CN.md)**
# 1、Clone the repository
# Clone projects on github
git clone https://github.com/kuaifan/dootask.git
# or you can use gitee
git clone https://gitee.com/aipaw/dootask.git
git clone --depth=1 https://github.com/kuaifan/dootask.git
# Or you can use gitee
git clone --depth=1 https://gitee.com/aipaw/dootask.git
# 2、enter directory
# 2、Enter directory
cd dootask
# 3、Build project
# 3、InstallationCustom port installation: ./cmd install --port 2222
./cmd install
```
@@ -37,8 +42,17 @@ cd dootask
### Change port
```bash
./cmd php bin/run --port=2222
./cmd up -d
./cmd port 2222
```
### Change App Url
```bash
# This URL only affects the email reply.
./cmd url {Your domain url}
# example:
./cmd url https://domain.com
```
### Stop server
@@ -50,18 +64,28 @@ cd dootask
./cmd start
```
### Development compilation
```bash
# Development mode, Mac OS only
./cmd dev
# Production projects, macOS only
./cmd prod
```
### Shortcuts for running command
```bash
# You can do this using the following command
./cmd artisan "your command" // To run a artisan command
./cmd php "your command" // To run a php command
./cmd nginx "your command" // To run a nginx command
./cmd redis "your command" // To run a redis command
./cmd composer "your command" // To run a composer command
./cmd supervisorctl "your command" // To run a supervisorctl command
./cmd test "your command" // To run a phpunit command
./cmd mysql "your command" // To run a mysql command (backup: Backup database, recovery: Restore database)
./cmd artisan "your command" # To run a artisan command
./cmd php "your command" # To run a php command
./cmd nginx "your command" # To run a nginx command
./cmd redis "your command" # To run a redis command
./cmd composer "your command" # To run a composer command
./cmd supervisorctl "your command" # To run a supervisorctl command
./cmd test "your command" # To run a phpunit command
./cmd mysql "your command" # To run a mysql command (backup: Backup database, recovery: Restore database)
```
### NGINX PROXY SSL
@@ -72,7 +96,7 @@ proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 2、Enter directory and run command
# 2、Running commands in a project
./cmd https
```
@@ -81,7 +105,7 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
**Note: Please back up your data before upgrading!**
```bash
# Method 1: enter directory and run command
# Method 1: Running commands in a project
./cmd update
# Or method 2: use this method if method 1 fails
@@ -92,13 +116,30 @@ git pull
./cmd mysql recovery
```
If 502 after the upgrade please run `./cmd restart` restart the service.
## Transfer
Follow these steps to complete the project migration after the new project is installed:
1. Backup original database
```bash
# Run command under old project
./cmd mysql backup
```
2. Copy `database backup file` and `public/uploads` directory to the new project.
3. Restore database to new project
```bash
# Run command under new project
./cmd mysql recovery
```
## Uninstall
```bash
# Enter directory and run command
# Running commands in a project
./cmd uninstall
```
## Contact us
QQ Group: 546574618

View File

@@ -5,10 +5,15 @@
- [截图预览](README_PREVIEW.md)
- [演示站点](http://www.dootask.com/)
**QQ交流群**
- QQ群号: `546574618`
## 安装程序
> 必须安装 `Docker` 和 `Docker Compose v2.0+`
- 必须安装`Docker``Docker Compose v2.0+`
- 支持环境:`Centos/Debian/Ubuntu/macOS`
- 硬件建议2核4G以上
### 部署项目
@@ -16,14 +21,14 @@
# 1、克隆项目到您的本地或服务器
# 通过github克隆项目
git clone https://github.com/kuaifan/dootask.git
git clone --depth=1 https://github.com/kuaifan/dootask.git
# 或者你也可以使用gitee
git clone https://gitee.com/aipaw/dootask.git
git clone --depth=1 https://gitee.com/aipaw/dootask.git
# 2、进入目录
cd dootask
# 3、一键构建项目
# 3、一键安装项目(自定义端口安装 ./cmd install --port 2222
./cmd install
```
@@ -37,8 +42,17 @@ cd dootask
### 更换端口
```bash
./cmd php bin/run --port=2222
./cmd up -d
./cmd port 2222
```
### 更换URL
```bash
# 此地址仅影响邮件回复功能
./cmd url {域名地址}
# 例如:
./cmd url https://domain.com
```
### 停止服务
@@ -50,18 +64,29 @@ cd dootask
./cmd start
```
### 开发编译
```bash
# 开发模式仅限macOS
./cmd dev
# 编译项目仅限macOS
./cmd prod
```
### 运行命令的快捷方式
```bash
# 你可以使用以下命令来执行
./cmd artisan "your command" // 运行 artisan 命令
./cmd php "your command" // 运行 php 命令
./cmd nginx "your command" // 运行 nginx 命令
./cmd redis "your command" // 运行 redis 命令
./cmd composer "your command" // 运行 composer 命令
./cmd supervisorctl "your command" // 运行 supervisorctl 命令
./cmd test "your command" // 运行 phpunit 命令
./cmd mysql "your command" // 运行 mysql 命令 (backup: 备份数据库recovery: 还原数据库)
./cmd artisan "your command" # 运行 artisan 命令
./cmd php "your command" # 运行 php 命令
./cmd nginx "your command" # 运行 nginx 命令
./cmd redis "your command" # 运行 redis 命令
./cmd composer "your command" # 运行 composer 命令
./cmd supervisorctl "your command" # 运行 supervisorctl 命令
./cmd test "your command" # 运行 phpunit 命令
./cmd mysql "your command" # 运行 mysql 命令 (backup: 备份数据库recovery: 还原数据库)
```
### NGINX 代理 SSL
@@ -72,7 +97,7 @@ proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 2、进入项目所在目录,运行以下命令
# 2、在项目下运行命令
./cmd https
```
@@ -81,7 +106,7 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
**注意:在升级之前请备份好你的数据!**
```bash
# 方法1进入项目所在目录,运行以下命令
# 方法1在项目下运行命令
./cmd update
# 或者方法2如果方法1失败请使用此方法
@@ -92,13 +117,30 @@ git pull
./cmd mysql recovery
```
如果升级后出现502请运行 `./cmd restart` 重启服务即可。
## 迁移项目
在新项目安装好之后按照以下步骤完成项目迁移:
1、备份原数据库
```bash
# 在旧的项目下运行命令
./cmd mysql backup
```
2、将`数据库备份文件`及`public/uploads`目录拷贝至新项目
3、还原数据库至新项目
```bash
# 在新的项目下运行命令
./cmd mysql recovery
```
## 卸载项目
```bash
# 进入项目所在目录,运行以下命令
# 在项目下运行命令
./cmd uninstall
```
## 联系我们
QQ群号: 546574618

28
README_PUBLISH.md Normal file
View File

@@ -0,0 +1,28 @@
# 发布说明
## 发布前
1. 添加环境变量 `APPLEID``APPLEIDPASS``CSC_LINK`
2. 发布GitHub还需要添加 `GH_PAT`
## 通过 GitHub Actions 发布
1. 执行 `./cmd prod` 编译
2. 执行 `node ./version.js` 制作版本
3. 执行 `git commit` 相关操作
4. 制作标签
5. 推送标签
## 本地发布
1. 执行 `./cmd prod` 编译
2. 执行 `node ./version.js` 制作版本
3. 执行 `git commit` 相关操作
4. 制作标签
5. 执行 `./cmd electron` 相关操作
## 编译App
1. 执行 `./cmd appbuild``./cmd appbuild setting` 编译
2. 进入 `resources/mobile` eeui框架内打包Android或iOS应用

File diff suppressed because it is too large Load Diff

View File

@@ -10,8 +10,15 @@ if (!function_exists('asset_main')) {
if (!function_exists('seeders_at')) {
function seeders_at($data)
{
$diff = time() - strtotime("2021-07-01");
$diff = time() - strtotime("2021-07-02");
$time = strtotime($data) + $diff;
return date("Y-m-d H:i:s", $time);
}
}
if (!function_exists('md5s')) {
function md5s($val, $len = 16)
{
return substr(md5($val), 32 - $len);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -2,10 +2,13 @@
namespace App\Http\Controllers\Api;
use App\Models\Setting;
use App\Models\User;
use App\Module\Base;
use Arr;
use Guanguans\Notify\Factory;
use Guanguans\Notify\Messages\EmailMessage;
use Request;
use Response;
/**
* @apiDefine system
@@ -25,7 +28,7 @@ class SystemController extends AbstractController
* @apiParam {String} type
* - get: 获取(默认)
* - all: 获取所有(需要管理员权限)
* - save: 保存设置参数regreg_invitelogin_codepassword_policyproject_invitechat_nickname
* - save: 保存设置(参数:['reg', 'reg_invite', 'login_code', 'password_policy', 'project_invite', 'chat_information', 'auto_archived', 'archived_day', 'all_group_mute', 'all_group_autoin', 'start_home', 'home_footer']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -41,10 +44,31 @@ class SystemController extends AbstractController
User::auth('admin');
$all = Request::input();
foreach ($all AS $key => $value) {
if (!in_array($key, ['reg', 'reg_invite', 'login_code', 'password_policy', 'project_invite', 'chat_nickname'])) {
if (!in_array($key, [
'reg',
'reg_invite',
'login_code',
'password_policy',
'project_invite',
'chat_information',
'auto_archived',
'archived_day',
'all_group_mute',
'all_group_autoin',
'start_home',
'home_footer'
])) {
unset($all[$key]);
}
}
$all['archived_day'] = floatval($all['archived_day']);
if ($all['auto_archived'] == 'open') {
if ($all['archived_day'] <= 0) {
return Base::retError('自动归档时间不可小于1天');
} elseif ($all['archived_day'] > 100) {
return Base::retError('自动归档时间不可大于100天');
}
}
$setting = Base::setting('system', Base::newTrim($all));
} else {
$setting = Base::setting('system');
@@ -61,13 +85,176 @@ class SystemController extends AbstractController
$setting['login_code'] = $setting['login_code'] ?: 'auto';
$setting['password_policy'] = $setting['password_policy'] ?: 'simple';
$setting['project_invite'] = $setting['project_invite'] ?: 'open';
$setting['chat_nickname'] = $setting['chat_nickname'] ?: 'optional';
$setting['chat_information'] = $setting['chat_information'] ?: 'optional';
$setting['auto_archived'] = $setting['auto_archived'] ?: 'close';
$setting['archived_day'] = floatval($setting['archived_day']) ?: 7;
$setting['all_group_mute'] = $setting['all_group_mute'] ?: 'open';
$setting['all_group_autoin'] = $setting['all_group_autoin'] ?: 'yes';
$setting['start_home'] = $setting['start_home'] ?: 'close';
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/demo 02. 获取演示账号
* @api {get} api/system/setting/email 02. 获取邮箱设置、保存邮箱设置(限管理员)
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName setting__email
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['smtp_server', 'port', 'account', 'password', 'reg_verify', 'notice', 'task_start_minute', 'task_remind_hours', 'task_remind_hours2', 'notice_msg', 'msg_unread_user_minute', 'msg_unread_group_minute', 'ignore_addr']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function setting__email()
{
$user = User::auth();
//
$type = trim(Request::input('type'));
if ($type == 'save') {
if (env("SYSTEM_SETTING") == 'disabled') {
return Base::retError('当前环境禁止修改');
}
$user->identity('admin');
$all = Request::input();
foreach ($all as $key => $value) {
if (!in_array($key, [
'smtp_server',
'port',
'account',
'password',
'reg_verify',
'notice',
'task_start_minute',
'task_remind_hours',
'task_remind_hours2',
'notice_msg',
'msg_unread_user_minute',
'msg_unread_group_minute',
'ignore_addr'
])) {
unset($all[$key]);
}
}
$setting = Base::setting('emailSetting', Base::newTrim($all));
} else {
$setting = Base::setting('emailSetting');
}
//
$setting['smtp_server'] = $setting['smtp_server'] ?: '';
$setting['port'] = $setting['port'] ?: '';
$setting['account'] = $setting['account'] ?: '';
$setting['password'] = $setting['password'] ?: '';
$setting['reg_verify'] = $setting['reg_verify'] ?: 'close';
$setting['notice'] = $setting['notice'] ?: 'close';
$setting['task_start_minute'] = intval($setting['task_start_minute'] ?? -1);
$setting['task_remind_hours'] = floatval($setting['task_remind_hours'] ?? -1);
$setting['task_remind_hours2'] = floatval($setting['task_remind_hours2'] ?? -1);
$setting['notice_msg'] = $setting['notice_msg'] ?: 'close';
$setting['msg_unread_user_minute'] = intval($setting['msg_unread_user_minute'] ?? -1);
$setting['msg_unread_group_minute'] = intval($setting['msg_unread_group_minute'] ?? -1);
$setting['ignore_addr'] = $setting['ignore_addr'] ?: '';
//
if ($type != 'save' && !in_array('admin', $user->identity)) {
$setting = array_intersect_key($setting, array_flip(['reg_verify']));
}
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/setting/meeting 03. 获取会议设置、保存会议设置(限管理员)
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName setting__meeting
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['open', 'appid', 'app_certificate']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function setting__meeting()
{
User::auth('admin');
//
$type = trim(Request::input('type'));
if ($type == 'save') {
if (env("SYSTEM_SETTING") == 'disabled') {
return Base::retError('当前环境禁止修改');
}
$all = Request::input();
foreach ($all as $key => $value) {
if (!in_array($key, [
'open',
'appid',
'app_certificate',
])) {
unset($all[$key]);
}
}
$setting = Base::setting('meetingSetting', Base::newTrim($all));
} else {
$setting = Base::setting('meetingSetting');
}
//
$setting['open'] = $setting['open'] ?: 'close';
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/setting/apppush 04. 获取APP推送设置、保存APP推送设置限管理员
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName setting__apppush
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['push', 'ios_key', 'ios_secret', 'android_key', 'android_secret']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function setting__apppush()
{
User::auth('admin');
//
$type = trim(Request::input('type'));
if ($type == 'save') {
if (env("SYSTEM_SETTING") == 'disabled') {
return Base::retError('当前环境禁止修改');
}
$all = Request::input();
foreach ($all as $key => $value) {
if (!in_array($key, [
'push',
'ios_key',
'ios_secret',
'android_key',
'android_secret'
])) {
unset($all[$key]);
}
}
$setting = Base::setting('appPushSetting', Base::newTrim($all));
} else {
$setting = Base::setting('appPushSetting');
}
//
$setting['push'] = $setting['push'] ?: 'close';
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/demo 05. 获取演示帐号
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -91,12 +278,16 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/priority 03. 获取优先级、保存优先级
* @api {post} api/system/priority 06. 任务优先级
*
* @apiDescription 获取任务优先级、保存任务优先级
* @apiVersion 1.0.0
* @apiGroup system
* @apiName priority
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存(限管理员)
* @apiParam {Array} list 优先级数据,格式:[{name,color,days,priority}]
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
@@ -136,7 +327,54 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/info 04. 获取终端详细信息
* @api {post} api/system/column/template 07. 创建项目模板
*
* @apiDescription 获取创建项目模板、保存创建项目模板
* @apiVersion 1.0.0
* @apiGroup system
* @apiName column__template
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存(限管理员)
* @apiParam {Array} list 优先级数据,格式:[{name,columns}]
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function column__template()
{
$type = trim(Request::input('type'));
if ($type == 'save') {
User::auth('admin');
$list = Base::getPostValue('list');
$array = [];
if (empty($list) || !is_array($list)) {
return Base::retError('参数错误');
}
foreach ($list AS $item) {
if (empty($item['name']) || empty($item['columns'])) {
continue;
}
$array[] = [
'name' => $item['name'],
'columns' => array_values(array_filter(array_unique(explode(",", $item['columns']))))
];
}
if (empty($array)) {
return Base::retError('参数为空');
}
$setting = Base::setting('columnTemplate', $array);
} else {
$setting = Base::setting('columnTemplate');
}
//
return Base::retSuccess('success', $setting);
}
/**
* @api {get} api/system/get/info 08. 获取终端详细信息
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -165,7 +403,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ip 05. 获取IP地址
* @api {get} api/system/get/ip 09. 获取IP地址
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -180,7 +418,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/cnip 06. 是否中国IP地址
* @api {get} api/system/get/cnip 10. 是否中国IP地址
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -197,7 +435,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ipgcj02 07. 获取IP地址经纬度
* @api {get} api/system/get/ipgcj02 11. 获取IP地址经纬度
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -214,7 +452,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ipinfo 08. 获取IP地址详细信息
* @api {get} api/system/get/ipinfo 12. 获取IP地址详细信息
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -231,15 +469,22 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/imgupload 09. 上传图片
* @api {post} api/system/imgupload 13. 上传图片
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup system
* @apiName imgupload
*
* @apiParam {String} image64 图片base64
* @apiParam {String} filename 文件名
* @apiParam {File} image post-图片对象
* @apiParam {String} [image64] post-图片base64与'image'二选一)
* @apiParam {String} filename post-文件名
* @apiParam {Number} [width] 压缩图片宽默认0
* @apiParam {Number} [height] 压缩图片高默认0
* @apiParam {String} [whcut] 压缩方式
* - 1裁切默认宽、高非0有效
* - 0缩放
* - -1或'auto':保持等比裁切
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -250,11 +495,14 @@ class SystemController extends AbstractController
if (User::userid() === 0) {
return Base::retError('身份失效,等重新登录');
}
$scale = [intval(Request::input('width')), intval(Request::input('height'))];
if (!$scale[0] && !$scale[1]) {
$scale = [2160, 4160, -1];
$width = intval(Request::input('width'));
$height = intval(Request::input('height'));
$whcut = intval(Request::input('whcut', 1));
$scale = [2160, 4160, -1];
if ($width > 0 || $height > 0) {
$scale = [$width, $height, $whcut];
}
$path = "uploads/picture/" . User::userid() . "/" . date("Ym") . "/";
$path = "uploads/user/picture/" . User::userid() . "/" . date("Ym") . "/";
$image64 = trim(Base::getPostValue('image64'));
$fileName = trim(Base::getPostValue('filename'));
if ($image64) {
@@ -281,7 +529,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/imgview 10. 浏览图片空间
* @api {get} api/system/get/imgview 14. 浏览图片空间
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -299,7 +547,7 @@ class SystemController extends AbstractController
if (User::userid() === 0) {
return Base::retError('身份失效,等重新登录');
}
$publicPath = "uploads/picture/" . User::userid() . "/";
$publicPath = "uploads/user/picture/" . User::userid() . "/";
$dirPath = public_path($publicPath);
$dirs = $files = [];
//
@@ -377,7 +625,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/fileupload 11. 上传文件
* @api {post} api/system/fileupload 15. 上传文件
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -397,7 +645,7 @@ class SystemController extends AbstractController
if (User::userid() === 0) {
return Base::retError('身份失效,等重新登录');
}
$path = "uploads/files/" . User::userid() . "/" . date("Ym") . "/";
$path = "uploads/user/file/" . User::userid() . "/" . date("Ym") . "/";
$image64 = trim(Base::getPostValue('image64'));
$fileName = trim(Base::getPostValue('filename'));
if ($image64) {
@@ -417,4 +665,99 @@ class SystemController extends AbstractController
//
return $data;
}
/**
* @api {get} api/system/get/starthome 16. 启动首页设置信息
*
* @apiDescription 用于判断注册是否需要启动首页
* @apiVersion 1.0.0
* @apiGroup system
* @apiName get__starthome
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function get__starthome()
{
return Base::retSuccess('success', [
'need_start' => Base::settingFind('system', 'start_home') == 'open',
'home_footer' => Base::settingFind('system', 'home_footer')
]);
}
/**
* @api {get} api/system/email/check 17. 邮件发送测试(限管理员)
*
* @apiDescription 测试配置邮箱是否能发送邮件
* @apiVersion 1.0.0
* @apiGroup system
* @apiName email__check
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function email__check()
{
User::auth('admin');
//
$all = Request::input();
if (!Base::isEmail($all['to'])) {
return Base::retError('请输入正确的收件人地址');
}
try {
Setting::validateAddr($all['to'], function($to) use ($all) {
Factory::mailer()
->setDsn("smtp://{$all['account']}:{$all['password']}@{$all['smtp_server']}:{$all['port']}?verify_peer=0")
->setMessage(EmailMessage::create()
->from(env('APP_NAME', 'Task') . " <{$all['account']}>")
->to($to)
->subject('Mail sending test')
->html('<p>收到此电子邮件意味着您的邮箱配置正确。</p><p>Receiving this email means that your mailbox is configured correctly.</p>'))
->send();
}, function () {
throw new \Exception("收件人地址错误或已被忽略");
});
return Base::retSuccess('成功发送');
} catch (\Throwable $e) {
// 一般是请求超时
if (str_contains($e->getMessage(), "Timed Out")) {
return Base::retError("language.TimedOut");
} elseif ($e->getCode() === 550) {
return Base::retError('邮件内容被拒绝,请检查邮箱是否开启接收功能');
} else {
return Base::retError($e->getMessage());
}
}
}
/**
* @api {get} api/system/version 18. 获取版本号
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName version
*
* @apiSuccess {String} version
* @apiSuccess {String} publish
*/
public function version()
{
$url = url('');
$package = Base::getPackage();
$array = [
'version' => Base::getVersion(),
'publish' => Arr::get($package, 'app.0.publish'),
];
if (is_array($package['app'])) {
foreach ($package['app'] as $item) {
$urls = $item['urls'] && is_array($item['urls']) ? $item['urls'] : $item['url'];
if (is_array($item['publish']) && Base::hostContrast($url, $urls)) {
$array['publish'] = $item['publish'];
}
}
}
return $array;
}
}

View File

@@ -2,7 +2,18 @@
namespace App\Http\Controllers\Api;
use App\Models\AbstractModel;
use App\Models\Meeting;
use App\Models\Project;
use App\Models\UmengAlias;
use App\Models\User;
use App\Models\UserDelete;
use App\Models\UserEmailVerification;
use App\Models\UserTransfer;
use App\Models\WebSocket;
use App\Models\WebSocketDialog;
use App\Models\WebSocketDialogMsg;
use App\Module\AgoraIO\AgoraTokenGenerator;
use App\Module\Base;
use Arr;
use Cache;
@@ -42,6 +53,7 @@ class UsersController extends AbstractController
$type = trim(Request::input('type'));
$email = trim(Request::input('email'));
$password = trim(Request::input('password'));
$isRegVerify = Base::settingFind('emailSetting', 'reg_verify') === 'open';
if ($type == 'reg') {
$setting = Base::setting('system');
if ($setting['reg'] == 'close') {
@@ -53,6 +65,10 @@ class UsersController extends AbstractController
}
}
$user = User::reg($email, $password);
if ($isRegVerify) {
UserEmailVerification::userEmailSend($user);
return Base::retError('注册成功,请验证邮箱后登录', ['code' => 'email']);
}
} else {
$needCode = !Base::isError(User::needCode($email));
if ($needCode) {
@@ -68,21 +84,25 @@ class UsersController extends AbstractController
$retError = function ($msg) use ($email) {
Cache::forever("code::" . $email, "need");
$needCode = !Base::isError(User::needCode($email));
$needData = [ 'code' => $needCode ? 'need' : 'no' ];
$needData = ['code' => $needCode ? 'need' : 'no'];
return Base::retError($msg, $needData);
};
$user = User::whereEmail($email)->first();
if (empty($user)) {
return $retError('号或密码错误');
return $retError('号或密码错误');
}
if ($user->password != Base::md52($password, $user->encrypt)) {
return $retError('号或密码错误');
return $retError('号或密码错误');
}
//
if (in_array('disable', $user->identity)) {
return $retError('帐号已停用...');
}
Cache::forget("code::" . $email);
if ($isRegVerify && $user->email_verity === 0) {
UserEmailVerification::userEmailSend($user);
return Base::retError('您还没有验证邮箱,请先登录邮箱通过验证邮件验证邮箱', ['code' => 'email']);
}
}
//
$array = [
@@ -95,6 +115,15 @@ class UsersController extends AbstractController
$user->updateInstance($array);
$user->save();
User::token($user);
//
if (!Project::withTrashed()->whereUserid($user->userid)->wherePersonal(1)->exists()) {
Project::createProject([
'name' => Base::Lang('个人项目'),
'desc' => Base::Lang('注册时系统自动创建项目,你可以自由删除。'),
'personal' => 1,
], $user->userid);
}
//
return Base::retSuccess($type == 'reg' ? "注册成功" : "登录成功", $user);
}
@@ -125,8 +154,6 @@ class UsersController extends AbstractController
* @apiGroup users
* @apiName login__codeimg
*
* @apiParam {String} email 用户名
*
* @apiSuccess {Image} data 返回数据(直接输出图片)
*/
public function login__codeimg()
@@ -204,7 +231,9 @@ class UsersController extends AbstractController
$user = User::auth();
User::token($user);
//
return Base::retSuccess('success', $user);
$data = $user->toArray();
$data['nickname_original'] = $user->getRawOriginal('nickname');
return Base::retSuccess('success', $data);
}
/**
@@ -216,6 +245,7 @@ class UsersController extends AbstractController
* @apiName editdata
*
* @apiParam {Object} [userimg] 会员头像(地址)
* @apiParam {String} [tel] 电话
* @apiParam {String} [nickname] 昵称
* @apiParam {String} [profession] 职位/职称
*
@@ -228,31 +258,42 @@ class UsersController extends AbstractController
$user = User::auth();
$data = Request::all();
$user->checkSystem(1);
//头像
// 头像
if (Arr::exists($data, 'userimg')) {
$userimg = Request::input('userimg');
if ($userimg) {
$userimg = is_array($userimg) ? $userimg[0]['path'] : $userimg;
$user->userimg = Base::unFillUrl($userimg);
} else {
$user->userimg = $userimg ? Base::unFillUrl(is_array($userimg) ? $userimg[0]['path'] : $userimg) : '';
if (str_contains($user->userimg, 'avatar/')) {
$user->userimg = '';
}
}
//昵称
// 电话
if (Arr::exists($data, 'tel')) {
$tel = trim(Request::input('tel'));
if (strlen($tel) < 6 || strlen($tel) > 20) {
return Base::retError('联系电话长度错误');
}
if ($tel != $user->tel && User::whereTel($tel)->exists()) {
return Base::retError('联系电话已存在');
}
$user->tel = $tel;
}
// 昵称
if (Arr::exists($data, 'nickname')) {
$nickname = trim(Request::input('nickname'));
if (mb_strlen($nickname) < 2) {
if ($nickname && mb_strlen($nickname) < 2) {
return Base::retError('昵称不可以少于2个字');
} elseif (mb_strlen($nickname) > 20) {
return Base::retError('昵称最多只能设置20个字');
} else {
} elseif ($nickname != $user->nickname) {
$user->nickname = $nickname;
$user->az = Base::getFirstCharter($nickname);
$user->pinyin = Base::cn2pinyin($nickname);
}
}
//职位/职称
// 职位/职称
if (Arr::exists($data, 'profession')) {
$profession = trim(Request::input('profession'));
if (mb_strlen($profession) < 2) {
if ($profession && mb_strlen($profession) < 2) {
return Base::retError('职位/职称不可以少于2个字');
} elseif (mb_strlen($profession) > 20) {
return Base::retError('职位/职称最多只能设置20个字');
@@ -263,7 +304,10 @@ class UsersController extends AbstractController
//
$user->save();
User::token($user);
User::AZUpdate($user->userid);
//
if (empty($user->userimg)) {
$user->userimg = $user->getUserimgAttribute(null);
}
return Base::retSuccess('修改成功', $user);
}
@@ -316,11 +360,17 @@ class UsersController extends AbstractController
* @apiName searchinfo
*
* @apiParam {Object} keys 搜索条件
* - keys.key 昵称、邮箱
* - keys.key 昵称、邮箱关键字
* - keys.disable 0-排除禁止默认1-含禁止2-仅禁止
* - keys.project_id 在指定项目ID
* - keys.no_project_id 不在指定项目ID
* - keys.dialog_id 在指定对话ID
* @apiParam {Object} sorts 排序方式
* - sorts.az 字母
* - sorts.az 字母asc|desc
* @apiParam {Number} updated_time 在这个时间戳之后更新的
* @apiParam {Number} state 获取在线状态
* - 0: 不获取(默认)
* - 1: 获取会员在线状态返回数据多一个online值
*
* @apiParam {Number} [take] 获取数量10-100
* @apiParam {Number} [page] 当前页,默认:1赋值分页模式take参数无效
@@ -332,32 +382,46 @@ class UsersController extends AbstractController
*/
public function search()
{
$builder = User::select(['userid', 'email', 'nickname', 'profession', 'userimg', 'az']);
$builder = User::select(User::$basicField);
//
$keys = Request::input('keys');
$sorts = Request::input('sorts');
if (is_array($keys)) {
if ($keys['key']) {
$builder->where(function($query) use ($keys) {
$query->where("email", "like", "%{$keys['key']}%")
->orWhere("nickname", "like", "%{$keys['key']}%");
});
}
if (intval($keys['project_id']) > 0) {
$builder->whereIn('userid', function ($query) use ($keys) {
$query->select('userid')->from('project_users')->where('project_id', $keys['project_id']);
});
}
if (intval($keys['no_project_id']) > 0) {
$builder->whereNotIn('userid', function ($query) use ($keys) {
$query->select('userid')->from('project_users')->where('project_id', $keys['no_project_id']);
});
}
$updatedTime = intval(Request::input('updated_time'));
$state = intval(Request::input('state', 0));
$keys = is_array($keys) ? $keys : [];
$sorts = is_array($sorts) ? $sorts : [];
//
if ($keys['key']) {
$builder->where(function($query) use ($keys) {
$query->where("email", "like", "%{$keys['key']}%")
->orWhere("nickname", "like", "%{$keys['key']}%");
});
}
if (is_array($sorts)) {
if (in_array($sorts['az'], ['asc', 'desc'])) {
$builder->orderBy('az', $sorts['az']);
}
if (intval($keys['disable']) == 0) {
$builder->whereNull("disable_at");
} elseif (intval($keys['disable']) == 2) {
$builder->whereNotNull("disable_at");
}
if ($updatedTime > 0) {
$builder->where("updated_at", ">=", Carbon::createFromTimestamp($updatedTime));
}
if (intval($keys['project_id']) > 0) {
$builder->whereIn('userid', function ($query) use ($keys) {
$query->select('userid')->from('project_users')->where('project_id', $keys['project_id']);
});
}
if (intval($keys['no_project_id']) > 0) {
$builder->whereNotIn('userid', function ($query) use ($keys) {
$query->select('userid')->from('project_users')->where('project_id', $keys['no_project_id']);
});
}
if (intval($keys['dialog_id']) > 0) {
$builder->whereIn('userid', function ($query) use ($keys) {
$query->select('userid')->from('web_socket_dialog_users')->where('dialog_id', $keys['dialog_id']);
});
}
if (in_array($sorts['az'], ['asc', 'desc'])) {
$builder->orderBy('az', $sorts['az']);
}
//
if (Request::exists('page')) {
@@ -365,6 +429,13 @@ class UsersController extends AbstractController
} else {
$list = $builder->orderBy('userid')->take(Base::getPaginate(100, 10, 'take'))->get();
}
//
if ($state === 1) {
$list->transform(function (User $userInfo) {
$userInfo->online = $userInfo->getOnlineStatus();
return $userInfo;
});
}
return Base::retSuccess('success', $list);
}
@@ -384,6 +455,8 @@ class UsersController extends AbstractController
*/
public function basic()
{
User::auth();
//
$userid = Request::input('userid');
$array = Base::json2array($userid);
if (empty($array)) {
@@ -395,6 +468,9 @@ class UsersController extends AbstractController
$retArray = [];
foreach ($array AS $id) {
$basic = User::userid2basic($id);
if (empty($basic)) {
$basic = UserDelete::userid2basic($id);
}
if ($basic) {
$retArray[] = $basic;
}
@@ -403,14 +479,35 @@ class UsersController extends AbstractController
}
/**
* 会员列表(限管理员)
* @api {get} api/users/lists 11. 会员列表(限管理员)
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName lists
*
* @apiParam {Object} [keys] 搜索条件
* - keys.key 邮箱/电话/昵称/职位赋值后keys.email、keys.tel、keys.nickname、keys.profession失效
* - keys.email 邮箱
* - keys.tel 电话
* - keys.nickname 昵称
* - keys.profession 职位
* - keys.identity 身份admin、noadmin
* - keys.disable 是否离职
* - yes: 仅离职
* - all: 全部
* - 其他值: 仅在职(默认)
* - keys.email_verity 邮箱是否认证
* - yes: 已认证
* - no: 未认证
* - 其他值: 全部(默认)
*
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:20最大:50
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function lists()
{
@@ -420,14 +517,30 @@ class UsersController extends AbstractController
//
$keys = Request::input('keys');
if (is_array($keys)) {
if ($keys['email']) {
$builder->where("email", "like", "%{$keys['email']}%");
}
if ($keys['nickname']) {
$builder->where("nickname", "like", "%{$keys['nickname']}%");
}
if ($keys['profession']) {
$builder->where("profession", "like", "%{$keys['profession']}%");
if ($keys['key']) {
if (str_contains($keys['key'], "@")) {
$builder->where("email", "like", "%{$keys['key']}%");
} else {
$builder->where(function($query) use ($keys) {
$query->where("email", "like", "%{$keys['key']}%")
->orWhere("tel", "like", "%{$keys['key']}%")
->orWhere("nickname", "like", "%{$keys['key']}%")
->orWhere("profession", "like", "%{$keys['key']}%");
});
}
} else {
if ($keys['email']) {
$builder->where("email", "like", "%{$keys['email']}%");
}
if ($keys['tel']) {
$builder->where("tel", "like", "%{$keys['tel']}%");
}
if ($keys['nickname']) {
$builder->where("nickname", "like", "%{$keys['nickname']}%");
}
if ($keys['profession']) {
$builder->where("profession", "like", "%{$keys['profession']}%");
}
}
if ($keys['identity']) {
if (Base::leftExists($keys['identity'], "no")) {
@@ -436,6 +549,18 @@ class UsersController extends AbstractController
$builder->where("identity", "like", "%,{$keys['identity']},%");
}
}
if ($keys['disable'] === 'yes') {
$builder->whereNotNull('disable_at');
} elseif ($keys['disable'] !== 'all') {
$builder->whereNull('disable_at');
}
if ($keys['email_verity'] === 'yes') {
$builder->whereEmailVerity(1);
} elseif ($keys['email_verity'] === 'no') {
$builder->whereEmailVerity(0);
}
} else {
$builder->whereNull('disable_at');
}
$list = $builder->orderByDesc('userid')->paginate(Base::getPaginate(50, 20));
//
@@ -443,22 +568,36 @@ class UsersController extends AbstractController
}
/**
* 操作会员(限管理员)
* @api {get} api/users/operation 12. 操作会员(限管理员)
*
* @apiParam {Number} userid 会员ID
* @apiParam {String} [type] 操作
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName operation
*
* @apiParam {Number} userid 会员ID
* @apiParam {String} [type] 操作
* - setadmin 设为管理员
* - clearadmin 取消管理员
* - setdisable 设为禁用
* - cleardisable 取消禁用
* - delete 删除会员
* @apiParam {String} [password] 新的密码
* @apiParam {String} [nickname] 昵称
* @apiParam {String} [profession] 职位
* - setdisable 设为离职(需要参数 disable_time、transfer_userid
* - cleardisable 取消离职
* - delete 删除会员(需要参数 delete_reason
* @apiParam {String} [email] 邮箱地址
* @apiParam {String} [tel] 联系电话
* @apiParam {String} [password] 新的密码
* @apiParam {String} [nickname] 昵称
* @apiParam {String} [profession] 职位
* @apiParam {String} [disable_time] 离职时间
* @apiParam {String} [transfer_userid] 离职交接人
* @apiParam {String} [delete_reason] 删除原因
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function operation()
{
User::auth('admin');
$user = User::auth('admin');
//
$data = Request::all();
$userid = intval($data['userid']);
@@ -466,11 +605,12 @@ class UsersController extends AbstractController
//
$userInfo = User::find($userid);
if (empty($userInfo)) {
return Base::retError('员不存在或已被删除');
return Base::retError('员不存在或已被删除');
}
$userInfo->checkSystem(1);
//
$upArray = [];
$transferUser = null;
switch ($type) {
case 'setadmin':
$upArray['identity'] = array_diff($userInfo->identity, ['admin']);
@@ -482,21 +622,59 @@ class UsersController extends AbstractController
break;
case 'setdisable':
if ($userInfo->userid === $user->userid) {
return Base::retError('不能操作自己离职');
}
$upArray['identity'] = array_diff($userInfo->identity, ['disable']);
$upArray['identity'][] = 'disable';
$upArray['disable_at'] = Carbon::parse($data['disable_time']);
$transferUserid = is_array($data['transfer_userid']) ? $data['transfer_userid'][0] : $data['transfer_userid'];
$transferUser = User::find(intval($transferUserid));
if (empty($transferUser)) {
return Base::retError('请选择正确的交接人');
}
if ($transferUser->userid === $userInfo->userid) {
return Base::retError('不能移交给自己');
}
if (in_array('disable', $transferUser->identity)) {
return Base::retError('交接人已离职,请选择另一个交接人');
}
break;
case 'cleardisable':
$upArray['identity'] = array_diff($userInfo->identity, ['disable']);
$upArray['disable_at'] = null;
break;
case 'delete':
$userInfo->delete();
if ($userInfo->userid === $user->userid) {
return Base::retError('不能删除自己');
}
if (empty($data['delete_reason'])) {
return Base::retError('请填写删除原因');
}
$userInfo->deleteUser($data['delete_reason']);
break;
}
if (isset($upArray['identity'])) {
$upArray['identity'] = "," . implode(",", $upArray['identity']) . ",";
}
// 邮箱
if (Arr::exists($data, 'email')) {
$email = trim($data['email']);
if (User::whereEmail($email)->where('userid', '!=', $userInfo->userid)->exists()) {
return Base::retError('邮箱地址已存在');
}
$upArray['email'] = $email;
}
// 电话
if (Arr::exists($data, 'tel')) {
$tel = trim($data['tel']);
if (User::whereTel($tel)->where('userid', '!=', $userInfo->userid)->exists()) {
return Base::retError('联系电话已存在');
}
$upArray['tel'] = $tel;
}
// 密码
if (Arr::exists($data, 'password')) {
$password = trim($data['password']);
@@ -508,18 +686,20 @@ class UsersController extends AbstractController
// 昵称
if (Arr::exists($data, 'nickname')) {
$nickname = trim($data['nickname']);
if (mb_strlen($nickname) < 2) {
if ($nickname && mb_strlen($nickname) < 2) {
return Base::retError('昵称不可以少于2个字');
} elseif (mb_strlen($nickname) > 20) {
return Base::retError('昵称最多只能设置20个字');
} else {
$upArray['nickname'] = $nickname;
$upArray['az'] = Base::getFirstCharter($nickname);
$upArray['pinyin'] = Base::cn2pinyin($nickname);
}
}
// 职位/职称
if (Arr::exists($data, 'profession')) {
$profession = trim($data['profession']);
if (mb_strlen($profession) < 2) {
if ($profession && mb_strlen($profession) < 2) {
return Base::retError('职位/职称不可以少于2个字');
} elseif (mb_strlen($profession) > 20) {
return Base::retError('职位/职称最多只能设置20个字');
@@ -528,10 +708,432 @@ class UsersController extends AbstractController
}
}
if ($upArray) {
$userInfo->updateInstance($upArray);
$userInfo->save();
AbstractModel::transaction(function() use ($user, $type, $upArray, $userInfo, $transferUser) {
$userInfo->updateInstance($upArray);
$userInfo->save();
if ($type === 'setdisable') {
$userTransfer = UserTransfer::createInstance([
'original_userid' => $userInfo->userid,
'new_userid' => $transferUser->userid,
]);
$userTransfer->save();
$userTransfer->start();
// 离职移出全员群组
$dialog = WebSocketDialog::whereGroupType('all')->orderByDesc('id')->first();
$dialog?->exitGroup($userInfo->userid, 'remove', false);
} elseif ($type === 'cleardisable') {
// 取消离职重新加入全员群组
$dialog = WebSocketDialog::whereGroupType('all')->orderByDesc('id')->first();
$dialog?->joinGroup($userInfo->userid, $user->userid);
}
});
}
//
return Base::retSuccess('修改成功', $userInfo);
}
/**
* @api {get} api/users/email/verification 13. 邮箱验证
*
* @apiDescription 不需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName email__verification
*
* @apiParam {String} code 验证参数
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据(同"获取我的信息"接口)
*/
public function email__verification()
{
$data = Request::input();
// 表单验证
Base::validator($data, [
'code.required' => '验证码不能为空',
]);
//
$res = UserEmailVerification::whereCode($data['code'])->first();
if (empty($res)) {
return Base::retError('无效连接,请重新注册');
}
// 如果已经校验过
if (intval($res->status) === 1)
return Base::retError('链接已经使用过', ['code' => 2]);
$oldTime = Carbon::parse($res->created_at)->timestamp;
$time = Base::Time();
// 30分钟失效
if (abs($time - $oldTime) > 1800) {
return Base::retError("链接已失效,请重新登录/注册");
}
UserEmailVerification::whereCode($data['code'])->update([
'status' => 1
]);
User::whereUserid($res->userid)->update([
'email_verity' => 1
]);
return Base::retSuccess('绑定邮箱成功');
}
/**
* @api {get} api/users/umeng/alias 14. 设置友盟别名
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName umeng__alias
*
* @apiParam {String} alias 别名
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据(同"获取我的信息"接口)
*/
public function umeng__alias()
{
$data = Request::input();
// 表单验证
Base::validator($data, [
'alias.required' => '别名不能为空',
'alias.between:2,20' => '别名的长度在2-20个字符',
]);
//
$agent = strtolower(Request::server('HTTP_USER_AGENT'));
if (str_contains($agent, 'android')) {
$platform = 'android';
} elseif (str_contains($agent, 'iphone') || str_contains($agent, 'ipad')) {
$platform = 'ios';
} else {
return Base::retError('设备类型错误');
}
//
$user = User::auth();
$inArray = [
'userid' => $user->userid,
'alias' => $data['alias'],
'platform' => $platform,
];
$row = UmengAlias::where($inArray);
if ($row->exists()) {
$row->update(['updated_at' => Carbon::now()]);
return Base::retSuccess('别名已存在');
}
$row = UmengAlias::createInstance($inArray);
if ($row->save()) {
return Base::retSuccess('添加成功');
} else {
return Base::retError('添加错误');
}
}
/**
* @api {get} api/users/ws/exist 15. websocket是否存在
*
* @apiDescription 查询websocket连接是否存在
* @apiVersion 1.0.0
* @apiGroup users
* @apiName ws__exist
*
* @apiSuccess {Number} ret 返回状态码1存在、0不存在
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据(同"获取我的信息"接口)
*/
public function ws__exist()
{
$fd = Request::header('fd');
if (empty($fd)) {
return Base::retError('empty');
}
if (WebSocket::whereFd($fd)->exists()) {
return Base::retSuccess('success');
} else {
return Base::retError('not exist');
}
}
/**
* @api {get} api/users/meeting/open 16. 【会议】创建会议、加入会议
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName meeting__open
*
* @apiParam {String} type 类型
* - create: 创建会议有效参数name、userids
* - join: 加入会议有效参数meetingid (必填)
* @apiParam {String} [meetingid] 频道ID不是数字
* @apiParam {String} [name] 会话ID
* @apiParam {Array} [userids] 邀请成员
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function meeting__open()
{
$user = User::auth();
//
$type = trim(Request::input('type'));
$meetingid = trim(Request::input('meetingid'));
$name = trim(Request::input('name'));
$userids = Request::input('userids');
$isCreate = false;
// 创建、加入
if ($type === 'join') {
$meeting = Meeting::whereMeetingid($meetingid)->first();
if (empty($meeting)) {
return Base::retError('频道ID不存在');
}
} elseif ($type === 'create') {
$meetingid = strtoupper(Base::generatePassword(11, 1));
$name = $name ?: "{$user->nickname} 发起的会议";
$channel = "DooTask:" . substr(md5($meetingid . env("APP_KEY")), 16);
$meeting = Meeting::createInstance([
'meetingid' => $meetingid,
'name' => $name,
'channel' => $channel,
'userid' => $user->userid
]);
$meeting->save();
$isCreate = true;
} else {
return Base::retError('参数错误');
}
$data = $meeting->toArray();
// 创建令牌
$meetingSetting = Base::setting('meetingSetting');
if ($meetingSetting['open'] !== 'open') {
return Base::retError('会议功能未开启,请联系管理员开启');
}
if (empty($meetingSetting['appid']) || empty($meetingSetting['app_certificate'])) {
return Base::retError('会议功能配置错误,请联系管理员');
}
$uid = $user->userid . '_' . Request::header('fd');
try {
$service = new AgoraTokenGenerator($meetingSetting['appid'], $meetingSetting['app_certificate'], $meeting->channel, $uid);
} catch (\Exception $e) {
return Base::retError($e->getMessage());
}
$token = $service->buildToken();
if (empty($token)) {
return Base::retError('会议令牌创建失败');
}
// 发送给邀请人
$msgs = [];
if ($isCreate) {
foreach ($userids as $userid) {
if (!User::whereUserid($userid)->exists()) {
continue;
}
$dialog = WebSocketDialog::checkUserDialog($user->userid, $userid);
if ($dialog) {
$res = WebSocketDialogMsg::sendMsg(null, $dialog->id, 'meeting', $data, $user->userid);
if (Base::isSuccess($res)) {
$msgs[] = $res['data'];
}
}
}
}
//
$data['appid'] = $meetingSetting['appid'];
$data['uid'] = $uid;
$data['token'] = $token;
$data['msgs'] = $msgs;
return Base::retSuccess('success', $data);
}
/**
* @api {get} api/users/meeting/invitation 17. 【会议】发送邀请
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName meeting__invitation
*
* @apiParam {String} meetingid 频道ID不是数字
* @apiParam {Array} userids 邀请成员
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function meeting__invitation()
{
$user = User::auth();
//
$meetingid = trim(Request::input('meetingid'));
$userids = Request::input('userids');
//
$meeting = Meeting::whereMeetingid($meetingid)->first();
if (empty($meeting)) {
return Base::retError('频道ID不存在');
}
$data = $meeting->toArray();
// 发送给邀请人
$msgs = [];
foreach ($userids as $userid) {
if (!User::whereUserid($userid)->exists()) {
continue;
}
$dialog = WebSocketDialog::checkUserDialog($user->userid, $userid);
if ($dialog) {
$res = WebSocketDialogMsg::sendMsg(null, $dialog->id, 'meeting', $data, $user->userid);
if (Base::isSuccess($res)) {
$msgs[] = $res['data'];
}
}
}
//
$data['msgs'] = $msgs;
return Base::retSuccess('发送邀请成功', $data);
}
/**
* @api {get} api/users/email/send 18. 发送邮箱验证码
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName email__send
*
* @apiParam {Number} type 邮件类型
* @apiParam {String} email 邮箱地址
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function email__send()
{
$user = User::auth();
//
$type = Request::input('type', 2);
$email = Request::input('email');
if (!$email) {
return Base::retError('请输入新邮箱地址');
}
if (!Base::isEmail($email)) {
return Base::retError('邮箱地址错误');
}
if ($user->email == $email && $type == 2) {
return Base::retError('不能与旧邮箱一致');
}
if ($user->email != $email && $type == 3) {
return Base::retError('与当前登录邮箱不一致');
}
if (User::where('userid', '<>', $user->userid)->whereEmail($email)->exists()) {
return Base::retError('邮箱地址已存在');
}
UserEmailVerification::userEmailSend($user, $type, $email);
return Base::retSuccess('发送成功');
}
/**
* @api {get} api/users/email/edit 19. 修改邮箱
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName edit__email
*
* @apiParam {String} newEmail 新邮箱地址
* @apiParam {String} code 邮箱验证码
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function email__edit()
{
$user = User::auth();
//
$user->checkSystem();
//
$newEmail = trim(Request::input('newEmail'));
$code = trim(Request::input('code'));
if (!$newEmail) {
return Base::retError('请输入新邮箱地址');
}
if (!Base::isEmail($newEmail)) {
return Base::retError('邮箱地址错误');
}
$isRegVerify = Base::settingFind('emailSetting', 'reg_verify') === 'open';
if ($isRegVerify) {
UserEmailVerification::verify($newEmail, $code, 2);
}
$user->email = $newEmail;
$user->save();
User::token($user);
return Base::retSuccess('修改成功', $user);
}
/**
* @api {get} api/users/delete/account 20. 删除帐号
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName delete__account
*
* @apiParam {String} email 帐号邮箱
* @apiParam {String} code 邮箱验证码
* @apiParam {String} reason 注销理由
* @apiParam {String} password 登录密码
* @apiParam {Number} type 类型
* - warning: 提交校验
* - confirm: 确认删除
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function delete__account()
{
$user = User::auth();
//
$user->checkSystem(1);
//
$email = Request::input('email');
$code = Request::input('code');
$reason = Request::input('reason');
$password = Request::input('password');
$type = Request::input('type');
if (!$email) {
return Base::retError('请输入新邮箱地址');
}
if (!Base::isEmail($email)) {
return Base::retError('邮箱地址错误');
}
if ($user->email != $email) {
return Base::retError('与当前登录邮箱不一致');
}
$isRegVerify = Base::settingFind('emailSetting', 'reg_verify') === 'open';
if ($isRegVerify) {
UserEmailVerification::verify($email, $code, 3);
} else {
if (!$password) {
return Base::retError('请输入登录密码');
}
if ($user->password != Base::md52($password, $user->encrypt)) {
return Base::retError('密码错误');
}
}
if ($type == 'confirm') {
if ($user->deleteUser($reason)) {
return Base::retSuccess('删除成功', $user);
} else {
return Base::retError('删除失败');
}
}
return Base::retSuccess('success', $user);
}
}

View File

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

View File

@@ -2,10 +2,20 @@
namespace App\Http\Controllers;
use App\Models\File;
use App\Module\Base;
use App\Module\Ihttp;
use App\Module\RandomColor;
use App\Tasks\AutoArchivedTask;
use App\Tasks\DeleteTmpTask;
use App\Tasks\EmailNoticeTask;
use App\Tasks\LoopTask;
use Arr;
use Cache;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use LasseRafn\InitialAvatarGenerator\InitialAvatar;
use Redirect;
use Request;
/**
@@ -21,6 +31,10 @@ class IndexController extends InvokeController
if ($action) {
$app .= "__" . $action;
}
if ($app === 'manifest.txt') {
$app = 'manifest';
$child = 'txt';
}
if (!method_exists($this, $app)) {
$app = method_exists($this, $method) ? $method : 'main';
}
@@ -29,11 +43,118 @@ class IndexController extends InvokeController
/**
* 首页
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @return \Illuminate\Http\Response
*/
public function main()
{
return view('main', ['version' => Base::getVersion()]);
$hash = 'no';
$path = public_path('js/hash');
$murl = url('manifest.txt');
if (file_exists($path)) {
$hash = trim(file_get_contents(public_path('js/hash')));
if (strlen($hash) > 16) {
$hash = 'long';
}
}
return response()->view('main', [
'version' => Base::getVersion(),
'hash' => $hash
])->header('Link', "<{$murl}>; rel=\"prefetch\"");
}
/**
* Manifest
* @param $child
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|string
*/
public function manifest($child = '')
{
if (empty($child)) {
$murl = url('manifest.txt');
return response($murl)->header('Link', "<{$murl}>; rel=\"prefetch\"");
}
$array = [
"office/web-apps/apps/api/documents/api.js?hash=" . Base::getVersion(),
"office/7.1.1-23/web-apps/vendor/requirejs/require.js",
"office/7.1.1-23/web-apps/apps/api/documents/api.js",
"office/7.1.1-23/sdkjs/common/AllFonts.js",
"office/7.1.1-23/web-apps/vendor/xregexp/xregexp-all-min.js",
"office/7.1.1-23/web-apps/vendor/sockjs/sockjs.min.js",
"office/7.1.1-23/web-apps/vendor/jszip/jszip.min.js",
"office/7.1.1-23/web-apps/vendor/jszip-utils/jszip-utils.min.js",
"office/7.1.1-23/sdkjs/common/libfont/wasm/fonts.js",
"office/7.1.1-23/sdkjs/common/Charts/ChartStyles.js",
"office/7.1.1-23/sdkjs/slide/themes//themes.js",
"office/7.1.1-23/web-apps/apps/presentationeditor/main/app.js",
"office/7.1.1-23/sdkjs/slide/sdk-all-min.js",
"office/7.1.1-23/sdkjs/slide/sdk-all.js",
"office/7.1.1-23/web-apps/apps/documenteditor/main/app.js",
"office/7.1.1-23/sdkjs/word/sdk-all-min.js",
"office/7.1.1-23/sdkjs/word/sdk-all.js",
"office/7.1.1-23/web-apps/apps/spreadsheeteditor/main/app.js",
"office/7.1.1-23/sdkjs/cell/sdk-all-min.js",
"office/7.1.1-23/sdkjs/cell/sdk-all.js",
];
foreach ($array as &$item) {
$item = url($item);
}
return implode(PHP_EOL, $array);
}
/**
* 获取版本号
* @return \Illuminate\Http\RedirectResponse
*/
public function version()
{
return Redirect::to(Base::fillUrl('api/system/version'), 301);
}
/**
* 头像
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
*/
public function avatar()
{
$segment = Request::segment(2);
if ($segment && preg_match('/.*?\.png$/i', $segment)) {
$name = substr($segment, 0, -4);
} else {
$name = Request::input('name', 'H');
}
$size = Request::input('size', 128);
$color = Request::input('color');
$background = Request::input('background');
//
if (preg_match('/^[\x{4e00}-\x{9fa5}]+$/u', $name)) {
$name = mb_substr($name, mb_strlen($name) - 2);
}
if (empty($color)) {
$color = '#ffffff';
$cacheKey = "avatarBackgroundColor::" . md5($name);
$background = Cache::rememberForever($cacheKey, function() {
return RandomColor::one(['luminosity' => 'dark']);
});
}
//
$avatar = new InitialAvatar();
$content = $avatar->name($name)
->size($size)
->color($color)
->background($background)
->fontSize(0.35)
->autoFont()
->generate()
->stream('png', 100);
//
return response($content)
->header('Pragma', 'public')
->header('Cache-Control', 'max-age=1814400')
->header('Content-type', 'image/png')
->header('Expires', gmdate('D, d M Y H:i:s \G\M\T', time() + 1814400));
}
/**
@@ -55,13 +176,151 @@ class IndexController extends InvokeController
// 限制内网访问
return "Forbidden Access";
}
// 自动归档
Task::deliver(new AutoArchivedTask());
// 邮件通知
Task::deliver(new EmailNoticeTask());
// 删除过期的临时表数据
Task::deliver(new DeleteTmpTask('wg_tmp_msgs', 1));
Task::deliver(new DeleteTmpTask('tmp', 24));
// 周期任务
Task::deliver(new LoopTask());
return "success";
}
/**
* 桌面客户端发布
*/
public function desktop__publish($name = '')
{
$genericVersion = Request::header('generic-version');
$latestFile = public_path("uploads/desktop/latest");
$latestVersion = file_exists($latestFile) ? trim(file_get_contents($latestFile)) : "0.0.1";
if (strtolower($name) === 'latest') {
$name = $latestVersion;
}
// 上传
if (preg_match("/^\d+\.\d+\.\d+$/", $genericVersion)) {
if (version_compare($genericVersion, $latestVersion) > -1) { // 限制上传版本必须 ≥ 当前版本
$genericPath = "uploads/desktop/{$genericVersion}/";
$res = Base::upload([
"file" => Request::file('file'),
"type" => 'desktop',
"path" => $genericPath,
"fileName" => true
]);
if (Base::isSuccess($res)) {
file_put_contents($latestFile, $genericVersion);
}
return $res;
}
}
// 列表
if (preg_match("/^\d+\.\d+\.\d+$/", $name)) {
$path = "uploads/desktop/{$name}";
$dirPath = public_path($path);
$lists = Base::readDir($dirPath);
$files = [];
foreach ($lists as $file) {
if (str_ends_with($file, '.yml') || str_ends_with($file, '.yaml')) {
continue;
}
$fileName = Base::leftDelete($file, $dirPath);
$files[] = [
'name' => substr($fileName, 1),
'time' => date("Y-m-d H:i:s", fileatime($file)),
'size' => Base::readableBytes(filesize($file)),
'url' => Base::fillUrl($path . $fileName),
];
}
return view('desktop', ['version' => $name, 'files' => $files]);
}
// 下载
if ($name && file_exists($latestFile)) {
$genericVersion = file_get_contents($latestFile);
if (preg_match("/^\d+\.\d+\.\d+$/", $genericVersion)) {
$filePath = public_path("uploads/desktop/{$genericVersion}/{$name}");
if (file_exists($filePath)) {
return response()->download($filePath);
}
}
}
return abort(404);
}
/**
* Drawio 图标搜索
* @return array|mixed
*/
public function drawio__iconsearch()
{
$query = Request::input('q');
$page = Request::input('p');
$size = Request::input('c');
$url = "https://app.diagrams.net/iconSearch?q={$query}&p={$page}&c={$size}";
$result = Cache::remember("drawioIconsearch::" . md5($url), now()->addDays(15), function () use ($url) {
return Ihttp::ihttp_get($url);
});
if (Base::isSuccess($result)) {
return $result['data'];
}
return [
'icons' => [],
'total_count' => 0
];
}
/**
* 预览文件
* @return array|mixed
*/
public function online__preview()
{
$key = trim(Request::input('key'));
//
$data = parse_url($key);
$path = Arr::get($data, 'path');
$file = public_path($path);
//
if (file_exists($file)) {
parse_str($data['query'], $query);
$name = Arr::get($query, 'name');
$ext = strtolower(Arr::get($query, 'ext'));
$userAgent = strtolower(Request::server('HTTP_USER_AGENT'));
if ($ext === 'pdf'
&& (str_contains($userAgent, 'electron') || str_contains($userAgent, 'chrome'))) {
return response()->download($file, $name, [], 'inline');
}
//
if (in_array($ext, File::localExt)) {
$url = Base::fillUrl($path);
} else {
$url = 'http://' . env('APP_IPPR') . '.3/' . $path;
}
if ($ext !== 'pdf') {
$url = Base::urlAddparameter($url, [
'fullfilename' => $name . '.' . $ext
]);
}
$toUrl = Base::fillUrl("fileview/onlinePreview?url=" . urlencode(base64_encode($url)));
return Redirect::to($toUrl, 301);
}
return abort(404);
}
/**
* 设置语言和皮肤
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
public function setting__theme_language()
{
return view('setting', [
'theme' => Request::input('theme'),
'language' => Request::input('language')
]);
}
/**
* 提取所有中文
* @return array|string

View File

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

View File

@@ -10,13 +10,15 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
/**
* App\Model\AbstractModel
* App\Models\AbstractModel
*
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel query()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
* @method static \Illuminate\Database\Eloquent\Model|object|static|null cancelAppend()
* @method static \Illuminate\Database\Eloquent\Model|object|static|null cancelHidden()
* @method static \Illuminate\Database\Eloquent\Builder|static with($relations)
* @method static \Illuminate\Database\Query\Builder|static select($columns = [])
* @method static \Illuminate\Database\Query\Builder|static whereNotIn($column, $values, $boolean = 'and')
@@ -44,7 +46,7 @@ class AbstractModel extends Model
{
try {
return $this->save();
} catch (\Exception $e) {
} catch (\Throwable) {
return false;
}
}
@@ -62,6 +64,24 @@ class AbstractModel extends Model
return $this->$key;
}
/**
* 取消附加值
* @return static
*/
protected function scopeCancelAppend()
{
return $this->setAppends([]);
}
/**
* 取消隐藏值
* @return static
*/
protected function scopeCancelHidden()
{
return $this->setHidden([]);
}
/**
* 为数组 / JSON 序列化准备日期。
* @param DateTimeInterface $date
@@ -133,16 +153,23 @@ class AbstractModel extends Model
* @param $where
* @param array $update 存在时更新的内容
* @param array $insert 不存在时插入的内容,如果没有则插入更新内容
* @param bool $isInsert 是否是插入数据
* @return AbstractModel|\Illuminate\Database\Eloquent\Builder|Model|object|static|null
*/
public static function updateInsert($where, $update = [], $insert = [])
public static function updateInsert($where, $update = [], $insert = [], &$isInsert = true)
{
$row = static::where($where)->first();
if (empty($row)) {
$row = new static;
$row->updateInstance(array_merge($where, $insert ?: $update));
$array = array_merge($where, $insert ?: $update);
if (isset($array[$row->primaryKey])) {
unset($array[$row->primaryKey]);
}
$row->updateInstance($array);
$isInsert = true;
} elseif ($update) {
$row->updateInstance($update);
$isInsert = false;
}
if (!$row->save()) {
return null;

View File

@@ -14,6 +14,7 @@ use Request;
*
* @property int $id
* @property int|null $pid 上级ID
* @property string|null $pids 上级ID递归
* @property int|null $cid 复制ID
* @property string|null $name 名称
* @property string|null $type 类型
@@ -21,6 +22,7 @@ use Request;
* @property int|null $size 大小(B)
* @property int|null $userid 拥有者ID
* @property int|null $share 是否共享
* @property int|null $pshare 所属分享ID
* @property int|null $created_id 创建者
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
@@ -37,6 +39,8 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|File whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|File wherePid($value)
* @method static \Illuminate\Database\Eloquent\Builder|File wherePids($value)
* @method static \Illuminate\Database\Eloquent\Builder|File wherePshare($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereShare($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereSize($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereType($value)
@@ -51,28 +55,55 @@ class File extends AbstractModel
use SoftDeletes;
/**
* 是否有访问权限
* @param $userid
* 文件文件
*/
public function exceAllow($userid)
{
if ($this->chackAllow($userid) === -1) {
throw new ApiException('没有访问权限');
}
}
const codeExt = [
'txt',
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css',
'dockerfile', 'go', 'golang', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
'ocamlmakefile', 'make', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php',
'inc', 'phtml', 'shtml', 'php3', 'php4', 'php5', 'phps', 'phpt', 'aw', 'ctp', 'module', 'ps1', 'py', 'r', 'rb', 'ru', 'gemspec', 'rake', 'guardfile', 'rakefile',
'gemfile', 'rs', 'sass', 'scss', 'sh', 'bash', 'bashrc', 'sql', 'sqlserver', 'swift', 'ts', 'typescript', 'str', 'vbs', 'vb', 'v', 'vh', 'sv', 'svh', 'xml',
'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx', 'plist'
];
/**
* office文件
*/
const officeExt = [
'doc', 'docx',
'xls', 'xlsx',
'ppt', 'pptx',
];
/**
* 图片文件
*/
const imageExt = [
'jpg', 'jpeg', 'png', 'gif', 'bmp'
];
/**
* 本地媒体文件
*/
const localExt = [
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw',
'tif', 'tiff',
'mp3', 'wav', 'mp4', 'flv',
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm',
];
/**
* 是否有访问权限
* ① 自己的文件夹
* ② 在指定共享成员内
* @param $userid
* @return int -1:没有权限0:只读1:读写
* @return int -1:没有权限0:访问权限1:读写权限1000:所有者或创建者
*/
public function chackAllow($userid)
public function getPermission($userid)
{
if ($userid == $this->userid) {
// ① 自己的文件夹
return 1;
if ($userid == $this->userid || $userid == $this->created_id) {
// ① 自己的文件夹 或 自己创建的文件夹
return 1000;
}
$row = $this->getShareInfo();
if ($row) {
@@ -113,7 +144,7 @@ class File extends AbstractModel
/**
* 是否处于共享文件夹内(不含自身)
* @return bool
* @return File|false
*/
public function isNnShare()
{
@@ -124,19 +155,28 @@ class File extends AbstractModel
break;
}
if ($row->share) {
return true;
return $row;
}
$pid = $row->pid;
}
return false;
}
/**
* 目录内是否存在共享文件或文件夹
* @return bool
*/
public function isSubShare()
{
return $this->type == 'folder' && File::where("pids", "like", "%,{$this->id},%")->whereShare(1)->exists();
}
/**
* 设置/关闭 共享(同时遍历取消里面的共享)
* @param $share
* @return bool
*/
public function setShare($share = null)
public function updataShare($share = null)
{
if ($share === null) {
$share = FileUser::whereFileId($this->id)->count() == 0 ? 0 : 1;
@@ -145,10 +185,14 @@ class File extends AbstractModel
AbstractModel::transaction(function () use ($share) {
$this->share = $share;
$this->save();
File::where("pids", "like", "%,{$this->id},%")->update(['pshare' => $share ? $this->id : 0]);
if ($share === 0) {
FileUser::deleteFileAll($this->id, $this->userid);
}
$list = self::wherePid($this->id)->get();
if ($list->isNotEmpty()) {
foreach ($list as $item) {
$item->setShare(0);
$item->updataShare(0);
}
}
});
@@ -156,6 +200,75 @@ class File extends AbstractModel
return true;
}
/**
* 处理重名
* @return void
*/
public function handleDuplicateName()
{
$builder = self::wherePid($this->pid)->whereUserid($this->userid)->whereExt($this->ext);
$exist = $builder->clone()->whereName($this->name)->exists();
if (!$exist) {
return; // 未重名,不需要处理
}
// 发现重名,自动重命名
$nextNum = 2;
if (preg_match("/(.*?)(\s+\(\d+\))*$/", $this->name)) {
$preName = preg_replace("/(.*?)(\s+\(\d+\))*$/", "$1", $this->name);
$nextNum = $builder->clone()->where("name", "LIKE", "{$preName}%")->count() + 1;
}
$newName = "{$this->name} ({$nextNum})";
if ($builder->clone()->whereName($newName)->exists()) {
$nextNum = rand(100, 9999);
$newName = "{$this->name} ({$nextNum})";
}
$this->name = $newName;
}
/**
* 保存前更新pids/pshare
* @return bool
*/
public function saveBeforePP()
{
$pid = $this->pid;
$pshare = $this->share ? $this->id : 0;
$array = [];
while ($pid > 0) {
$array[] = $pid;
$file = self::select(['id', 'pid', 'share'])->find($pid);
if ($file) {
$pid = $file->pid;
if ($file->share) {
$pshare = $file->id;
}
} else {
$pid = 0;
}
}
$opids = $this->pids;
if ($array) {
$array = array_values(array_reverse($array));
$this->pids = ',' . implode(',', $array) . ',';
} else {
$this->pids = '';
}
$this->pshare = $pshare;
if (!$this->save()) {
return false;
}
// 更新子文件(夹)
if ($opids != $this->pids) {
self::wherePid($this->id)->chunkById(100, function ($lists) {
/** @var self $item */
foreach ($lists as $item) {
$item->saveBeforePP();
}
});
}
return true;
}
/**
* 遍历删除文件(夹)
* @return bool
@@ -165,8 +278,7 @@ class File extends AbstractModel
AbstractModel::transaction(function () {
$this->delete();
$this->pushMsg('delete');
FileLink::whereFileId($this->id)->delete();
FileUser::whereFileId($this->id)->delete();
FileUser::deleteFileAll($this->id);
FileContent::whereFid($this->id)->delete();
$list = self::wherePid($this->id)->get();
if ($list->isNotEmpty()) {
@@ -192,19 +304,7 @@ class File extends AbstractModel
];
}
//
if ($userid === null) {
$userid = [$this->userid];
if ($this->share == 1) {
$builder = WebSocket::select(['userid']);
if ($action == 'content') {
$builder->wherePath('file/content/' . $this->id);
}
$userid = array_merge($userid, $builder->pluck('userid')->toArray());
} elseif ($this->share == 2) {
$userid = array_merge($userid, FileUser::whereFileId($this->id)->pluck('userid')->toArray());
}
$userid = array_values(array_filter(array_unique($userid)));
}
$userid = $this->pushUserid($action, $userid);
if (empty($userid)) {
return;
}
@@ -227,19 +327,216 @@ class File extends AbstractModel
Task::deliver($task);
}
/**
* 获取推送会员
* @param $action
* @param $userid
* @return array|int[]|mixed|null[]
*/
public function pushUserid($action, $userid = null) {
$wherePath = "/manage/file";
if ($userid === null) {
$array = [$this->userid];
if ($action == 'add' && $this->pid == 0) {
return $array;
}
if ($action == 'content') {
$wherePath = "/single/file/{$this->id}";
} elseif ($this->pid > 0) {
$wherePath = "/manage/file/{$this->pid}";
} else {
$tmpArray = FileUser::whereFileId($this->id)->pluck('userid')->toArray();
if (empty($tmpArray)) {
return $array;
}
if (!in_array(0, $tmpArray)) {
return $tmpArray;
}
}
$tmpArray = WebSocket::wherePath($wherePath)->pluck('userid')->toArray();
if (empty($tmpArray)) {
return $array;
}
$array = array_values(array_filter(array_unique(array_merge($array, $tmpArray))));
} else {
$array = is_array($userid) ? $userid : [$userid];
if (in_array(0, $array)) {
return WebSocket::wherePath($wherePath)->pluck('userid')->toArray();
}
}
return $array;
}
/**
* 处理返回图片地址
* @param array $item
* @return array
*/
public static function handleImageUrl($item)
{
if (in_array($item['ext'], self::imageExt) ) {
$content = Base::json2array(FileContent::whereFid($item['id'])->orderByDesc('id')->value('content'));
if ($content) {
$item['image_url'] = Base::fillUrl($content['url']);
$item['image_width'] = intval($content['width']);
$item['image_height'] = intval($content['height']);
}
}
return $item;
}
/**
* 获取文件并检测权限
* @param $id
* @param null $noExistTis
* @param int $limit 要求权限: 0-访问权限、1-读写权限、1000-所有者或创建者
* @param $permission
* @return File
*/
public static function allowFind($id, $noExistTis = null)
public static function permissionFind($id, $limit = 0, &$permission = -1)
{
$file = File::find($id);
if (empty($file)) {
throw new ApiException($noExistTis ?: '文件不存在或已被删除');
throw new ApiException('文件不存在或已被删除');
}
//
$permission = $file->getPermission(User::userid());
if ($permission < $limit) {
$msg = match ($limit) {
1000 => '仅限所有者或创建者操作',
1 => '没有修改写入权限',
default => '没有查看访问权限',
};
throw new ApiException($msg);
}
$file->exceAllow(User::userid());
return $file;
}
/**
* 格式化内容数据
* @param array $data [path, size, ext, name]
* @return array
*/
public static function formatFileData(array $data)
{
$fileName = $data['name'];
$filePath = $data['path'];
$fileSize = $data['size'];
$fileExt = $data['ext'];
$publicPath = public_path($filePath);
//
switch ($fileExt) {
case 'md':
case 'text':
// 文本
$data['content'] = [
'type' => $fileExt,
'content' => file_get_contents($publicPath) ?: 'Content deleted',
];
$data['file_mode'] = $fileExt;
break;
case 'drawio':
// 图表
$data['content'] = [
'xml' => file_get_contents($publicPath)
];
$data['file_mode'] = $fileExt;
break;
case 'mind':
// 思维导图
$data['content'] = Base::json2array(file_get_contents($publicPath));
$data['file_mode'] = $fileExt;
break;
default:
if (in_array($fileExt, self::codeExt) && $fileSize < 2 * 1024 * 1024)
{
// 文本预览限制2M内的文件
$data['content'] = [
'content' => file_get_contents($publicPath) ?: 'Content deleted',
];
$data['file_mode'] = 'code';
}
elseif (in_array($fileExt, File::officeExt))
{
// office预览
$data['content'] = json_decode('{}');
$data['file_mode'] = 'office';
}
else
{
// 其他预览
$name = Base::rightDelete($fileName, ".{$fileExt}") . ".{$fileExt}";
$data['content'] = [
'preview' => true,
'name' => $name,
'key' => urlencode(Base::urlAddparameter($filePath, [
'name' => $name,
'ext' => $fileExt
])),
];
$data['file_mode'] = 'preview';
}
break;
}
return $data;
}
/**
* 移交文件
* @param $originalUserid
* @param $newUserid
* @return void
*/
public static function transfer($originalUserid, $newUserid)
{
if (!self::whereUserid($originalUserid)->exists()) {
return;
}
// 创建一个文件夹存放移交的文件
$name = User::userid2nickname($originalUserid) ?: ('ID:' . $originalUserid);
$file = File::createInstance([
'pid' => 0,
'name' => "{$name}】移交的文件",
'type' => "folder",
'ext' => "",
'userid' => $newUserid,
'created_id' => 0,
]);
$file->handleDuplicateName();
$file->saveBeforePP();
// 移交文件
self::whereUserid($originalUserid)->chunkById(100, function($list) use ($file, $newUserid) {
/** @var self $item */
foreach ($list as $item) {
if ($item->pid === 0) {
$item->pid = $file->id;
}
$item->userid = $newUserid;
$item->saveBeforePP();
}
});
// 移交文件权限
FileUser::whereUserid($originalUserid)->chunkById(100, function ($list) use ($newUserid) {
/** @var FileUser $item */
foreach ($list as $item) {
$row = FileUser::whereFileId($item->file_id)->whereUserid($newUserid)->first();
if ($row) {
// 已存在则删除原数据,判断改变已存在的数据
$row->permission = max($row->permission, $item->permission);
$row->save();
$item->delete();
} else {
// 不存在则改变原数据
$item->userid = $newUserid;
$item->save();
}
}
});
}
}

View File

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

View File

@@ -6,9 +6,10 @@ namespace App\Models;
* App\Models\FileLink
*
* @property int $id
* @property int|null $file_id 项目ID
* @property int|null $file_id 文件ID
* @property int|null $num 累计访问
* @property string|null $code 链接码
* @property int|null $userid 会员ID
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\File|null $file
@@ -21,6 +22,7 @@ namespace App\Models;
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereNum($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereUserid($value)
* @mixin \Eloquent
*/
class FileLink extends AbstractModel

View File

@@ -25,4 +25,34 @@ namespace App\Models;
*/
class FileUser extends AbstractModel
{
/**
* 删除所有共享成员(同时删除成员分享的链接)
* @param $file_id
* @param int $retain_link_userid 保留指定会员的链接
* @return mixed
*/
public static function deleteFileAll($file_id, $retain_link_userid = 0)
{
return AbstractModel::transaction(function() use ($retain_link_userid, $file_id) {
if ($retain_link_userid > 0) {
FileLink::whereFileId($file_id)->where('userid', '!=', $retain_link_userid)->delete();
} else {
FileLink::whereFileId($file_id)->delete();
}
FileUser::whereFileId($file_id)->delete();
});
}
/**
* 删除指定共享成员(同时删除成员分享的链接)
* @param $file_id
* @param $userid
* @return mixed
*/
public static function deleteFileUser($file_id, $userid)
{
return AbstractModel::transaction(function() use ($userid, $file_id) {
FileLink::whereFileId($file_id)->whereUserid($userid)->delete();
return self::whereFileId($file_id)->whereUserid($userid)->delete();
});
}
}

34
app/Models/Meeting.php Normal file
View File

@@ -0,0 +1,34 @@
<?php
namespace App\Models;
/**
* App\Models\Meeting
*
* @property int $id
* @property string|null $meetingid 会议ID不是数字
* @property string|null $name 会议主题
* @property string|null $channel 频道
* @property int|null $userid 创建人
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $end_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder|Meeting newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Meeting newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Meeting query()
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereChannel($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereEndAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereMeetingid($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereUserid($value)
* @mixin \Eloquent
*/
class Meeting extends AbstractModel
{
}

View File

@@ -3,40 +3,39 @@
namespace App\Models;
use App\Exceptions\ApiException;
use App\Module\Base;
use App\Tasks\PushTask;
use Arr;
use Carbon\Carbon;
use DB;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
use Request;
/**
* Class Project
* App\Models\Project
*
* @package App\Models
* @property int $id
* @property string|null $name 名称
* @property string|null $desc 描述、备注
* @property int|null $userid 创建人
* @property int|mixed $dialog_id 聊天会话ID
* @property int|null $personal 是否个人项目
* @property string|null $user_simple 成员总数|1,2,3
* @property int|null $dialog_id 聊天会话ID
* @property string|null $archived_at 归档时间
* @property int|null $archived_userid 归档会员
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read int $owner_userid
* @property-read int $task_complete
* @property-read int $task_my_complete
* @property-read int $task_my_num
* @property-read int $task_my_percent
* @property-read int $task_num
* @property-read int $task_percent
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectColumn[] $projectColumn
* @property-read int|null $project_column_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectLog[] $projectLog
* @property-read int|null $project_log_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectUser[] $projectUser
* @property-read int|null $project_user_count
* @method static \Illuminate\Database\Eloquent\Builder|Project authData($userid = null)
* @method static \Illuminate\Database\Eloquent\Builder|Project allData($userid = null)
* @method static \Illuminate\Database\Eloquent\Builder|Project authData($userid = null, $owner = null)
* @method static \Illuminate\Database\Eloquent\Builder|Project newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Project newQuery()
* @method static \Illuminate\Database\Query\Builder|Project onlyTrashed()
@@ -49,7 +48,9 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|Project whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project wherePersonal($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUserSimple($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|Project withTrashed()
* @method static \Illuminate\Database\Query\Builder|Project withoutTrashed()
@@ -59,99 +60,14 @@ class Project extends AbstractModel
{
use SoftDeletes;
const projectSelect = [
'projects.*',
'project_users.owner',
protected $hidden = [
'deleted_at',
];
protected $appends = [
'task_num',
'task_complete',
'task_percent',
'task_my_num',
'task_my_complete',
'task_my_percent',
'owner_userid',
];
/**
* 生成任务数据
*/
private function generateTaskData()
{
if (!isset($this->appendattrs['task_num'])) {
$builder = ProjectTask::whereProjectId($this->id)->whereParentId(0)->whereNull('archived_at');
$this->appendattrs['task_num'] = $builder->count();
$this->appendattrs['task_complete'] = $builder->whereNotNull('complete_at')->count();
$this->appendattrs['task_percent'] = $this->appendattrs['task_num'] ? intval($this->appendattrs['task_complete'] / $this->appendattrs['task_num'] * 100) : 0;
//
$builder = ProjectTask::whereProjectId($this->id)->whereParentId(0)->authData(User::userid(), true)->whereNull('archived_at');
$this->appendattrs['task_my_num'] = $builder->count();
$this->appendattrs['task_my_complete'] = $builder->whereNotNull('complete_at')->count();
$this->appendattrs['task_my_percent'] = $this->appendattrs['task_my_num'] ? intval($this->appendattrs['task_my_complete'] / $this->appendattrs['task_my_num'] * 100) : 0;
}
}
/**
* 任务数量
* @return int
*/
public function getTaskNumAttribute()
{
$this->generateTaskData();
return $this->appendattrs['task_num'];
}
/**
* 任务完成数量
* @return int
*/
public function getTaskCompleteAttribute()
{
$this->generateTaskData();
return $this->appendattrs['task_complete'];
}
/**
* 任务完成率
* @return int
*/
public function getTaskPercentAttribute()
{
$this->generateTaskData();
return $this->appendattrs['task_percent'];
}
/**
* 任务数量(我的)
* @return int
*/
public function getTaskMyNumAttribute()
{
$this->generateTaskData();
return $this->appendattrs['task_my_num'];
}
/**
* 任务完成数量(我的)
* @return int
*/
public function getTaskMyCompleteAttribute()
{
$this->generateTaskData();
return $this->appendattrs['task_my_complete'];
}
/**
* 任务完成率(我的)
* @return int
*/
public function getTaskMyPercentAttribute()
{
$this->generateTaskData();
return $this->appendattrs['task_my_percent'];
}
/**
* 负责人会员ID
* @return int
@@ -159,7 +75,7 @@ class Project extends AbstractModel
public function getOwnerUseridAttribute()
{
if (!isset($this->appendattrs['owner_userid'])) {
$ownerUser = $this->projectUser->where('owner', 1)->first();
$ownerUser = ProjectUser::whereProjectId($this->id)->whereOwner(1)->first();
$this->appendattrs['owner_userid'] = $ownerUser ? $ownerUser->userid : 0;
}
return $this->appendattrs['owner_userid'];
@@ -190,19 +106,73 @@ class Project extends AbstractModel
}
/**
* 查询自己的项目
* 查询所有项目与正常查询多返回owner字段
* @param self $query
* @param null $userid
* @return self
*/
public function scopeAuthData($query, $userid = null)
public function scopeAllData($query, $userid = null)
{
$userid = $userid ?: User::userid();
$query->join('project_users', 'projects.id', '=', 'project_users.project_id')
->where('project_users.userid', $userid);
$query
->select([
'projects.*',
'project_users.owner',
'project_users.top_at',
])
->leftJoin('project_users', function ($leftJoin) use ($userid) {
$leftJoin
->on('project_users.userid', '=', DB::raw($userid))
->on('projects.id', '=', 'project_users.project_id');
});
return $query;
}
/**
* 查询自己负责或参与的项目
* @param self $query
* @param null $userid
* @param null $owner
* @return self
*/
public function scopeAuthData($query, $userid = null, $owner = null)
{
$userid = $userid ?: User::userid();
$query
->select([
'projects.*',
'project_users.owner',
'project_users.top_at',
])
->join('project_users', 'projects.id', '=', 'project_users.project_id')
->where('project_users.userid', $userid);
if ($owner !== null) {
$query->where('project_users.owner', $owner);
}
return $query;
}
/**
* 获取任务统计数据
* @param $userid
* @return array
*/
public function getTaskStatistics($userid)
{
$array = [];
$builder = ProjectTask::whereProjectId($this->id)->whereNull('archived_at');
$array['task_num'] = $builder->count();
$array['task_complete'] = $builder->whereNotNull('complete_at')->count();
$array['task_percent'] = $array['task_num'] ? intval($array['task_complete'] / $array['task_num'] * 100) : 0;
//
$builder = ProjectTask::authData($userid, 1)->where('project_tasks.project_id', $this->id)->whereNull('project_tasks.archived_at');
$array['task_my_num'] = $builder->count();
$array['task_my_complete'] = $builder->whereNotNull('project_tasks.complete_at')->count();
$array['task_my_percent'] = $array['task_my_num'] ? intval($array['task_my_complete'] / $array['task_my_num'] * 100) : 0;
//
return $array;
}
/**
* 加入项目
* @param int $userid 加入的会员ID
@@ -237,9 +207,11 @@ class Project extends AbstractModel
WebSocketDialogUser::updateInsert([
'dialog_id' => $this->dialog_id,
'userid' => $userid,
], [
'important' => 1
]);
}
WebSocketDialogUser::whereDialogId($this->dialog_id)->whereNotIn('userid', $userids)->delete();
WebSocketDialogUser::whereDialogId($this->dialog_id)->whereNotIn('userid', $userids)->whereImportant(1)->delete();
});
}
@@ -249,7 +221,7 @@ class Project extends AbstractModel
*/
public function relationUserids()
{
return $this->projectUser->pluck('userid')->toArray();
return ProjectUser::whereProjectId($this->id)->orderBy('id')->pluck('userid')->toArray();
}
/**
@@ -277,6 +249,7 @@ class Project extends AbstractModel
if ($archived_at === null) {
// 取消归档
$this->archived_at = null;
$this->archived_userid = User::userid();
$this->addLog("项目取消归档");
$this->pushMsg('add', $this);
ProjectTask::whereProjectId($this->id)->whereArchivedFollow(1)->update([
@@ -322,18 +295,23 @@ class Project extends AbstractModel
/**
* 添加项目日志
* @param string $detail
* @param array $record
* @param int $userid
* @return ProjectLog
*/
public function addLog($detail, $userid = 0)
public function addLog($detail, $record = [], $userid = 0)
{
$log = ProjectLog::createInstance([
$array = [
'project_id' => $this->id,
'column_id' => 0,
'task_id' => 0,
'userid' => $userid ?: User::userid(),
'detail' => $detail,
]);
];
if ($record) {
$array['record'] = $record;
}
$log = ProjectLog::createInstance($array);
$log->save();
return $log;
}
@@ -341,45 +319,267 @@ class Project extends AbstractModel
/**
* 推送消息
* @param string $action
* @param array $data 发送内容,默认为[id=>项目ID]
* @param array $userid 指定会员,默认为项目所有成员
* @param array|self $data 发送内容,默认为[id=>项目ID]
* @param array $userid 指定会员,默认为项目所有成员
*/
public function pushMsg($action, $data = null, $userid = null)
{
if ($data === null) {
$data = ['id' => $this->id];
} elseif ($data instanceof self) {
$data = $data->toArray();
}
//
$array = [$userid, []];
if ($userid === null) {
$userid = $this->relationUserids();
$array[0] = $this->relationUserids();
} elseif (!is_array($userid)) {
$array[0] = [$userid];
}
//
if (isset($data['owner'])) {
$owners = ProjectUser::whereProjectId($data['id'])->whereOwner(1)->pluck('userid')->toArray();
$array = [array_intersect($array[0], $owners), array_diff($array[0], $owners)];
}
//
foreach ($array as $index => $item) {
if ($index > 0) {
$data['owner'] = 0;
}
$params = [
'ignoreFd' => Request::header('fd'),
'userid' => array_values($item),
'msg' => [
'type' => 'project',
'action' => $action,
'data' => $data,
]
];
$task = new PushTask($params, false);
Task::deliver($task);
}
$params = [
'ignoreFd' => Request::header('fd'),
'userid' => $userid,
'msg' => [
'type' => 'project',
'action' => $action,
'data' => $data,
]
];
$task = new PushTask($params, false);
Task::deliver($task);
}
/**
* 根据用户获取项目信息(用于判断会员是否存在项目内)
* 添加工作流
* @param $flows
* @return mixed
*/
public function addFlow($flows)
{
return AbstractModel::transaction(function() use ($flows) {
$projectFlow = ProjectFlow::whereProjectId($this->id)->first();
if (empty($projectFlow)) {
$projectFlow = ProjectFlow::createInstance([
'project_id' => $this->id,
'name' => 'Default'
]);
if (!$projectFlow->save()) {
throw new ApiException('工作流创建失败');
}
}
//
$ids = [];
$idc = [];
$hasStart = false;
$hasEnd = false;
$upTaskList = [];
foreach ($flows as $item) {
$id = intval($item['id']);
$turns = Base::arrayRetainInt($item['turns'] ?: [], true);
$userids = Base::arrayRetainInt($item['userids'] ?: [], true);
$usertype = trim($item['usertype']);
$userlimit = intval($item['userlimit']);
if ($usertype == 'replace' && empty($userids)) {
throw new ApiException("状态[{$item['name']}]设置错误,设置流转模式时必须填写状态负责人");
}
if ($usertype == 'merge' && empty($userids)) {
throw new ApiException("状态[{$item['name']}]设置错误,设置剔除模式时必须填写状态负责人");
}
if ($userlimit && empty($userids)) {
throw new ApiException("状态[{$item['name']}]设置错误,设置限制负责人时必须填写状态负责人");
}
$flow = ProjectFlowItem::updateInsert([
'id' => $id,
'project_id' => $this->id,
'flow_id' => $projectFlow->id,
], [
'name' => trim($item['name']),
'status' => trim($item['status']),
'sort' => intval($item['sort']),
'turns' => $turns,
'userids' => $userids,
'usertype' => trim($item['usertype']),
'userlimit' => $userlimit,
], [], $isInsert);
if ($flow) {
$ids[] = $flow->id;
if ($flow->id != $id) {
$idc[$id] = $flow->id;
}
if ($flow->status == 'start') {
$hasStart = true;
}
if ($flow->status == 'end') {
$hasEnd = true;
}
if (!$isInsert) {
$upTaskList[$flow->id] = $flow->status . "|" . $flow->name;
}
}
}
if (!$hasStart) {
throw new ApiException('至少需要1个开始状态');
}
if (!$hasEnd) {
throw new ApiException('至少需要1个结束状态');
}
ProjectFlowItem::whereFlowId($projectFlow->id)->whereNotIn('id', $ids)->chunk(100, function($list) {
foreach ($list as $item) {
$item->deleteFlowItem();
}
});
//
foreach ($upTaskList as $id => $value) {
ProjectTask::whereFlowItemId($id)->update([
'flow_item_name' => $value
]);
}
//
$projectFlow = ProjectFlow::with(['projectFlowItem'])->whereProjectId($this->id)->find($projectFlow->id);
$itemIds = $projectFlow->projectFlowItem->pluck('id')->toArray();
foreach ($projectFlow->projectFlowItem as $item) {
$turns = $item->turns;
foreach ($idc as $oid => $nid) {
if (in_array($oid, $turns)) {
$turns = array_diff($turns, [$oid]);
$turns[] = $nid;
}
}
if (!in_array($item->id, $turns)) {
$turns[] = $item->id;
}
$turns = array_values(array_filter(array_unique(array_intersect($turns, $itemIds))));
sort($turns);
$item->turns = $turns;
ProjectFlowItem::whereId($item->id)->update([ 'turns' => Base::array2json($turns) ]);
}
return $projectFlow;
});
}
/**
* 创建项目
* @param $params
* - name 项目名称
* - desc
* - flow
* - personal
* - columns
* @return array
*/
public static function createProject($params, $userid)
{
$name = trim(Arr::get($params, 'name', ''));
$desc = trim(Arr::get($params, 'desc', ''));
$flow = trim(Arr::get($params, 'flow', 'close'));
$isPersonal = intval(Arr::get($params, 'personal'));
if (mb_strlen($name) < 2) {
return Base::retError('项目名称不可以少于2个字');
} elseif (mb_strlen($name) > 32) {
return Base::retError('项目名称最多只能设置32个字');
}
if (mb_strlen($desc) > 255) {
return Base::retError('项目介绍最多只能设置255个字');
}
// 列表
$columns = explode(",", Arr::get($params, 'columns'));
$insertColumns = [];
$sort = 0;
foreach ($columns AS $column) {
$column = trim($column);
if ($column) {
$insertColumns[] = [
'name' => $column,
'sort' => $sort++,
];
}
}
if (empty($insertColumns)) {
$insertColumns[] = [
'name' => 'Default',
'sort' => 0,
];
}
if (count($insertColumns) > 30) {
return Base::retError('项目列表最多不能超过30个');
}
// 开始创建
$project = Project::createInstance([
'name' => $name,
'desc' => $desc,
'userid' => $userid,
]);
if ($isPersonal) {
if (Project::whereUserid($userid)->wherePersonal(1)->exists()) {
return Base::retError('个人项目已存在,无须重复创建');
}
$project->personal = 1;
}
AbstractModel::transaction(function() use ($flow, $insertColumns, $project) {
$project->save();
ProjectUser::createInstance([
'project_id' => $project->id,
'userid' => $project->userid,
'owner' => 1,
])->save();
foreach ($insertColumns AS $column) {
$column['project_id'] = $project->id;
ProjectColumn::createInstance($column)->save();
}
$dialog = WebSocketDialog::createGroup($project->name, $project->userid, 'project');
if (empty($dialog)) {
throw new ApiException('创建项目聊天室失败');
}
$project->dialog_id = $dialog->id;
$project->save();
//
if ($flow == 'open') {
$project->addFlow(Base::json2array('[{"id":-10,"name":"待处理","status":"start","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-11,"name":"进行中","status":"progress","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-12,"name":"待测试","status":"test","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-13,"name":"已完成","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-14,"name":"已取消","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0}]'));
}
});
//
$data = Project::find($project->id);
$data->addLog("创建项目");
$data->pushMsg('add', $data);
return Base::retSuccess('添加成功', $data);
}
/**
* 获取项目信息(用于判断会员是否存在项目内)
* @param int $project_id
* @param bool $ignoreArchived 排除已归档
* @param null|bool $archived true:仅限未归档, false:仅限已归档, null:不限制
* @param null|bool $mustOwner true:仅限项目负责人, false:仅限非项目负责人, null:不限制
* @return self
*/
public static function userProject($project_id, $ignoreArchived = true)
public static function userProject($project_id, $archived = true, $mustOwner = null)
{
$project = self::select(self::projectSelect)->authData()->where('projects.id', intval($project_id))->first();
$project = self::authData()->where('projects.id', intval($project_id))->first();
if (empty($project)) {
throw new ApiException('项目不存在或不在成员列表内', [ 'project_id' => $project_id ], -4001);
}
if ($ignoreArchived && $project->archived_at != null) {
if ($archived === true && $project->archived_at != null) {
throw new ApiException('项目已归档', [ 'project_id' => $project_id ], -4001);
}
if ($archived === false && $project->archived_at == null) {
throw new ApiException('项目未归档', [ 'project_id' => $project_id ]);
}
if ($mustOwner === true && !$project->owner) {
throw new ApiException('仅限项目负责人操作', [ 'project_id' => $project_id ]);
}
if ($mustOwner === false && $project->owner) {
throw new ApiException('禁止项目负责人操作', [ 'project_id' => $project_id ]);
}
return $project;
}
}

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,11 @@
namespace App\Models;
use App\Module\Base;
/**
* Class ProjectTaskContent
* App\Models\ProjectTaskContent
*
* @package App\Models
* @property int $id
* @property int|null $project_id 项目ID
* @property int|null $task_id 任务ID
@@ -29,4 +30,55 @@ class ProjectTaskContent extends AbstractModel
'created_at',
'updated_at',
];
/**
* 获取内容详情
* @return array
*/
public function getContentInfo()
{
$content = Base::json2array($this->content);
if (isset($content['url'])) {
$filePath = public_path($content['url']);
$array = $this->toArray();
$array['content'] = file_get_contents($filePath) ?: '';
if ($array['content']) {
$replace = Base::fillUrl('uploads/task');
$array['content'] = str_replace('{{RemoteURL}}uploads/task', $replace, $array['content']);
}
return $array;
}
return $this->toArray();
}
/**
* 保存任务详情至文件并返回文件路径
* @param $task_id
* @param $content
* @return string
*/
public static function saveContent($task_id, $content)
{
$path = 'uploads/task/content/' . date("Ym") . '/' . $task_id . '/';
//
preg_match_all("/<img\s+src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"/s", $content, $matchs);
foreach ($matchs[2] as $key => $text) {
$tmpPath = $path . 'attached/';
Base::makeDir(public_path($tmpPath));
$tmpPath .= md5($text) . "." . $matchs[1][$key];
if (file_put_contents(public_path($tmpPath), base64_decode($text))) {
$paramet = getimagesize(public_path($tmpPath));
$content = str_replace($matchs[0][$key], '<img src="{{RemoteURL}}' . $tmpPath . '" original-width="' . $paramet[0] . '" original-height="' . $paramet[1] . '"', $content);
}
}
$pattern = '/<img(.*?)src=("|\')https*:\/\/(.*?)\/(uploads\/task\/content\/(.*?))\2/is';
$content = preg_replace($pattern, '<img$1src=$2{{RemoteURL}}$4$2', $content);
//
$filePath = $path . md5($content);
$publicPath = public_path($filePath);
Base::makeDir(dirname($publicPath));
file_put_contents($publicPath, $content);
//
return $filePath;
}
}

View File

@@ -3,23 +3,25 @@
namespace App\Models;
use App\Module\Base;
use Cache;
/**
* Class ProjectTaskFile
* App\Models\ProjectTaskFile
*
* @package App\Models
* @property int $id
* @property int|null $project_id 项目ID
* @property int|null $task_id 任务ID
* @property string|null $name 文件名称
* @property int|null $size 文件大小(B)
* @property string|null $ext 文件格式
* @property string|null $path 文件地址
* @property string|null $thumb 缩略图
* @property string $path 文件地址
* @property string $thumb 缩略图
* @property int|null $userid 上传用户ID
* @property int|null $download 下载次数
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read int $height
* @property-read int $width
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFile newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFile newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFile query()
@@ -39,6 +41,11 @@ use App\Module\Base;
*/
class ProjectTaskFile extends AbstractModel
{
protected $appends = [
'width',
'height',
];
/**
* 地址
* @param $value
@@ -58,4 +65,50 @@ class ProjectTaskFile extends AbstractModel
{
return Base::fillUrl($value ?: Base::extIcon($this->ext));
}
/**
* 宽
* @return int
*/
public function getWidthAttribute()
{
$this->generateSizeData();
return $this->appendattrs['width'];
}
/**
* 高
* @return int
*/
public function getHeightAttribute()
{
$this->generateSizeData();
return $this->appendattrs['height'];
}
/**
* 生成尺寸数据
*/
private function generateSizeData()
{
if (!isset($this->appendattrs['width'])) {
$width = -1;
$height = -1;
if (in_array($this->ext, ['jpg', 'jpeg', 'gif', 'png'])) {
$path = public_path($this->getRawOriginal('path'));
[$width, $height] = Cache::remember("File::size-" . md5($path), now()->addDays(7), function () use ($path) {
$width = -1;
$height = -1;
if (file_exists($path)) {
$paramet = getimagesize($path);
$width = $paramet[0];
$height = $paramet[1];
}
return [$width, $height];
});
}
$this->appendattrs['width'] = $width;
$this->appendattrs['height'] = $height;
}
}
}

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

@@ -0,0 +1,45 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* App\Models\ProjectTaskMailLog
*
* @property int $id
* @property int|null $userid 用户id
* @property int|null $task_id 任务id
* @property string|null $email 电子邮箱
* @property int|null $type 提醒类型0 任务开始提醒1 距离到期提醒2到期超时提醒
* @property int|null $is_send 邮件发送是否成功0否1是
* @property string|null $send_error 邮件发送错误详情
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog newQuery()
* @method static \Illuminate\Database\Query\Builder|ProjectTaskMailLog onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereIsSend($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereSendError($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereTaskId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|ProjectTaskMailLog withTrashed()
* @method static \Illuminate\Database\Query\Builder|ProjectTaskMailLog withoutTrashed()
* @mixin \Eloquent
*/
class ProjectTaskMailLog extends AbstractModel
{
use SoftDeletes;
}

View File

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

View File

@@ -3,9 +3,8 @@
namespace App\Models;
/**
* Class ProjectTaskUser
* App\Models\ProjectTaskUser
*
* @package App\Models
* @property int $id
* @property int|null $project_id 项目ID
* @property int|null $task_id 任务ID
@@ -14,6 +13,7 @@ namespace App\Models;
* @property int|null $owner 是否任务负责人
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\ProjectTask|null $projectTask
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskUser newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskUser newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskUser query()
@@ -30,4 +30,45 @@ namespace App\Models;
class ProjectTaskUser extends AbstractModel
{
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function projectTask(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(ProjectTask::class, 'id', 'task_id');
}
/**
* 移交任务身份
* @param $originalUserid
* @param $newUserid
* @return void
*/
public static function transfer($originalUserid, $newUserid)
{
self::whereUserid($originalUserid)->chunk(100, function ($list) use ($originalUserid, $newUserid) {
$tastIds = [];
/** @var self $item */
foreach ($list as $item) {
$row = self::whereTaskId($item->task_id)->whereUserid($newUserid)->first();
if ($row) {
// 已存在则删除原数据,判断改变已存在的数据
$row->owner = max($row->owner, $item->owner);
$row->save();
$item->delete();
} else {
// 不存在则改变原数据
$item->userid = $newUserid;
$item->save();
}
if ($item->projectTask) {
$item->projectTask->addLog("移交{任务}身份", ['userid' => [$originalUserid, ' => ',$newUserid]]);
if (!in_array($item->task_pid, $tastIds)) {
$tastIds[] = $item->task_pid;
$item->projectTask->syncDialogUser();
}
}
}
});
}
}

View File

@@ -5,13 +5,13 @@ namespace App\Models;
use App\Module\Base;
/**
* Class ProjectUser
* App\Models\ProjectUser
*
* @package App\Models
* @property int $id
* @property int|null $project_id 项目ID
* @property int|null $userid 成员ID
* @property int|null $owner 是否负责人
* @property string|null $top_at 置顶时间
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Project|null $project
@@ -22,6 +22,7 @@ use App\Module\Base;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereOwner($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereProjectId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereTopAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereUserid($value)
* @mixin \Eloquent
@@ -37,17 +38,59 @@ class ProjectUser extends AbstractModel
return $this->hasOne(Project::class, 'id', 'project_id');
}
/**
* 移交项目身份
* @param $originalUserid
* @param $newUserid
* @return void
*/
public static function transfer($originalUserid, $newUserid)
{
self::whereUserid($originalUserid)->chunkById(100, function ($list) use ($originalUserid, $newUserid) {
/** @var self $item */
foreach ($list as $item) {
$row = self::whereProjectId($item->project_id)->whereUserid($newUserid)->first();
if ($row) {
// 已存在则删除原数据,判断改变已存在的数据
$row->owner = max($row->owner, $item->owner);
$row->save();
$item->delete();
} else {
// 不存在则改变原数据
$item->userid = $newUserid;
$item->save();
}
if ($item->project) {
if ($item->project->personal) {
$name = User::userid2nickname($originalUserid) ?: ('ID:' . $originalUserid);
$item->project->name = "{$name}{$item->project->name}";
$item->project->save();
}
$item->project->addLog("移交项目身份", ['userid' => [$originalUserid, ' => ', $newUserid]]);
$item->project->syncDialogUser();
}
}
});
}
/**
* 退出项目
*/
public function exitProject()
{
$tasks = ProjectTask::whereProjectId($this->project_id)->authData($this->userid)->get();
foreach ($tasks as $task) {
if (ProjectTaskUser::whereTaskId($task->id)->whereUserid($this->userid)->delete()) {
$task->syncDialogUser();
}
}
ProjectTaskUser::whereProjectId($this->project_id)
->whereUserid($this->userid)
->chunk(100, function ($list) {
$tastIds = [];
/** @var ProjectTaskUser $item */
foreach ($list as $item) {
$item->delete();
if (!in_array($item->task_pid, $tastIds)) {
$tastIds[] = $item->task_pid;
$item->projectTask?->syncDialogUser();
}
}
});
$this->delete();
}
}

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

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

View File

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

View File

@@ -2,10 +2,11 @@
namespace App\Models;
use App\Module\Base;
/**
* Class Setting
* App\Models\Setting
*
* @package App\Models
* @property int $id
* @property string|null $name
* @property string|null $desc 参数描述、备注
@@ -25,5 +26,40 @@ namespace App\Models;
*/
class Setting extends AbstractModel
{
/**
* 验证邮箱地址(过滤忽略地址)
* @param $array
* @param \Closure $resultClosure
* @param \Closure|null $emptyClosure
* @return array|mixed
*/
public static function validateAddr($array, $resultClosure, $emptyClosure = null)
{
if (!is_array($array)) {
$array = [$array];
}
$ignoreAddr = Base::settingFind('emailSetting', 'ignore_addr');
$ignoreAddr = explode("\n", $ignoreAddr);
$ignoreArray = ['admin@dootask.com', 'test@dootask.com'];
foreach ($ignoreAddr as $item) {
if (Base::isEmail($item)) {
$ignoreArray[] = trim($item);
}
}
if ($ignoreArray) {
$array = array_diff($array, $ignoreArray);
}
if ($array) {
if ($resultClosure instanceof \Closure) {
foreach ($array as $value) {
$resultClosure($value);
}
}
} else {
if ($emptyClosure instanceof \Closure) {
$emptyClosure();
}
}
return $array;
}
}

View File

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

169
app/Models/UmengAlias.php Normal file
View File

@@ -0,0 +1,169 @@
<?php
namespace App\Models;
use App\Module\Base;
use Carbon\Carbon;
use Hedeqiang\UMeng\Android;
use Hedeqiang\UMeng\IOS;
/**
* App\Models\UmengAlias
*
* @property int $id
* @property int|null $userid 会员ID
* @property string|null $alias 别名
* @property string|null $platform 平台类型
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias query()
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereAlias($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias wherePlatform($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereUserid($value)
* @mixin \Eloquent
*/
class UmengAlias extends AbstractModel
{
protected $table = 'umeng_alias';
/**
* 获取推送配置
* @return array|false
*/
public static function getPushConfig()
{
$setting = Base::setting('appPushSetting');
if ($setting['push'] !== 'open') {
return false;
}
$config = [];
if ($setting['ios_key']) {
$config['iOS'] = [
'appKey' => $setting['ios_key'],
'appMasterSecret' => $setting['ios_secret'],
'production_mode' => true,
];
}
if ($setting['android_key']) {
$config['Android'] = [
'appKey' => $setting['android_key'],
'appMasterSecret' => $setting['android_secret'],
'production_mode' => true,
];
}
return $config;
}
/**
* 推送消息
* @param string $alias
* @param string $platform
* @param array $array [title, subtitle, body, description, extra, seconds, badge]
* @return array|false
*/
public static function pushMsgToAlias($alias, $platform, $array)
{
$config = self::getPushConfig();
if ($config === false) {
return false;
}
//
$title = $array['title'] ?: ''; // 标题
$subtitle = $array['subtitle'] ?: ''; // 副标题iOS
$body = $array['body'] ?: ''; // 通知内容
$description = $array['description'] ?: 'no description'; // 描述
$extra = is_array($array['extra']) ? $array['extra'] : []; // 额外参数
$seconds = intval($array['seconds']) ?: 86400; // 有效时间(单位:秒)
$badge = intval($array['badge']) ?: 0; // 角标数iOS
//
switch ($platform) {
case 'ios':
if (!isset($config['iOS'])) {
return false;
}
$ios = new IOS($config);
return $ios->send([
'description' => $description,
'payload' => array_merge([
'aps' => [
'alert' => [
'title' => $title,
'subtitle' => $subtitle,
'body' => $body,
],
'sound' => 'default',
'badge' => $badge,
],
], $extra),
'type' => 'customizedcast',
'alias_type' => 'userid',
'alias' => $alias,
'policy' => [
'expire_time' => Carbon::now()->addSeconds($seconds)->toDateTimeString(),
],
]);
case 'android':
if (!isset($config['Android'])) {
return false;
}
$android = new Android($config);
return $android->send([
'description' => $description,
'payload' => array_merge([
'display_type' => 'notification',
'body' => [
'ticker' => $title,
'text' => $body,
'title' => $title,
'after_open' => 'go_app',
'play_sound' => true,
],
], $extra),
'type' => 'customizedcast',
'alias_type' => 'userid',
'alias' => $alias,
'policy' => [
'expire_time' => Carbon::now()->addSeconds($seconds)->toDateTimeString(),
],
'channel_properties' => [
'channel_activity' => 'app.eeui.umeng.activity.MfrMessageActivity',
'huawei_channel_importance' => 'NORMAL'
],
]);
default:
return false;
}
}
/**
* 推送给指定会员
* @param array|int $userid
* @param array $array
* @return void
*/
public static function pushMsgToUserid($userid, $array)
{
$builder = self::select(['id', 'platform', 'alias'])->where('updated_at', '>', Carbon::now()->subMonth());
if (is_array($userid)) {
$builder->whereIn('userid', $userid);
} elseif (Base::isNumber($userid)) {
$builder->whereUserid($userid);
}
$builder
->orderByDesc('id')
->chunkById(100, function ($rows) use ($array) {
$lists = $rows->groupBy('platform');
foreach ($lists as $platform => $list) {
$alias = $list->pluck('alias')->implode(',');
self::pushMsgToAlias($alias, $platform, $array);
}
});
}
}

View File

@@ -9,13 +9,14 @@ use Cache;
use Carbon\Carbon;
/**
* Class User
* App\Models\User
*
* @package App\Models
* @property int $userid
* @property array $identity 身份
* @property string|null $az A-Z
* @property string|null $pinyin 拼音(主要用于搜索)
* @property string|null $email 邮箱
* @property string|null $tel 联系电话
* @property string $nickname 昵称
* @property string|null $profession 职位/职称
* @property string $userimg 头像
@@ -29,8 +30,11 @@ use Carbon\Carbon;
* @property string|null $line_at 最后在线时间(接口)
* @property int|null $task_dialog_id 最后打开的任务会话ID
* @property string|null $created_ip 注册IP
* @property string|null $disable_at 禁用时间(离职时间)
* @property int|null $email_verity 邮箱是否已验证
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Database\Factories\UserFactory factory(...$parameters)
* @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|User newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|User query()
@@ -38,7 +42,9 @@ use Carbon\Carbon;
* @method static \Illuminate\Database\Eloquent\Builder|User whereChangepass($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedIp($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereDisableAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereEmailVerity($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereEncrypt($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereIdentity($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereLastAt($value)
@@ -48,8 +54,10 @@ use Carbon\Carbon;
* @method static \Illuminate\Database\Eloquent\Builder|User whereLoginNum($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereNickname($value)
* @method static \Illuminate\Database\Eloquent\Builder|User wherePassword($value)
* @method static \Illuminate\Database\Eloquent\Builder|User wherePinyin($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereProfession($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereTaskDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereTel($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereUserid($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereUserimg($value)
@@ -63,6 +71,12 @@ class User extends AbstractModel
'updated_at',
];
// 默认头像类型auto自动生成system系统默认
public static $defaultAvatarMode = 'auto';
// 基本信息的字段
public static $basicField = ['userid', 'email', 'nickname', 'profession', 'userimg', 'az', 'pinyin', 'line_at', 'disable_at'];
/**
* 更新数据校验
* @param array $param
@@ -93,11 +107,17 @@ class User extends AbstractModel
*/
public function getUserimgAttribute($value)
{
if ($value) {
if ($value && !str_contains($value, 'avatar/')) {
// 自定义头像
return Base::fillUrl($value);
} else if (self::$defaultAvatarMode === 'auto') {
// 自动生成头像
return url("avatar/" . urlencode($this->nickname) . ".png");
} else {
// 系统默认头像
$name = ($this->userid - 1) % 21 + 1;
return url("images/avatar/default_{$name}.png");
}
$name = ($this->userid - 1) % 21 + 1;
return url("images/avatar/default_{$name}.png");
}
/**
@@ -164,6 +184,34 @@ class User extends AbstractModel
}
}
/**
* 删除会员
* @param $reason
* @return bool|null
*/
public function deleteUser($reason)
{
return AbstractModel::transaction(function () use ($reason) {
// 删除原因
$userDelete = UserDelete::createInstance([
'operator' => User::userid(),
'userid' => $this->userid,
'email' => $this->email,
'reason' => $reason,
'cache' => $this->getRawOriginal()
]);
$userDelete->save();
// 删除未读
WebSocketDialogMsgRead::whereUserid($this->userid)->delete();
// 删除待办
WebSocketDialogMsgTodo::whereUserid($this->userid)->delete();
// 删除邮箱验证记录
UserEmailVerification::whereEmail($this->email)->delete();
//
return $this->delete();
});
}
/** ***************************************************************************************** */
/** ***************************************************************************************** */
/** ***************************************************************************************** */
@@ -178,10 +226,16 @@ class User extends AbstractModel
public static function reg($email, $password, $other = [])
{
//邮箱
if (!Base::isMail($email)) {
if (!Base::isEmail($email)) {
throw new ApiException('请输入正确的邮箱地址');
}
if (User::email2userid($email) > 0) {
$isRegVerify = Base::settingFind('emailSetting', 'reg_verify') === 'open';
$user = self::whereUserid(User::email2userid($email))->first();
if ($isRegVerify && $user->email_verity === 0) {
UserEmailVerification::userEmailSend($user);
throw new ApiException('您的帐号已注册过,请验证邮箱', ['code' => 'email']);
}
throw new ApiException('邮箱地址已存在');
}
//密码
@@ -198,8 +252,15 @@ class User extends AbstractModel
$inArray = array_merge($inArray, $other);
}
$user = User::createInstance($inArray);
$user->save();
User::AZUpdate($user->userid);
$user->az = Base::getFirstCharter($user->nickname);
$user->pinyin = Base::cn2pinyin($user->nickname);
if ($user->save()) {
// 加入全员群组
if (Base::settingFind('system', 'all_group_autoin', 'yes') === 'yes') {
$dialog = WebSocketDialog::whereGroupType('all')->orderByDesc('id')->first();
$dialog?->joinGroup($user->userid, 0);
}
}
return $user->find($user->userid);
}
@@ -399,8 +460,7 @@ class User extends AbstractModel
if (isset($_A["__static_userid2basic_" . $userid])) {
return $_A["__static_userid2basic_" . $userid];
}
$fields = ['userid', 'email', 'nickname', 'profession', 'userimg'];
$userInfo = self::whereUserid($userid)->select($fields)->first();
$userInfo = self::whereUserid($userid)->select(User::$basicField)->first();
if ($userInfo) {
$userInfo->online = $userInfo->getOnlineStatus();
}
@@ -419,19 +479,6 @@ class User extends AbstractModel
return $basic ? $basic->nickname : '';
}
/**
* 更新首字母
* @param $userid
*/
public static function AZUpdate($userid)
{
$row = self::whereUserid($userid)->first();
if ($row) {
$row->az = Base::getFirstCharter($row->nickname);
$row->save();
}
}
/**
* 是否需要验证码
* @param $email

76
app/Models/UserDelete.php Normal file
View File

@@ -0,0 +1,76 @@
<?php
namespace App\Models;
use App\Module\Base;
/**
* App\Models\UserDelete
*
* @property int $id
* @property int|null $operator 操作人员
* @property int|null $userid 用户id
* @property string|null $email 邮箱账号
* @property string|null $reason 注销原因
* @property string|null $cache 会员资料缓存
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete query()
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereCache($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereOperator($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereReason($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereUserid($value)
* @mixin \Eloquent
*/
class UserDelete extends AbstractModel
{
/**
* 昵称
* @param $value
* @return string
*/
public function getCacheAttribute($value)
{
if (!is_array($value)) {
$value = Base::json2array($value);
// 昵称
if (!$value['nickname']) {
$value['nickname'] = Base::cardFormat($value['email']);
}
// 头像
if ($value['userimg'] && !str_contains($value['userimg'], 'avatar/')) {
$value['userimg'] = Base::fillUrl($value['userimg']);
} else if (User::$defaultAvatarMode === 'auto') {
$value['userimg'] = url("avatar/" . urlencode($value['nickname']) . ".png");
} else {
$name = ($value['userid'] - 1) % 21 + 1;
$value['userimg'] = url("images/avatar/default_{$name}.png");
}
}
return $value;
}
/**
* userid 获取 基础信息
* @param int $userid 会员ID
* @return array|null
*/
public static function userid2basic($userid)
{
$row = self::whereUserid($userid)->first();
if (empty($row) || empty($row->cache)) {
return null;
}
$cache = $row->cache;
$cache = array_intersect_key($cache, array_flip(User::$basicField));
$cache['delete_at'] = $row->created_at->format($row->dateFormat ?: 'Y-m-d H:i:s');
return $cache;
}
}

View File

@@ -0,0 +1,133 @@
<?php
namespace App\Models;
use App\Exceptions\ApiException;
use App\Module\Base;
use Carbon\Carbon;
use Guanguans\Notify\Factory;
use Guanguans\Notify\Messages\EmailMessage;
/**
* App\Models\UserEmailVerification
*
* @property int $id
* @property int|null $userid 用户id
* @property string|null $code 验证参数
* @property string|null $email 电子邮箱
* @property int|null $status 0-未验证1-已验证
* @property int|null $type 邮件类型1-邮箱认证2-修改邮箱
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification query()
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereCode($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereStatus($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereUserid($value)
* @mixin \Eloquent
*/
class UserEmailVerification extends AbstractModel
{
/**
* 发验证邮箱
* @param User $user
* @param int $type
* @param null $email
*/
public static function userEmailSend(User $user, $type = 1, $email = null)
{
$email = $type == 1 ? $user->email : $email;
$res = self::whereEmail($email)->where('created_at', '>', Carbon::now()->subMinutes(30))->whereType($type)->first();
if ($res && $type == 1) return;
//删除
self::whereUserid($email)->delete();
$code = $type == 1 ? Base::generatePassword(64) : rand(100000, 999999);
$row = self::createInstance([
'userid' => $user->userid,
'email' => $email,
'code' => $code,
'status' => 0,
'type' => $type
]);
$row->save();
$setting = Base::setting('emailSetting');
try {
if (!Base::isEmail($email)) {
throw new \Exception("User email '{$email}' address error");
}
switch ($type) {
case 2:
$subject = env('APP_NAME') . "修改邮箱验证";
$content = "<p>{$user->nickname} 您好,您正在修改 " . env('APP_NAME') . " 的邮箱验证码如下。请在30分钟内输入验证码</p><p style='color: #0000DD;'><u>$code</u></p><p>如果不是本人操作,您的帐号可能存在风险,请及时修改密码!</p>";
break;
case 3:
$subject = env('APP_NAME') . "注销帐号验证";
$content = "<p>{$user->nickname} 您好,您正在注销 " . env('APP_NAME') . " 的帐号验证码如下。请在30分钟内输入验证码</p><p style='color: #0000DD;'><u>$code</u></p><p>如果不是本人操作,您的帐号可能存在风险,请及时修改密码!</p>";
break;
default:
$url = Base::fillUrl('single/valid/email') . '?code=' . $row->code;
$subject = env('APP_NAME') . "绑定邮箱验证";
$content = "<p>{$user->nickname} 您好,您正在绑定 " . env('APP_NAME') . " 的邮箱请于30分钟之内点击以下链接完成验证 :</p><p style='display: flex; justify-content: center;'><a href='{$url}' target='_blank'>{$url}</a></p>";
break;
}
Factory::mailer()
->setDsn("smtp://{$setting['account']}:{$setting['password']}@{$setting['smtp_server']}:{$setting['port']}?verify_peer=0")
->setMessage(EmailMessage::create()
->from(env('APP_NAME', 'Task') . " <{$setting['account']}>")
->to($email)
->subject($subject)
->html($content))
->send();
} catch (\Throwable $e) {
if (str_contains($e->getMessage(), "Timed Out")) {
throw new ApiException("language.TimedOut");
} elseif ($e->getCode() === 550) {
throw new ApiException('邮件内容被拒绝,请检查邮箱是否开启接收功能');
} else {
throw new ApiException($e->getMessage());
}
}
}
/**
* 校验验证码
* @param $email
* @param $code
* @param int $type
* @return bool
*/
public static function verify($email, $code, $type = 1)
{
if (!$code) {
throw new ApiException('请输入验证码');
}
/** @var UserEmailVerification $emailVerify */
$emailVerify = self::whereEmail($email)->whereType($type)->orderByDesc('id')->first();
if (empty($emailVerify) || $emailVerify->code != $code) {
throw new ApiException('验证码错误');
}
$oldTime = Carbon::parse($emailVerify->created_at)->timestamp;
$time = Base::Time();
// 30分钟失效
if (abs($time - $oldTime) > 1800) {
throw new ApiException('验证码已失效');
}
self::whereEmail($email)->whereCode($code)->whereType($type)->update([
'status' => 1
]);
return true;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Models;
use App\Exceptions\ApiException;
use App\Module\Base;
use Carbon\Carbon;
use Guanguans\Notify\Factory;
use Guanguans\Notify\Messages\EmailMessage;
/**
* App\Models\UserTransfer
*
* @property int $id
* @property int|null $original_userid 原作者
* @property int|null $new_userid 交接人
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer query()
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer whereNewUserid($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer whereOriginalUserid($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer whereUpdatedAt($value)
* @mixin \Eloquent
*/
class UserTransfer extends AbstractModel
{
/**
* 开始移交
* @return void
*/
public function start()
{
// 移交项目身份
ProjectUser::transfer($this->original_userid, $this->new_userid);
// 移交任务身份
ProjectTaskUser::transfer($this->original_userid, $this->new_userid);
// 移交文件
File::transfer($this->original_userid, $this->new_userid);
}
}

View File

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

View File

@@ -3,18 +3,21 @@
namespace App\Models;
use App\Exceptions\ApiException;
use App\Module\Base;
use App\Tasks\PushTask;
use Carbon\Carbon;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* Class WebSocketDialog
* App\Models\WebSocketDialog
*
* @package App\Models
* @property int $id
* @property string|null $type 对话类型
* @property string|null $group_type 聊天室类型
* @property string|null $name 对话名称
* @property string|null $last_at 最后消息时间
* @property int|null $owner_id 群主用户ID
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
@@ -30,6 +33,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereLastAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereOwnerId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|WebSocketDialog withTrashed()
@@ -48,6 +52,175 @@ class WebSocketDialog extends AbstractModel
return $this->hasMany(WebSocketDialogUser::class, 'dialog_id', 'id');
}
/**
* 格式化对话
* @param int $userid 会员ID
* @param bool $hasData
* @return $this
*/
public function formatData($userid, $hasData = false)
{
if (isset($this->search_msg_id)) {
// 最后消息 (搜索预览消息)
$this->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->find($this->search_msg_id);
$this->last_at = $this->last_msg?->created_at;
} else {
// 最后消息
$this->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->orderByDesc('id')->first();
// 未读信息
$unreadBuilder = WebSocketDialogMsgRead::whereDialogId($this->id)->whereUserid($userid)->whereReadAt(null);
$this->unread = $unreadBuilder->count();
$this->mention = 0;
$this->last_umid = 0;
if ($this->unread > 0) {
$this->mention = $unreadBuilder->clone()->whereMention(1)->count();
$this->last_umid = intval($unreadBuilder->clone()->orderByDesc('msg_id')->value('msg_id'));
}
$this->mark_unread = $this->mark_unread ?? WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->value('mark_unread');
// 对话人数
$builder = WebSocketDialogUser::whereDialogId($this->id);
$this->people = $builder->count();
// 有待办
$this->todo_num = WebSocketDialogMsgTodo::whereDialogId($this->id)->whereUserid($userid)->whereDoneAt(null)->count();
}
// 对方信息
$this->dialog_user = null;
$this->group_info = null;
$this->top_at = $this->top_at ?? WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->value('top_at');
switch ($this->type) {
case "user":
$dialog_user = WebSocketDialogUser::whereDialogId($this->id)->where('userid', '!=', $userid)->first();
if ($dialog_user->userid === 0) {
$dialog_user->userid = $userid;
}
$basic = User::userid2basic($dialog_user->userid);
if ($basic) {
$this->name = $basic->nickname;
} else {
$this->name = 'non-existent';
$this->dialog_delete = 1;
}
$this->dialog_user = $dialog_user;
break;
case "group":
switch ($this->group_type) {
case 'project':
$this->group_info = Project::withTrashed()->select(['id', 'name', 'archived_at', 'deleted_at'])->whereDialogId($this->id)->first()?->cancelAppend()->cancelHidden();
if ($this->group_info) {
$this->name = $this->group_info->name;
} else {
$this->name = '[Delete]';
$this->dialog_delete = 1;
}
break;
case 'task':
$this->group_info = ProjectTask::withTrashed()->select(['id', 'name', 'complete_at', 'archived_at', 'deleted_at'])->whereDialogId($this->id)->first()?->cancelAppend()->cancelHidden();
if ($this->group_info) {
$this->name = $this->group_info->name;
} else {
$this->name = '[Delete]';
$this->dialog_delete = 1;
}
break;
case 'all':
$this->name = Base::Lang('全体成员');
$this->all_group_mute = Base::settingFind('system', 'all_group_mute');
break;
}
break;
}
if ($hasData === true) {
$msgBuilder = WebSocketDialogMsg::whereDialogId($this->id);
$this->has_tag = $msgBuilder->clone()->where('tag', '>', 0)->exists();
$this->has_image = $msgBuilder->clone()->whereMtype('image')->exists();
$this->has_file = $msgBuilder->clone()->whereMtype('file')->exists();
$this->has_link = $msgBuilder->clone()->whereLink(1)->exists();
}
$this->pinyin = Base::cn2pinyin($this->name);
return $this;
}
/**
* 加入聊天室
* @param int|array $userid 加入的会员ID或会员ID组
* @param int $inviter 邀请人
* @return bool
*/
public function joinGroup($userid, $inviter)
{
AbstractModel::transaction(function () use ($inviter, $userid) {
foreach (is_array($userid) ? $userid : [$userid] as $value) {
if ($value > 0) {
WebSocketDialogUser::updateInsert([
'dialog_id' => $this->id,
'userid' => $value,
], [
'inviter' => $inviter,
]);
WebSocketDialogMsg::sendMsg(null, $this->id, 'notice', [
'notice' => User::userid2nickname($value) . " 已加入群组"
], $inviter, true, true);
}
}
});
$this->pushMsg("groupUpdate", [
'id' => $this->id,
'people' => WebSocketDialogUser::whereDialogId($this->id)->count()
]);
return true;
}
/**
* 退出聊天室
* @param int|array $userid 退出的会员ID或会员ID组
* @param string $type exit|remove
* @param bool $checkDelete 是否检查删除
*/
public function exitGroup($userid, $type = 'exit', $checkDelete = true)
{
$typeDesc = $type === 'remove' ? '移出' : '退出';
AbstractModel::transaction(function () use ($checkDelete, $typeDesc, $type, $userid) {
$builder = WebSocketDialogUser::whereDialogId($this->id);
if (is_array($userid)) {
$builder->whereIn('userid', $userid);
} else {
$builder->whereUserid($userid);
}
$builder->chunkById(100, function($list) use ($checkDelete, $typeDesc, $type) {
/** @var WebSocketDialogUser $item */
foreach ($list as $item) {
if ($checkDelete) {
if ($type === 'remove' && !in_array(User::userid(), [$this->owner_id, $item->inviter])) {
throw new ApiException('只有群主或邀请人可以移出成员');
}
if ($item->userid == $this->owner_id) {
throw new ApiException('群主不可' . $typeDesc);
}
if ($item->important) {
throw new ApiException('项目人员或任务人员不可' . $typeDesc);
}
}
//
$item->delete();
//
if ($type === 'remove') {
$notice = User::nickname() . "" . User::userid2nickname($item->userid) . " 移出群组";
} else {
$notice = User::userid2nickname($item->userid) . " 退出群组";
}
WebSocketDialogMsg::sendMsg(null, $this->id, 'notice', [
'notice' => $notice
], User::userid(), true, true);
}
});
});
//
$this->pushMsg("groupUpdate", [
'id' => $this->id,
'people' => WebSocketDialogUser::whereDialogId($this->id)->count()
]);
}
/**
* 删除会话
* @return bool
@@ -55,20 +228,126 @@ class WebSocketDialog extends AbstractModel
public function deleteDialog()
{
AbstractModel::transaction(function () {
WebSocketDialogMsgRead::whereDialogId($this->id)->whereNull('read_at')->update([
'read_at' => Carbon::now()
]);
WebSocketDialogMsgRead::whereDialogId($this->id)
->whereNull('read_at')
->chunkById(100, function ($list) {
WebSocketDialogMsgRead::onlyMarkRead($list);
});
$this->delete();
});
return true;
}
/**
* 还原会话
* @return bool
*/
public function recoveryDialog()
{
$this->restore();
return true;
}
/**
* 检查群组类型
* @param string|array|null $groupType
* @return void
*/
public function checkGroup($groupType = null)
{
if ($this->type !== 'group') {
throw new ApiException('仅限群组操作');
}
if ($groupType) {
$groupTypes = is_array($groupType) ? $groupType : [$groupType];
if (!in_array($this->group_type, $groupTypes)) {
throw new ApiException('操作的群组类型错误');
}
}
}
/**
* 检查禁言
* @param $userid
* @return void
*/
public function checkMute($userid)
{
if ($this->group_type === 'all') {
$allGroupMute = Base::settingFind('system', 'all_group_mute');
switch ($allGroupMute) {
case 'all':
throw new ApiException('当前会话全员禁言');
case 'user':
if (!User::find($userid)?->isAdmin()) {
throw new ApiException('当前会话禁言');
}
}
}
}
/**
* 获取群组名称
* @return mixed|string|null
*/
public function getGroupName()
{
if (!isset($this->appendattrs['groupName'])) {
$name = $this->name;
if ($this->type == "group") {
switch ($this->group_type) {
case 'project':
$name = \DB::table('projects')->where('dialog_id', $this->id)->value('name');
break;
case 'task':
$name = \DB::table('project_tasks')->where('dialog_id', $this->id)->value('name');
break;
case 'all':
$name = Base::Lang('全体成员');
break;
}
}
$this->appendattrs['groupName'] = $name;
}
return $this->appendattrs['groupName'];
}
/**
* 推送消息
* @param $action
* @param array $data 发送内容,默认为[id=>会话ID]
* @param array $userid 指定会员,默认为群组所有成员
* @return void
*/
public function pushMsg($action, $data = null, $userid = null)
{
if ($data === null) {
$data = ['id' => $this->id];
}
//
if ($userid === null) {
$userid = $this->dialogUser->pluck('userid')->toArray();
}
//
$params = [
'userid' => $userid,
'msg' => [
'type' => 'dialog',
'mode' => $action,
'data' => $data,
]
];
$task = new PushTask($params, false);
Task::deliver($task);
}
/**
* 获取对话(同时检验对话身份)
* @param $dialog_id
* @param bool|string $checkOwner 是否校验群组身份,'auto'时有群主为true无群主为false
* @return self
*/
public static function checkDialog($dialog_id)
public static function checkDialog($dialog_id, $checkOwner = false)
{
$dialog = WebSocketDialog::find($dialog_id);
if (empty($dialog)) {
@@ -76,7 +355,14 @@ class WebSocketDialog extends AbstractModel
}
//
$userid = User::userid();
if ($dialog->type === 'group' && $dialog->group_type === 'task') {
if ($checkOwner === 'auto') {
$checkOwner = $dialog->owner_id > 0;
}
if ($checkOwner === true && $dialog->owner_id != $userid) {
throw new ApiException('仅限群主操作');
}
//
if ($dialog->group_type === 'task') {
// 任务群对话校验是否在项目内
$project_id = intval(ProjectTask::whereDialogId($dialog->id)->value('project_id'));
if ($project_id > 0) {
@@ -86,48 +372,7 @@ class WebSocketDialog extends AbstractModel
}
}
if (!WebSocketDialogUser::whereDialogId($dialog->id)->whereUserid($userid)->exists()) {
throw new ApiException('不在成员列表内');
}
return $dialog;
}
/**
* 格式化对话
* @param WebSocketDialog $dialog
* @param int $userid 会员ID
* @return self|null
*/
public static function formatData(WebSocketDialog $dialog, $userid)
{
if (empty($dialog)) {
return null;
}
// 最后消息
$last_msg = WebSocketDialogMsg::whereDialogId($dialog->id)->orderByDesc('id')->first();
$dialog->last_msg = $last_msg;
// 未读信息
$dialog->unread = WebSocketDialogMsgRead::whereDialogId($dialog->id)->whereUserid($userid)->whereReadAt(null)->count();
// 对话人数
$builder = WebSocketDialogUser::whereDialogId($dialog->id);
$dialog->people = $builder->count();
// 对方信息
$dialog->dialog_user = null;
$dialog->group_info = null;
switch ($dialog->type) {
case "user":
$dialog_user = $builder->where('userid', '!=', $userid)->first();
$dialog->name = User::userid2nickname($dialog_user->userid);
$dialog->dialog_user = $dialog_user;
break;
case "group":
if ($dialog->group_type === 'project') {
$dialog->group_info = Project::withTrashed()->select(['id', 'name'])->whereDialogId($dialog->id)->first();
$dialog->name = $dialog->group_info ? $dialog->group_info->name : '';
} elseif ($dialog->group_type === 'task') {
$dialog->group_info = ProjectTask::withTrashed()->select(['id', 'name'])->whereDialogId($dialog->id)->first();
$dialog->name = $dialog->group_info ? $dialog->group_info->name : '';
}
break;
throw new ApiException('不在成员列表内', ['dialog_id' => $dialog_id], -4003);
}
return $dialog;
}
@@ -135,17 +380,20 @@ class WebSocketDialog extends AbstractModel
/**
* 创建聊天室
* @param string $name 聊天室名称
* @param int|array $userid 加入的会员ID或会员ID组
* @param int|array $userid 加入的会员ID(组)
* @param string $group_type 聊天室类型
* @param int $owner_id 群主会员ID
* @return self|null
*/
public static function createGroup($name, $userid, $group_type = '')
public static function createGroup($name, $userid, $group_type = '', $owner_id = 0)
{
return AbstractModel::transaction(function () use ($userid, $group_type, $name) {
return AbstractModel::transaction(function () use ($owner_id, $userid, $group_type, $name) {
$dialog = self::createInstance([
'type' => 'group',
'name' => $name ?: '',
'group_type' => $group_type,
'owner_id' => $owner_id,
'last_at' => in_array($group_type, ['user', 'all']) ? Carbon::now() : null,
]);
$dialog->save();
foreach (is_array($userid) ? $userid : [$userid] as $value) {
@@ -153,6 +401,7 @@ class WebSocketDialog extends AbstractModel
WebSocketDialogUser::createInstance([
'dialog_id' => $dialog->id,
'userid' => $value,
'important' => !in_array($group_type, ['user', 'all'])
])->save();
}
}
@@ -160,47 +409,6 @@ class WebSocketDialog extends AbstractModel
});
}
/**
* 加入聊天室
* @param int $dialog_id 会话ID即 聊天室ID
* @param int|array $userid 加入的会员ID或会员ID组
* @return bool
*/
public static function joinGroup($dialog_id, $userid)
{
$dialog = self::whereId($dialog_id)->whereType('group')->first();
if (empty($dialog)) {
return false;
}
AbstractModel::transaction(function () use ($dialog, $userid) {
foreach (is_array($userid) ? $userid : [$userid] as $value) {
if ($value > 0) {
WebSocketDialogUser::createInstance([
'dialog_id' => $dialog->id,
'userid' => $value,
])->save();
}
}
});
return true;
}
/**
* 退出聊天室
* @param int $dialog_id 会话ID即 聊天室ID
* @param int|array $userid 加入的会员ID或会员ID组
* @return bool
*/
public static function exitGroup($dialog_id, $userid)
{
if (is_array($userid)) {
WebSocketDialogUser::whereDialogId($dialog_id)->whereIn('userid', $userid)->delete();
} else {
WebSocketDialogUser::whereDialogId($dialog_id)->whereUserid($userid)->delete();
}
return true;
}
/**
* 获取会员对话(没有自动创建)
* @param int $userid 会员ID
@@ -209,6 +417,9 @@ class WebSocketDialog extends AbstractModel
*/
public static function checkUserDialog($userid, $userid2)
{
if ($userid == $userid2) {
$userid2 = 0;
}
$dialogUser = self::select(['web_socket_dialogs.*'])
->join('web_socket_dialog_users as u1', 'web_socket_dialogs.id', '=', 'u1.dialog_id')
->join('web_socket_dialog_users as u2', 'web_socket_dialogs.id', '=', 'u2.dialog_id')
@@ -235,5 +446,4 @@ class WebSocketDialog extends AbstractModel
return $dialog;
});
}
}

View File

@@ -8,45 +8,84 @@ use App\Tasks\PushTask;
use App\Tasks\WebSocketDialogMsgTask;
use Carbon\Carbon;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* Class WebSocketDialogMsg
* App\Models\WebSocketDialogMsg
*
* @package App\Models
* @property int $id
* @property int|null $dialog_id 对话ID
* @property string|null $dialog_type 对话类型
* @property int|null $userid 发送会员ID
* @property string|null $type 消息类型
* @property string|null $mtype 消息类型(用于搜索)
* @property array|mixed $msg 详细消息
* @property array|mixed $emoji emoji回复
* @property string|null $key 搜索关键词
* @property int|null $read 已阅数量
* @property int|null $send 发送数量
* @property int|null $tag 标注会员ID
* @property int|null $todo 设为待办会员ID
* @property int|null $link 是否存在链接
* @property int|null $modify 是否编辑
* @property int|null $reply_num 有多少条回复
* @property int|null $reply_id 回复ID
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read int|mixed $percentage
* @property-read \App\Models\WebSocketDialogMsg|null $reply_data
* @property-read \App\Models\WebSocketDialog|null $webSocketDialog
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newQuery()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg query()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDialogType($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereEmoji($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereKey($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereLink($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereModify($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereMsg($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereMtype($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereRead($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereReplyId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereReplyNum($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereSend($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereTag($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereTodo($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg withTrashed()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg withoutTrashed()
* @mixin \Eloquent
*/
class WebSocketDialogMsg extends AbstractModel
{
use SoftDeletes;
protected $appends = [
'percentage',
'reply_data',
];
protected $hidden = [
'key',
'updated_at',
];
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function webSocketDialog(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(WebSocketDialog::class, 'id', 'dialog_id');
}
/**
* 阅读占比
* @return int|mixed
@@ -54,15 +93,26 @@ class WebSocketDialogMsg extends AbstractModel
public function getPercentageAttribute()
{
if (!isset($this->appendattrs['percentage'])) {
if ($this->read > $this->send || empty($this->send)) {
$this->appendattrs['percentage'] = 100;
} else {
$this->appendattrs['percentage'] = intval($this->read / $this->send * 100);
}
$this->generatePercentage();
}
return $this->appendattrs['percentage'];
}
/**
* 回复消息详情
* @return WebSocketDialogMsg|null
*/
public function getReplyDataAttribute()
{
if (!isset($this->appendattrs['reply_data'])) {
$this->appendattrs['reply_data'] = null;
if ($this->reply_id > 0) {
$this->appendattrs['reply_data'] = self::find($this->reply_id, ['id', 'userid', 'type', 'msg'])?->cancelAppend() ?: null;
}
}
return $this->appendattrs['reply_data'];
}
/**
* 消息格式化
* @param $value
@@ -78,10 +128,41 @@ class WebSocketDialogMsg extends AbstractModel
$value['type'] = in_array($value['ext'], ['jpg', 'jpeg', 'png', 'gif']) ? 'img' : 'file';
$value['path'] = Base::fillUrl($value['path']);
$value['thumb'] = Base::fillUrl($value['thumb'] ?: Base::extIcon($value['ext']));
} else if ($this->type === 'record') {
$value['path'] = Base::fillUrl($value['path']);
}
return $value;
}
/**
* emoji回复格式化
* @param $value
* @return array|mixed
*/
public function getEmojiAttribute($value)
{
if (is_array($value)) {
return $value;
}
return Base::json2array($value);
}
/**
* 获取占比
* @param bool|int $increment 是否新增阅读数
* @return int
*/
public function generatePercentage($increment = false) {
if ($increment) {
$this->increment('read', is_bool($increment) ? 1 : $increment);
}
if ($this->read > $this->send || empty($this->send)) {
return $this->appendattrs['percentage'] = 100;
} else {
return $this->appendattrs['percentage'] = intval($this->read / $this->send * 100);
}
}
/**
* 标记已送达 同时 告诉发送人已送达
* @param $userid
@@ -111,13 +192,17 @@ class WebSocketDialogMsg extends AbstractModel
if (!$msgRead->read_at) {
$msgRead->read_at = Carbon::now();
$msgRead->save();
$this->increment('read');
$this->generatePercentage(true);
PushTask::push([
'userid' => $this->userid,
'msg' => [
'type' => 'dialog',
'mode' => 'update',
'data' => $this->toArray(),
'mode' => 'readed',
'data' => [
'id' => $this->id,
'read' => $this->read,
'percentage' => $this->percentage,
],
]
]);
}
@@ -126,33 +211,539 @@ class WebSocketDialogMsg extends AbstractModel
}
/**
* 发送消息
* @param int $dialog_id 会话ID即 聊天室ID
* @param string $type 消息类型
* @param array $msg 发送的消息
* @param int $sender 发送的会员ID默认自己0为系统
* emoji回复
* @param $symbol
* @param int $sender 发送的会员ID
* @return mixed
*/
public function emojiMsg($symbol, $sender)
{
$exist = false;
$array = $this->emoji;
foreach ($array as $index => &$item) {
if ($item['symbol'] === $symbol) {
if (in_array($sender, $item['userids'])) {
// 已存在 去除
$item['userids'] = array_values(array_diff($item['userids'], [$sender]));
if (empty($item['userids'])) {
unset($array[$index]);
$array = array_values($array);
}
} else {
// 未存在 添加
array_unshift($item['userids'], $sender);
}
$exist = true;
break;
}
}
if (!$exist) {
array_unshift($array, [
'symbol' => $symbol,
'userids' => [$sender]
]);
}
//
$this->emoji = Base::array2json($array);
$this->save();
$resData = [
'id' => $this->id,
'emoji' => $array,
];
//
$dialog = WebSocketDialog::find($this->dialog_id);
$dialog?->pushMsg('update', $resData);
//
return Base::retSuccess('sucess', $resData);
}
/**
* 标注、取消标注
* @param int $sender 标注的会员ID
* @return mixed
*/
public function toggleTagMsg($sender)
{
if (in_array($this->type, ['tag', 'todo', 'notice'])) {
return Base::retError('此消息不支持标注');
}
$before = $this->tag;
$this->tag = $before ? 0 : $sender;
$this->save();
$resData = [
'id' => $this->id,
'tag' => $this->tag,
];
//
$data = [
'update' => $resData
];
$res = self::sendMsg(null, $this->dialog_id, 'tag', [
'action' => $this->tag ? 'add' : 'remove',
'data' => [
'id' => $this->id,
'type' => $this->type,
'msg' => $this->quoteTextMsg(),
]
], $sender);
if (Base::isSuccess($res)) {
$data['add'] = $res['data'];
$dialog = WebSocketDialog::find($this->dialog_id);
$dialog->pushMsg('update', $resData);
} else {
$this->tag = $before;
$this->save();
}
//
return Base::retSuccess($this->tag ? '标注成功' : '取消成功', $data);
}
/**
* 设待办、取消待办
* @param int $sender 设待办的会员ID
* @param array $userids 设置给指定会员
* @return mixed
*/
public function toggleTodoMsg($sender, $userids = [])
{
if (in_array($this->type, ['tag', 'todo', 'notice'])) {
return Base::retError('此消息不支持设待办');
}
if ($this->todo && $this->todo != $sender) {
return Base::retError('仅支持设此待办人员【' . User::userid2nickname($this->todo) . '】取消');
}
$before = $this->todo;
$this->todo = $before ? 0 : $sender;
$this->save();
$resData = [
'id' => $this->id,
'todo' => $this->todo,
];
//
$data = [
'update' => $resData
];
$res = self::sendMsg(null, $this->dialog_id, 'todo', [
'action' => $this->todo ? 'add' : 'remove',
'data' => [
'id' => $this->id,
'type' => $this->type,
'msg' => $this->quoteTextMsg(),
'userids' => implode(",", $userids),
]
], $sender);
if (Base::isSuccess($res)) {
$data['add'] = $res['data'];
$dialog = WebSocketDialog::find($this->dialog_id);
$dialog->pushMsg('update', array_merge($resData, ['dialog_id' => $this->dialog_id]));
//
if ($this->todo) {
$useridList = $dialog->dialogUser->pluck('userid')->toArray();
foreach ($useridList as $userid) {
if ($userids && !in_array($userid, $userids)) {
continue;
}
if (empty($userid)) {
continue;
}
WebSocketDialogMsgTodo::createInstance([
'dialog_id' => $this->dialog_id,
'msg_id' => $this->id,
'userid' => $userid,
])->saveOrIgnore();
}
} else {
WebSocketDialogMsgTodo::whereMsgId($this->id)->delete();
}
} else {
$this->todo = $before;
$this->save();
}
//
return Base::retSuccess($this->todo ? '设置成功' : '取消成功', $data);
}
/**
* 转发消息
* @param array|int $dialogids
* @param array|int $userids
* @param int $sender 发送的会员ID
* @return mixed
*/
public function forwardMsg($dialogids, $userids, $sender)
{
return AbstractModel::transaction(function() use ($dialogids, $sender, $userids) {
$originalMsg = Base::json2array($this->getRawOriginal('msg'));
$msgs = [];
$already = [];
if ($dialogids) {
if (!is_array($dialogids)) {
$dialogids = [$dialogids];
}
foreach ($dialogids as $dialogid) {
$res = self::sendMsg(null, $dialogid, $this->type, $originalMsg, $sender);
if (Base::isSuccess($res)) {
$msgs[] = $res['data'];
$already[] = $dialogid;
}
}
}
if ($userids) {
if (!is_array($userids)) {
$userids = [$userids];
}
foreach ($userids as $userid) {
if (!User::whereUserid($userid)->exists()) {
continue;
}
$dialog = WebSocketDialog::checkUserDialog($sender, $userid);
if ($dialog && !in_array($dialog->id, $already)) {
$res = self::sendMsg(null, $dialog->id, $this->type, $originalMsg, $sender);
if (Base::isSuccess($res)) {
$msgs[] = $res['data'];
}
}
}
}
return Base::retSuccess('转发成功', [
'msgs' => $msgs
]);
});
}
/**
* 删除消息
* @return void
*/
public function deleteMsg()
{
$send_dt = Carbon::parse($this->created_at)->addDay();
if ($send_dt->lt(Carbon::now())) {
throw new ApiException('已超过24小时此消息不能撤回');
}
AbstractModel::transaction(function() {
$deleteRead = WebSocketDialogMsgRead::whereMsgId($this->id)->whereNull('read_at')->delete(); // 未阅读记录不需要软删除,直接删除即可
$this->delete();
//
if ($this->reply_id > 0) {
self::whereId($this->reply_id)->decrement('reply_num');
}
//
$last_msg = null;
if ($this->webSocketDialog) {
$last_msg = WebSocketDialogMsg::whereDialogId($this->dialog_id)->orderByDesc('id')->first();
$this->webSocketDialog->last_at = $last_msg->created_at;
$this->webSocketDialog->save();
}
//
$dialog = WebSocketDialog::find($this->dialog_id);
if ($dialog) {
$userids = $dialog->dialogUser->pluck('userid')->toArray();
PushTask::push([
'userid' => $userids,
'msg' => [
'type' => 'dialog',
'mode' => 'delete',
'data' => [
'id' => $this->id,
'dialog_id' => $this->dialog_id,
'last_msg' => $last_msg,
'update_read' => $deleteRead ? 1 : 0
],
]
]);
}
//
WebSocketDialogMsgTodo::whereMsgId($this->id)->delete();
});
}
/**
* 预览消息
* @param bool $preserveHtml 保留html格式
* @param null|array $data
* @return string
*/
public function previewMsg($preserveHtml = false, $data = null)
{
if ($data === null) {
$data = [
'type' => $this->type,
'msg' => $this->msg,
];
}
switch ($data['type']) {
case 'text':
return $this->previewTextMsg($data['msg']['text'], $preserveHtml);
case 'record':
return "[语音]";
case 'meeting':
return "[会议] ${$data['msg']['name']}";
case 'file':
if ($data['msg']['type'] == 'img') {
return "[图片]";
}
return "[文件] {$data['msg']['name']}";
case 'tag':
$action = $data['msg']['action'] === 'remove' ? '取消标注' : '标注';
return "[{$action}] {$this->previewMsg(false, $data['msg']['data'])}";
case 'todo':
$action = $data['msg']['action'] === 'remove' ? '取消待办' : ($data['msg']['action'] === 'done' ? '完成' : '设待办');
return "[{$action}] {$this->previewMsg(false, $data['msg']['data'])}";
case 'notice':
return $data['msg']['notice'];
default:
return "[未知的消息]";
}
}
/**
* 生成关键词
* @return string
*/
public function generateMsgKey()
{
return match ($this->type) {
'text' => str_replace("&nbsp;", " ", strip_tags($this->msg['text'])),
'meeting', 'file' => $this->msg['name'],
default => '',
};
}
/**
* 返回引用消息(如果是文本消息则截取)
* @param int $strlen
* @return array|mixed
*/
public function quoteTextMsg($strlen = 30)
{
$msg = $this->msg;
if ($this->type === 'text') {
$msg['text'] = $this->previewTextMsg($msg['text']);
if (mb_strlen($msg['text']) > $strlen) {
$msg['text'] = mb_substr($msg['text'], 0, $strlen - 3) . "...";
}
}
return $msg;
}
/**
* 返回文本预览消息
* @param $text
* @param bool $preserveHtml 保留html格式
* @return string|string[]|null
*/
private function previewTextMsg($text, $preserveHtml = false)
{
if (!$text) return '';
$text = preg_replace("/<img\s+class=\"emoticon\"[^>]*?alt=\"(\S+)\"[^>]*?>/", "[$1]", $text);
$text = preg_replace("/<img\s+class=\"emoticon\"[^>]*?>/", "[表情]", $text);
$text = preg_replace("/<img\s+class=\"browse\"[^>]*?>/", "[图片]", $text);
if (!$preserveHtml) {
$text = strip_tags($text);
$text = str_replace("&nbsp;", " ", $text);
}
return $text;
}
/**
* 处理文本消息内容,用于发送前
* @param $text
* @param $dialog_id
* @return mixed|string|string[]
*/
public static function formatMsg($text, $dialog_id)
{
@ini_set("pcre.backtrack_limit", 999999999);
// 图片 [:IMAGE:className:width:height:src:alt:]
preg_match_all("/<img\s+src=\"data:image\/(png|jpg|jpeg|gif);base64,(.*?)\"(.*?)>(<\/img>)*/s", $text, $matchs);
foreach ($matchs[2] as $key => $base64) {
$tmpPath = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
Base::makeDir(public_path($tmpPath));
$tmpPath .= md5s($base64) . "." . $matchs[1][$key];
if (file_put_contents(public_path($tmpPath), base64_decode($base64))) {
$imagesize = getimagesize(public_path($tmpPath));
if (Base::imgThumb(public_path($tmpPath), public_path($tmpPath) . "_thumb.jpg", 320, 0)) {
$tmpPath .= "_thumb.jpg";
}
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imagesize[0]}:{$imagesize[1]}:{$tmpPath}::]", $text);
}
}
// 表情图片
preg_match_all("/<img\s+class=\"emoticon\"(.*?)>/s", $text, $matchs);
foreach ($matchs[1] as $key => $str) {
preg_match("/data-asset=\"(.*?)\"/", $str, $matchAsset);
preg_match("/data-name=\"(.*?)\"/", $str, $matchName);
if (file_exists(public_path($matchAsset[1]))) {
$imagesize = getimagesize(public_path($matchAsset[1]));
$text = str_replace($matchs[0][$key], "[:IMAGE:emoticon:{$imagesize[0]}:{$imagesize[1]}:{$matchAsset[1]}:{$matchName[1]}:]", $text);
}
}
// 其他网络图片
preg_match_all("/<img[^>]*?src=([\"'])(.*?\.(png|jpg|jpeg|gif))\\1[^>]*?>/is", $text, $matchs);
foreach ($matchs[2] as $key => $str) {
if (str_starts_with($str, "{{RemoteURL}}")) {
$tmpPath = Base::leftDelete($str, "{{RemoteURL}}");
$tmpPath = Base::rightDelete($tmpPath, "_thumb.jpg");
} else {
$tmpPath = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
Base::makeDir(public_path($tmpPath));
$tmpPath .= md5s($str) . "." . $matchs[3][$key];
}
if (file_exists(public_path($tmpPath))) {
$imagesize = getimagesize(public_path($tmpPath));
if (Base::imgThumb(public_path($tmpPath), public_path($tmpPath) . "_thumb.jpg", 320, 0)) {
$tmpPath .= "_thumb.jpg";
}
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imagesize[0]}:{$imagesize[1]}:{$tmpPath}::]", $text);
} else {
$image = file_get_contents($str);
if (empty($image)) {
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:90:90:images/other/imgerr.jpg::]", $text);
} else if (file_put_contents(public_path($tmpPath), $image)) {
$imagesize = getimagesize(public_path($tmpPath));
if (Base::imgThumb(public_path($tmpPath), public_path($tmpPath) . "_thumb.jpg", 320, 0)) {
$tmpPath .= "_thumb.jpg";
}
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imagesize[0]}:{$imagesize[1]}:{$tmpPath}::]", $text);
}
}
}
// @成员、#任务
preg_match_all("/<span\s+class=\"mention\"(.*?)>.*?<\/span>.*?<\/span>.*?<\/span>/s", $text, $matchs);
foreach ($matchs[1] as $key => $str) {
preg_match("/data-denotation-char=\"(.*?)\"/", $str, $matchChar);
preg_match("/data-id=\"(.*?)\"/", $str, $matchId);
preg_match("/data-value=\"(.*?)\"/", $str, $matchValye);
$text = str_replace($matchs[0][$key], "[:{$matchChar[1]}:{$matchId[1]}:{$matchValye[1]}:]", $text);
}
// 处理链接
preg_match_all("/<a[^>]*?href=([\"'])(.*?)\\1[^>]*?>([^<]*?)<\/a>/is", $text, $matchs);
foreach ($matchs[2] as $key => $str) {
$herf = $matchs[2][$key];
$title = $matchs[3][$key] ?: $herf;
$text = str_replace($matchs[0][$key], "<a href=\"{$herf}\" target=\"_blank\">{$title}</a>", $text);
}
// 过滤标签
$text = strip_tags($text, '<blockquote> <strong> <pre> <ol> <ul> <li> <em> <p> <s> <u> <a>');
$text = preg_replace("/\<(blockquote|strong|pre|ol|ul|li|em|p|s|u).*?\>/is", "<$1>", $text); // 不用去除a标签上面已经处理过了
$text = preg_replace("/\[:IMAGE:(.*?):(.*?):(.*?):(.*?):(.*?):\]/i", "<img class=\"$1\" width=\"$2\" height=\"$3\" src=\"{{RemoteURL}}$4\" alt=\"$5\"/>", $text);
$text = preg_replace("/\[:@:(.*?):(.*?):\]/i", "<span class=\"mention user\" data-id=\"$1\">@$2</span>", $text);
$text = preg_replace("/\[:#:(.*?):(.*?):\]/i", "<span class=\"mention task\" data-id=\"$1\">#$2</span>", $text);
return preg_replace("/^(<p><\/p>)+|(<p><\/p>)+$/i", "", $text);
}
/**
* 发送消息、修改消息
* @param string $action 动作
* - reply-98回复消息ID=98
* - update-99更新消息ID=99
* @param int $dialog_id 会话ID即 聊天室ID
* @param string $type 消息类型
* @param array $msg 发送的消息
* @param int $sender 发送的会员ID默认自己0为系统
* @param bool $push_self 推送-是否推给自己
* @param bool $push_retry 推送-失败后重试1次有时候在事务里执行数据还没生成时会出现找不到消息的情况
* @param bool|null $push_silence 推送-静默
* - type = [text|file|record|meeting] 默认为false
* @return array
*/
public static function sendMsg($dialog_id, $type, $msg, $sender = 0)
public static function sendMsg($action, $dialog_id, $type, $msg, $sender = 0, $push_self = false, $push_retry = false, $push_silence = null)
{
$dialogMsg = self::createInstance([
'userid' => $sender ?: User::userid(),
'type' => $type,
'msg' => $msg,
'read' => 0,
]);
AbstractModel::transaction(function () use ($dialog_id, $msg, $dialogMsg) {
$dialog = WebSocketDialog::find($dialog_id);
if (empty($dialog)) {
throw new ApiException('获取会话失败');
$link = 0;
$mtype = $type;
if ($type === 'text') {
if (str_contains($msg['text'], '<a ') || preg_match("/https*:\/\//", $msg['text'])) {
$link = 1;
}
$dialog->last_at = Carbon::now();
$dialog->save();
$dialogMsg->send = 1;
$dialogMsg->dialog_id = $dialog->id;
if (str_contains($msg['text'], '<img ')) {
$mtype = str_contains($msg['text'], '"emoticon"') ? 'emoticon' : 'image';
}
} elseif ($type === 'file') {
if (in_array($msg['ext'], ['jpg', 'jpeg', 'png', 'gif'])) {
$mtype = 'image';
}
}
if ($push_silence === null) {
$push_silence = !in_array($type, ["text", "file", "record", "meeting"]);
}
//
$update_id = preg_match("/^update-(\d+)$/", $action, $match) ? $match[1] : 0;
$reply_id = preg_match("/^reply-(\d+)$/", $action, $match) ? $match[1] : 0;
$sender = $sender ?: User::userid();
//
$dialog = WebSocketDialog::find($dialog_id);
if (empty($dialog)) {
throw new ApiException('获取会话失败');
}
$dialog->checkMute($sender);
//
if ($update_id) {
// 修改
$dialogMsg = self::whereId($update_id)->whereDialogId($dialog_id)->first();
if (empty($dialogMsg)) {
throw new ApiException('消息不存在');
}
if ($dialogMsg->type !== 'text') {
throw new ApiException('此消息不支持此操作');
}
if ($dialogMsg->userid != $sender) {
throw new ApiException('仅支持修改自己的消息');
}
//
$updateData = [
'mtype' => $mtype,
'link' => $link,
'msg' => $msg,
'modify' => 1,
];
$dialogMsg->updateInstance($updateData);
$dialogMsg->key = $dialogMsg->generateMsgKey();
$dialogMsg->save();
});
Task::deliver(new WebSocketDialogMsgTask($dialogMsg->id));
return Base::retSuccess('发送成功', $dialogMsg);
//
$dialog->pushMsg('update', array_merge($updateData, [
'id' => $dialogMsg->id
]));
//
return Base::retSuccess('修改成功', $dialogMsg);
} else {
// 发送
if ($reply_id && !self::whereId($reply_id)->increment('reply_num')) {
throw new ApiException('回复的消息不存在');
}
//
$dialogMsg = self::createInstance([
'dialog_id' => $dialog_id,
'dialog_type' => $dialog->type,
'reply_id' => $reply_id,
'userid' => $sender,
'type' => $type,
'mtype' => $mtype,
'link' => $link,
'msg' => $msg,
'read' => 0,
]);
AbstractModel::transaction(function () use ($dialog, $dialogMsg) {
$dialog->last_at = Carbon::now();
$dialog->save();
$dialogMsg->send = 1;
$dialogMsg->key = $dialogMsg->generateMsgKey();
$dialogMsg->save();
});
//
$task = new WebSocketDialogMsgTask($dialogMsg->id);
if ($push_self) {
$task->setIgnoreFd(null);
}
if ($push_retry) {
$task->setMsgNotExistRetry(true);
}
if ($push_silence) {
$task->setSilence($push_silence);
}
Task::deliver($task);
//
return Base::retSuccess('发送成功', $dialogMsg);
}
}
}

View File

@@ -2,22 +2,28 @@
namespace App\Models;
use Carbon\Carbon;
/**
* Class WebSocketDialogMsgRead
* App\Models\WebSocketDialogMsgRead
*
* @package App\Models
* @property int $id
* @property int|null $dialog_id 对话ID
* @property int|null $msg_id 消息ID
* @property int|null $userid 发送会员ID
* @property int|null $mention 是否提及(被@
* @property int|null $email 是否发了邮件
* @property int|null $after 在阅读之后才添加的记录
* @property string|null $read_at 阅读时间
* @property-read \App\Models\WebSocketDialogMsg|null $webSocketDialogMsg
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead query()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereAfter($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereMention($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereMsgId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereReadAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereUserid($value)
@@ -30,4 +36,38 @@ class WebSocketDialogMsgRead extends AbstractModel
parent::__construct($attributes);
$this->timestamps = false;
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function webSocketDialogMsg(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(WebSocketDialogMsg::class, 'id', 'msg_id');
}
/**
* 仅标记成阅读
* @param $list
* @return void
*/
public static function onlyMarkRead($list)
{
$dialogMsg = [];
/** @var WebSocketDialogMsgRead $item */
foreach ($list as $item) {
$item->read_at = Carbon::now();
$item->save();
if (isset($dialogMsg[$item->msg_id])) {
$dialogMsg[$item->msg_id]['readNum']++;
} else {
$dialogMsg[$item->msg_id] = [
'dialogMsg' => $item->webSocketDialogMsg,
'readNum' => 1
];
}
}
foreach ($dialogMsg as $item) {
$item['dialogMsg']?->generatePercentage($item['readNum']);
}
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Models;
/**
* App\Models\WebSocketDialogMsgTodo
*
* @property int $id
* @property int|null $dialog_id 对话ID
* @property int|null $msg_id 消息ID
* @property int|null $userid 接收会员ID
* @property string|null $done_at 完成时间
* @property-read array|mixed $msg_data
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo query()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereDoneAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereMsgId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereUserid($value)
* @mixin \Eloquent
*/
class WebSocketDialogMsgTodo extends AbstractModel
{
protected $appends = [
'msg_data',
];
function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->timestamps = false;
}
/**
* 消息详情
* @return array|mixed
*/
public function getMsgDataAttribute()
{
if (!isset($this->appendattrs['msgData'])) {
$this->appendattrs['msgData'] = WebSocketDialogMsg::select(['id', 'type', 'msg'])->whereId($this->msg_id)->first()?->cancelAppend();
}
return $this->appendattrs['msgData'];
}
}

View File

@@ -3,12 +3,15 @@
namespace App\Models;
/**
* Class WebSocketDialogUser
* App\Models\WebSocketDialogUser
*
* @package App\Models
* @property int $id
* @property int|null $dialog_id 对话ID
* @property int|null $userid 会员ID
* @property string|null $top_at 置顶时间
* @property int|null $mark_unread 是否标记为未读0否1是
* @property int|null $inviter 邀请人
* @property int|null $important 是否不可移出(项目、任务人员)
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newModelQuery()
@@ -17,6 +20,10 @@ namespace App\Models;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereImportant($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereInviter($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereMarkUnread($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereTopAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUserid($value)
* @mixin \Eloquent

View File

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

View File

@@ -0,0 +1,193 @@
<?php
/**
* @Description :
*
* @Date : 2019-03-14 13:22
* @Author : hmy940118@gmail.com
*/
namespace App\Module\AgoraIO;
class AccessToken
{
const Privileges = array(
"kJoinChannel" => 1,
"kPublishAudioStream" => 2,
"kPublishVideoStream" => 3,
"kPublishDataStream" => 4,
"kPublishAudioCdn" => 5,
"kPublishVideoCdn" => 6,
"kRequestPublishAudioStream" => 7,
"kRequestPublishVideoStream" => 8,
"kRequestPublishDataStream" => 9,
"kInvitePublishAudioStream" => 10,
"kInvitePublishVideoStream" => 11,
"kInvitePublishDataStream" => 12,
"kAdministrateChannel" => 101
);
public $appID, $appCertificate, $channelName, $uid;
public $message;
/**
* AccessToken constructor.
* @throws \Exception
*/
public function __construct()
{
$this->message = new Message();
}
/**
* @param $uid
*/
public function setUid($uid)
{
if ($uid === 0) {
$this->uid = "";
} else {
$this->uid = $uid . '';
}
}
/**
* @param $name
* @param $str
* @return bool
*/
public function is_nonempty_string($name, $str)
{
if (is_string($str) && $str !== "") {
return true;
}
echo $name . " check failed, should be a non-empty string";
return false;
}
/**
* @param $appID
* @param $appCertificate
* @param $channelName
* @param $uid
* @return AccessToken|null
* @throws \Exception
*/
public static function init($appID, $appCertificate, $channelName, $uid)
{
$accessToken = new AccessToken();
if (!$accessToken->is_nonempty_string("appID", $appID) ||
!$accessToken->is_nonempty_string("appCertificate", $appCertificate) ||
!$accessToken->is_nonempty_string("channelName", $channelName)) {
return null;
}
$accessToken->appID = $appID;
$accessToken->appCertificate = $appCertificate;
$accessToken->channelName = $channelName;
$accessToken->setUid($uid);
$accessToken->message = new Message();
return $accessToken;
}
/**
* @param $token
* @param $appCertificate
* @param $channel
* @param $uid
* @return AccessToken|null
* @throws \Exception
*/
public static function initWithToken($token, $appCertificate, $channel, $uid)
{
$accessToken = new AccessToken();
if (!$accessToken->extract($token, $appCertificate, $channel, $uid)) {
return null;
}
return $accessToken;
}
/**
* @param $key
* @param $expireTimestamp
* @return $this
*/
public function addPrivilege($key, $expireTimestamp)
{
$this->message->privileges[$key] = $expireTimestamp;
return $this;
}
/**
* @param $token
* @param $appCertificate
* @param $channelName
* @param $uid
* @return bool
* @throws \Exception
*/
protected function extract($token, $appCertificate, $channelName, $uid)
{
$ver_len = 3;
$appid_len = 32;
$version = substr($token, 0, $ver_len);
if ($version !== "006") {
echo 'invalid version ' . $version;
return false;
}
if (!$this->is_nonempty_string("token", $token) ||
!$this->is_nonempty_string("appCertificate", $appCertificate) ||
!$this->is_nonempty_string("channelName", $channelName)) {
return false;
}
$appid = substr($token, $ver_len, $appid_len);
$content = (base64_decode(substr($token, $ver_len + $appid_len, strlen($token) - ($ver_len + $appid_len))));
$pos = 0;
$len = unpack("v", $content . substr($pos, 2))[1];
$pos += 2;
$sig = substr($content, $pos, $len);
$pos += $len;
$crc_channel = unpack("V", substr($content, $pos, 4))[1];
$pos += 4;
$crc_uid = unpack("V", substr($content, $pos, 4))[1];
$pos += 4;
$msgLen = unpack("v", substr($content, $pos, 2))[1];
$pos += 2;
$msg = substr($content, $pos, $msgLen);
$this->appID = $appid;
$message = new Message();
$message->unpackContent($msg);
$this->message = $message;
//non reversable values
$this->appCertificate = $appCertificate;
$this->channelName = $channelName;
$this->setUid($uid);
return true;
}
/**
* @return string
*/
public function build()
{
$msg = $this->message->packContent();
$val = array_merge(unpack("C*", $this->appID), unpack("C*", $this->channelName), unpack("C*", $this->uid), $msg);
$sig = hash_hmac('sha256', implode(array_map("chr", $val)), $this->appCertificate, true);
$crc_channel_name = crc32($this->channelName) & 0xffffffff;
$crc_uid = crc32($this->uid) & 0xffffffff;
$content = array_merge(unpack("C*", $this->packString($sig)), unpack("C*", pack("V", $crc_channel_name)), unpack("C*", pack("V", $crc_uid)), unpack("C*", pack("v", count($msg))), $msg);
$version = "006";
$ret = $version . $this->appID . base64_encode(implode(array_map("chr", $content)));
return $ret;
}
/**
* @param $value
* @return string
*/
public function packString($value)
{
return pack("v", strlen($value)) . $value;
}
}

View File

@@ -0,0 +1,122 @@
<?php
/**
* @Description :
*
* @Date : 2019-03-14 13:20
* @Author : hmy940118@gmail.com
*/
namespace App\Module\AgoraIO;
class AgoraTokenGenerator
{
const AttendeePrivileges = array(
AccessToken::Privileges["kJoinChannel"] => 0,
AccessToken::Privileges["kPublishAudioStream"] => 0,
AccessToken::Privileges["kPublishVideoStream"] => 0,
AccessToken::Privileges["kPublishDataStream"] => 0
);
const PublisherPrivileges = array(
AccessToken::Privileges["kJoinChannel"] => 0,
AccessToken::Privileges["kPublishAudioStream"] => 0,
AccessToken::Privileges["kPublishVideoStream"] => 0,
AccessToken::Privileges["kPublishDataStream"] => 0,
AccessToken::Privileges["kPublishAudioCdn"] => 0,
AccessToken::Privileges["kPublishVideoCdn"] => 0,
AccessToken::Privileges["kInvitePublishAudioStream"] => 0,
AccessToken::Privileges["kInvitePublishVideoStream"] => 0,
AccessToken::Privileges["kInvitePublishDataStream"] => 0
);
const SubscriberPrivileges = array(
AccessToken::Privileges["kJoinChannel"] => 0,
AccessToken::Privileges["kRequestPublishAudioStream"] => 0,
AccessToken::Privileges["kRequestPublishVideoStream"] => 0,
AccessToken::Privileges["kRequestPublishDataStream"] => 0
);
const AdminPrivileges = array(
AccessToken::Privileges["kJoinChannel"] => 0,
AccessToken::Privileges["kPublishAudioStream"] => 0,
AccessToken::Privileges["kPublishVideoStream"] => 0,
AccessToken::Privileges["kPublishDataStream"] => 0,
AccessToken::Privileges["kAdministrateChannel"] => 0
);
const Role = array(
"kRoleAttendee" => 0, // for communication
"kRolePublisher" => 1, // for live broadcast
"kRoleSubscriber" => 2, // for live broadcast
"kRoleAdmin" => 101
);
const RolePrivileges = array(
self::Role["kRoleAttendee"] => self::AttendeePrivileges,
self::Role["kRolePublisher"] => self::PublisherPrivileges,
self::Role["kRoleSubscriber"] => self::SubscriberPrivileges,
self::Role["kRoleAdmin"] => self::AdminPrivileges
);
public $token;
/**
* AgoraTokenGenerator constructor.
* @param $appID
* @param $appCertificate
* @param $channelName
* @param $uid
* @throws \Exception
*/
public function __construct($appID, $appCertificate, $channelName, $uid){
$this->token = new AccessToken();
$this->token->appID = $appID;
$this->token->appCertificate = $appCertificate;
$this->token->channelName = $channelName;
$this->token->setUid($uid);
}
/**
* @param $token
* @param $appCertificate
* @param $channel
* @param $uid
* @throws \Exception
*/
public function initWithToken($token, $appCertificate, $channel, $uid){
$this->token = AccessToken::initWithToken($token, $appCertificate, $channel, $uid);
}
/**
* @param $role
*/
public function initPrivilege($role){
$p = self::RolePrivileges[$role];
foreach($p as $key => $value){
$this->setPrivilege($key, $value);
}
}
/**
* @param $privilege
* @param $expireTimestamp
*/
public function setPrivilege($privilege, $expireTimestamp){
$this->token->addPrivilege($privilege, $expireTimestamp);
}
/**
* @param $privilege
*/
public function removePrivilege($privilege){
unset($this->token->message->privileges[$privilege]);
}
/**
* @return string
*/
public function buildToken(){
return $this->token->build();
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* @Description :
*
* @Date : 2019-03-14 13:27
* @Author : hmy940118@gmail.com
*/
namespace App\Module\AgoraIO;
class Message
{
public $salt;
public $ts;
public $privileges;
/**
* Message constructor.
* @throws \Exception
*/
public function __construct()
{
$this->salt = rand(0, 100000);
$date = new \DateTime("now", new \DateTimeZone('UTC'));
$this->ts = $date->getTimestamp() + 168 * 3600; // 7天时间
$this->privileges = array();
}
/**
* @return array
*/
public function packContent()
{
$buffer = unpack("C*", pack("V", $this->salt));
$buffer = array_merge($buffer, unpack("C*", pack("V", $this->ts)));
$buffer = array_merge($buffer, unpack("C*", pack("v", sizeof($this->privileges))));
foreach ($this->privileges as $key => $value) {
$buffer = array_merge($buffer, unpack("C*", pack("v", $key)));
$buffer = array_merge($buffer, unpack("C*", pack("V", $value)));
}
return $buffer;
}
/**
* @param $msg
*/
public function unpackContent($msg)
{
$pos = 0;
$salt = unpack("V", substr($msg, $pos, 4))[1];
$pos += 4;
$ts = unpack("V", substr($msg, $pos, 4))[1];
$pos += 4;
$size = unpack("v", substr($msg, $pos, 2))[1];
$pos += 2;
$privileges = array();
for ($i = 0; $i < $size; $i++) {
$key = unpack("v", substr($msg, $pos, 2));
$pos += 2;
$value = unpack("V", substr($msg, $pos, 4));
$pos += 4;
$privileges[$key[1]] = $value[1];
}
$this->salt = $salt;
$this->ts = $ts;
$this->privileges = $privileges;
}
}

View File

@@ -2,15 +2,17 @@
namespace App\Module;
use App\Exceptions\ApiException;
use App\Models\Setting;
use App\Models\Tmp;
use Cache;
use Exception;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use Overtrue\Pinyin\Pinyin;
use Redirect;
use Request;
use Storage;
use Validator;
class Base
{
@@ -58,20 +60,55 @@ class Base
])->save();
}
/**
* 获取package配置文件
* @return array
*/
public static function getPackage()
{
return Cache::remember("Base::package", now()->addSeconds(10), function () {
$file = base_path('package.json');
if (file_exists($file)) {
$package = json_decode(file_get_contents($file), true);
return is_array($package) ? $package : [];
}
return [];
});
}
/**
* 获取版本号
* @return string
*/
public static function getVersion()
{
return Cache::remember("Base::version", now()->addSeconds(10), function () {
$file = base_path('package.json');
if (file_exists($file)) {
$packageArray = json_decode(file_get_contents($file), true);
return $packageArray['version'] ?? '1.0.0';
}
return '1.0.0';
});
$package = self::getPackage();
return $package['version'] ?? '1.0.0';
}
/**
* 获取客户端版本号
* @return string
*/
public static function getClientVersion()
{
global $_A;
if (!isset($_A["__static_client_version"])) {
$_A["__static_client_version"] = Request::header('version') ?: '0.0.1';
}
return $_A["__static_client_version"];
}
/**
* 检查客户端版本
* @param string $min 最小版本
* @return void
*/
public static function checkClientVersion($min)
{
if (version_compare(Base::getClientVersion(), $min, '<')) {
throw new ApiException('当前版本 (v' . Base::getClientVersion() . ') 过低,最低版本要求 (v' . $min . ')。');
}
}
/**
@@ -248,7 +285,7 @@ class Base
{
try {
Storage::makeDirectory($path);
} catch (Exception $e) {
} catch (\Throwable $e) {
}
if (!file_exists($path)) {
self::makeDir(dirname($path));
@@ -316,19 +353,15 @@ class Base
{
if (strtolower($charset) == 'utf-8') {
if (Base::getStrlen($string) <= $length) return $string;
$strcut = str_replace(array('&amp;', '&quot;', '&lt;', '&gt;'), array('&', '"', '<', '>'), $string);
$strcut = Base::utf8Substr($strcut, $length, $start);
$strcut = str_replace(array('&', '"', '<', '>'), array('&amp;', '&quot;', '&lt;', '&gt;'), $strcut);
$strcut = Base::utf8Substr($string, $length, $start);
return $strcut . $dot;
} else {
$length = $length * 2;
if (strlen($string) <= $length) return $string;
$string = str_replace(array('&amp;', '&quot;', '&lt;', '&gt;'), array('&', '"', '<', '>'), $string);
$strcut = '';
for ($i = 0; $i < $length; $i++) {
$strcut .= ord($string[$i]) > 127 ? $string[$i] . $string[++$i] : $string[$i];
}
$strcut = str_replace(array('&', '"', '<', '>'), array('&amp;', '&quot;', '&lt;', '&gt;'), $strcut);
}
return $strcut . $dot;
}
@@ -453,7 +486,7 @@ class Base
try {
$array = json_decode($string, true);
return is_array($array) ? $array : [];
} catch (Exception $e) {
} catch (\Throwable) {
return [];
}
}
@@ -471,7 +504,7 @@ class Base
}
try {
return json_encode($array, $options);
} catch (Exception $e) {
} catch (\Throwable) {
return '';
}
}
@@ -684,24 +717,20 @@ class Base
/**
* 判断两个地址域名是否相同
* @param string $var1
* @param string $var2
* @param string|array $var2
* @return bool
*/
public static function hostContrast($var1, $var2)
{
$arr1 = parse_url($var1);
$arr2 = parse_url($var2);
$host1 = $arr1['host'] ?? $var1;
//
$host1 = $var1;
if (isset($arr1['host'])) {
$host1 = $arr1['host'];
$host2 = [];
foreach (is_array($var2) ? $var2 : [$var2] as $url) {
$arr2 = parse_url($url);
$host2[] = $arr2['host'] ?? $url;
}
//
$host2 = $var2;
if (isset($arr2['host'])) {
$host2 = $arr2['host'];
}
return $host1 == $host2;
return in_array($host1, $host2);
}
/**
@@ -725,6 +754,7 @@ class Base
*/
public static function fillUrl($str = '')
{
global $_A;
if (is_array($str)) {
foreach ($str as $key => $item) {
$str[$key] = Base::fillUrl($item);
@@ -743,9 +773,12 @@ class Base
) {
return $str;
} else {
if ($_A['__fill_url_remote_url'] === true) {
return "{{RemoteURL}}" . $str;
}
try {
return url($str);
} catch (\Throwable $e) {
} catch (\Throwable) {
return self::getSchemeAndHost() . "/" . $str;
}
}
@@ -766,7 +799,7 @@ class Base
}
try {
$find = url('');
} catch (\Throwable $e) {
} catch (\Throwable) {
$find = self::getSchemeAndHost();
}
return Base::leftDelete($str, $find . '/');
@@ -782,6 +815,31 @@ class Base
return $scheme.($_SERVER['HTTP_HOST'] ?? '');
}
/**
* 地址后拼接参数
* @param $url
* @param $parames
* @return mixed|string
*/
public static function urlAddparameter($url, $parames)
{
if ($parames && is_array($parames)) {
$array = [];
foreach ($parames as $key => $val) {
$array[] = $key . "=" . $val;
}
if ($array) {
$query = implode("&", $array);
if (str_contains($url, "?")) {
$url .= "&" . $query;
} else {
$url .= "?" . $query;
}
}
}
return $url;
}
/**
* 格式化内容图片地址
* @param $content
@@ -830,13 +888,16 @@ class Base
/**
* 数组只保留数字的
* @param $array
* @param bool $int 是否格式化值
* @return array
*/
public static function arrayRetainInt($array)
public static function arrayRetainInt($array, $int = false)
{
foreach ($array as $k => $v) {
if (!is_numeric($v)) {
unset($array[$k]);
} elseif ($int === true) {
$array[$k] = intval($v);
}
}
return array_values($array);
@@ -931,13 +992,16 @@ class Base
/**
* 检测邮箱格式
* @param string $str 需要检测的字符串
* @return int
* @param $str
* @return bool
*/
public static function isMail($str)
public static function isEmail($str)
{
$RegExp = '/^[a-z0-9][a-z\.0-9-_]+@[a-z0-9_-]+(?:\.[a-z]{0,3}\.[a-z]{0,2}|\.[a-z]{0,3}|\.[a-z]{0,2})$/i';
return preg_match($RegExp, $str);
if (filter_var($str, FILTER_VALIDATE_EMAIL)) {
return true;
} else {
return false;
}
}
/**
@@ -947,7 +1011,7 @@ class Base
*/
public static function isNumber($str)
{
if (preg_match("/^\d*$/", $str)) {
if (preg_match("/^\d+$/", $str)) {
return true;
} else {
return false;
@@ -1153,11 +1217,12 @@ class Base
/**
* 获取或设置
* @param $setname //配置名称
* @param bool $array //保存内容
* @param $setname // 配置名称
* @param bool $array // 保存内容
* @param false $isUpdate // 保存内容为更新模式,默认否
* @return array
*/
public static function setting($setname, $array = false)
public static function setting($setname, $array = false, $isUpdate = false)
{
global $_A;
if (empty($setname)) {
@@ -1168,15 +1233,19 @@ class Base
}
$setting = [];
$row = Setting::whereName($setname)->first();
if (!empty($row)) {
if ($row) {
$setting = Base::string2array($row->setting);
} else {
$row = Setting::createInstance(['name' => $setname]);
$row->save();
}
if ($array !== false) {
$setting = $array;
$row->updateInstance(['setting' => $array]);
if ($isUpdate && is_array($array)) {
$setting = array_merge($setting, $array);
} else {
$setting = $array;
}
$row->updateInstance(['setting' => $setting]);
$row->save();
}
$_A["__static_setting_" . $setname] = $setting;
@@ -1559,7 +1628,7 @@ class Base
}
/**
* 用户名、邮箱、手机号、银行卡号中间字符串以*隐藏
* 用户名、邮箱、手机号、银行卡号中间字符串以*隐藏
* @param $str
* @return string
*/
@@ -1658,24 +1727,46 @@ class Base
*/
public static function timeDiff($s, $e)
{
$d = $e - $s;
if ($d > 86400) {
$day = floor($d / 86400);
$hour = ceil(($d - ($day * 86400)) / 3600);
if ($hour > 0) {
return $day . '天' . $hour . '小时';
} else {
return $day . '天';
}
} elseif ($d > 3600) {
return ceil($d / 3600) . '小时';
} elseif ($d > 60) {
return ceil($d / 60) . '分钟';
} elseif ($d > 1) {
return '1分钟内';
} else {
return '0秒';
$time = $e - $s;
$days = 0;
if ($time >= 86400) { // 如果大于1天
$days = (int)($time / 86400);
$time = $time % 86400; // 计算天后剩余的毫秒数
}
$hours = 0;
if ($time >= 3600) { // 如果大于1小时
$hours = (int)($time / 3600);
$time = $time % 3600; // 计算小时后剩余的毫秒数
}
$minutes = ceil($time / 60); // 剩下的毫秒数都算作分
$daysStr = $days > 0 ? $days . '天' : '';
$hoursStr = ($hours > 0 || ($days > 0 && $minutes > 0)) ? $hours . '时' : '';
$minuteStr = ($minutes > 0) ? $minutes . '分' : '';
return $daysStr . $hoursStr . $minuteStr;
}
/**
* 时间秒数格式化
* @param int $time 时间秒数
* @return string
*/
public static function timeFormat($time)
{
$days = 0;
if ($time >= 86400) { // 如果大于1天
$days = (int)($time / 86400);
$time = $time % 86400; // 计算天后剩余的毫秒数
}
$hours = 0;
if ($time >= 3600) { // 如果大于1小时
$hours = (int)($time / 3600);
$time = $time % 3600; // 计算小时后剩余的毫秒数
}
$minutes = ceil($time / 60); // 剩下的毫秒数都算作分
$daysStr = $days > 0 ? $days . '天' : '';
$hoursStr = ($hours > 0 || ($days > 0 && $minutes > 0)) ? $hours . '时' : '';
$minuteStr = ($minutes > 0) ? $minutes . '分' : '';
return $daysStr . $hoursStr . $minuteStr;
}
/**
@@ -2083,9 +2174,42 @@ class Base
return Min(Max(Base::nullShow(Request::input($inputName), $default), 1), $max);
}
/**
* base64语音保存
* @param array $param [ base64=带前缀的base64, path=>文件路径 ]
* @return array [name=>文件名, size=>文件大小(单位KB),file=>绝对地址, path=>相对地址, url=>全路径地址, ext=>文件后缀名]
*/
public static function record64save($param)
{
$base64 = $param['base64'];
if (preg_match('/^(data:\s*audio\/(\w+);base64,)/', $base64, $res)) {
$extension = $res[2];
if (!in_array($extension, ['mp3', 'wav'])) {
return Base::retError('语音格式错误');
}
$fileName = 'record_' . md5($base64) . '.' . $extension;
$fileDir = $param['path'];
$filePath = public_path($fileDir);
Base::makeDir($filePath);
if (file_put_contents($filePath . $fileName, base64_decode(str_replace($res[1], '', $base64)))) {
$fileSize = filesize($filePath . $fileName);
$array = [
"name" => $fileName, //原文件名
"size" => Base::twoFloat($fileSize / 1024, true), //大小KB
"file" => $filePath . $fileName, //文件的完整路径 "D:\www....KzZ.jpg"
"path" => $fileDir . $fileName, //相对路径 "uploads/pic....KzZ.jpg"
"url" => Base::fillUrl($fileDir . $fileName), //完整的URL "https://.....hhsKzZ.jpg"
"ext" => $extension, //文件后缀名
];
return Base::retSuccess('success', $array);
}
}
return Base::retError('语音保存失败');
}
/**
* image64图片保存
* @param array $param [ image64=带前缀的base64, path=>文件路径, fileName=>文件名称, scale=>[压缩原图宽,高, 压缩方式] ]
* @param array $param [ image64=带前缀的base64, path=>文件路径, fileName=>文件名称, scale=>[压缩原图宽,高, 压缩方式], autoThumb=>false不要自动生成缩略图 ]
* @return array [name=>文件名, size=>文件大小(单位KB),file=>绝对地址, path=>相对地址, url=>全路径地址, ext=>文件后缀名]
*/
public static function image64save($param)
@@ -2105,7 +2229,7 @@ class Base
if ($width > 0 || $height > 0) {
$scaleName = "_{WIDTH}x{HEIGHT}";
if (isset($param['scale'][2])) {
$scaleName .= $param['scale'][2];
$scaleName .= "_c{$param['scale'][2]}";
}
}
}
@@ -2166,8 +2290,13 @@ class Base
}
//生成缩略图
$array['thumb'] = $array['path'];
if (Base::imgThumb($array['file'], $array['file'] . "_thumb.jpg", 180, 0)) {
$array['thumb'] .= "_thumb.jpg";
if ($extension === 'gif' && !isset($param['autoThumb'])) {
$param['autoThumb'] = false;
}
if ($param['autoThumb'] !== false) {
if (Base::imgThumb($array['file'], $array['file'] . "_thumb.jpg", 320, 0)) {
$array['thumb'] .= "_thumb.jpg";
}
}
$array['thumb'] = Base::fillUrl($array['thumb']);
return Base::retSuccess('success', $array);
@@ -2216,7 +2345,7 @@ class Base
$type = ['zip'];
break;
case 'file':
$type = ['jpg', 'jpeg', 'png', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'esp', 'pdf', 'rar', 'zip', 'gz'];
$type = ['jpg', 'jpeg', 'png', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'esp', 'pdf', 'rar', 'zip', 'gz', 'ai', 'avi', 'bmp', 'cdr', 'eps', 'mov', 'mp3', 'mp4', 'pr', 'psd', 'svg', 'tif'];
break;
case 'firmware':
$type = ['img', 'tar', 'bin'];
@@ -2224,20 +2353,33 @@ class Base
case 'md':
$type = ['md'];
break;
case 'desktop':
$type = ['yml', 'yaml', 'dmg', 'pkg', 'blockmap', 'zip', 'exe', 'msi'];
break;
case 'more':
$type = [
'text', 'md', 'markdown',
'drawio',
'mind',
'docx', 'wps', 'doc', 'xls', 'xlsx', 'ppt', 'pptx',
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw',
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z',
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw', 'svg',
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z', 'gz', 'apk', 'dmg',
'tif', 'tiff',
'dwg', 'dxf',
'ofd',
'pdf',
'txt',
'html', 'htm', 'asp', 'jsp', 'xml', 'json', 'properties', 'md', 'gitignore', 'log', 'java', 'py', 'c', 'cpp', 'sql', 'sh', 'bat', 'm', 'bas', 'prg', 'cmd',
'php', 'go', 'python', 'js', 'ftl', 'css', 'lua', 'rb', 'yaml', 'yml', 'h', 'cs', 'aspx',
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css',
'dockerfile', 'go', 'golang', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
'ocamlmakefile', 'make', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php',
'inc', 'phtml', 'shtml', 'php3', 'php4', 'php5', 'phps', 'phpt', 'aw', 'ctp', 'module', 'ps1', 'py', 'r', 'rb', 'ru', 'gemspec', 'rake', 'guardfile', 'rakefile',
'gemfile', 'rs', 'sass', 'scss', 'sh', 'bash', 'bashrc', 'sql', 'sqlserver', 'swift', 'ts', 'typescript', 'str', 'vbs', 'vb', 'v', 'vh', 'sv', 'svh', 'xml',
'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx', 'plist',
'mp3', 'wav', 'mp4', 'flv',
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm',
'xmind',
'rp',
];
break;
default:
@@ -2252,11 +2394,13 @@ class Base
if ($param['size'] > 0 && $fileSize > $param['size'] * 1024) {
return Base::retError('文件大小超限,最大限制:' . $param['size'] . 'KB');
}
} catch (Exception $e) {
} catch (\Throwable) {
$fileSize = 0;
}
$scaleName = "";
if ($param['fileName']) {
if ($param['fileName'] === true) {
$fileName = $file->getClientOriginalName();
} elseif ($param['fileName']) {
$fileName = $param['fileName'];
} else {
if ($param['scale'] && is_array($param['scale'])) {
@@ -2264,7 +2408,7 @@ class Base
if ($width > 0 || $height > 0) {
$scaleName = "_{WIDTH}x{HEIGHT}";
if (isset($param['scale'][2])) {
$scaleName .= $param['scale'][2];
$scaleName .= "_c{$param['scale'][2]}";
}
}
}
@@ -2344,7 +2488,9 @@ class Base
}
//生成缩略图
$array['thumb'] = $array['path'];
if ($param['autoThumb'] === "false") $param['autoThumb'] = false;
if ($extension === 'gif' && !isset($param['autoThumb'])) {
$param['autoThumb'] = false;
}
if ($param['autoThumb'] !== false) {
if (Base::imgThumb($array['file'], $array['file'] . "_thumb.jpg", 320, 0)) {
$array['thumb'] .= "_thumb.jpg";
@@ -2359,6 +2505,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 源图绝对完整地址{带文件名及后缀名}
@@ -2476,7 +2653,7 @@ class Base
try {
$white = imagecolorallocate($dst, 255, 255, 255);
imagefill($dst, 0, 0, $white);
} catch (Exception $e) {
} catch (\Throwable) {
}
if (function_exists('imagecopyresampled')) {
@@ -2630,16 +2807,19 @@ class Base
/**
* 遍历获取文件
* @param $dir
* @param bool $subdirectory 是否遍历子目录
* @return array
*/
public static function readDir($dir)
public static function readDir($dir, $subdirectory = true)
{
$files = array();
$dir_list = scandir($dir);
foreach ($dir_list as $file) {
if ($file != '..' && $file != '.') {
if (is_dir($dir . '/' . $file)) {
$files = array_merge($files, self::readDir($dir . '/' . $file));
if ($subdirectory) {
$files = array_merge($files, self::readDir($dir . '/' . $file, $subdirectory));
}
} else {
$files[] = $dir . "/" . $file;
}
@@ -2658,36 +2838,34 @@ class Base
if (empty($str)) {
return '';
}
$fchar = ord($str[0]);
if ($fchar >= ord('A') && $fchar <= ord('z')) return strtoupper($str[0]);
$s1 = iconv('UTF-8', 'gb2312', $str);
$s2 = iconv('gb2312', 'UTF-8', $s1);
$s = $s2 == $str ? $s1 : $str;
$asc = ord($s[0]) * 256 + ord($s[1]) - 65536;
if ($asc >= -20319 && $asc <= -20284) return 'A';
if ($asc >= -20283 && $asc <= -19776) return 'B';
if ($asc >= -19775 && $asc <= -19219) return 'C';
if ($asc >= -19218 && $asc <= -18711) return 'D';
if ($asc >= -18710 && $asc <= -18527) return 'E';
if ($asc >= -18526 && $asc <= -18240) return 'F';
if ($asc >= -18239 && $asc <= -17923) return 'G';
if ($asc >= -17922 && $asc <= -17418) return 'H';
if ($asc >= -17417 && $asc <= -16475) return 'J';
if ($asc >= -16474 && $asc <= -16213) return 'K';
if ($asc >= -16212 && $asc <= -15641) return 'L';
if ($asc >= -15640 && $asc <= -15166) return 'M';
if ($asc >= -15165 && $asc <= -14923) return 'N';
if ($asc >= -14922 && $asc <= -14915) return 'O';
if ($asc >= -14914 && $asc <= -14631) return 'P';
if ($asc >= -14630 && $asc <= -14150) return 'Q';
if ($asc >= -14149 && $asc <= -14091) return 'R';
if ($asc >= -14090 && $asc <= -13319) return 'S';
if ($asc >= -13318 && $asc <= -12839) return 'T';
if ($asc >= -12838 && $asc <= -12557) return 'W';
if ($asc >= -12556 && $asc <= -11848) return 'X';
if ($asc >= -11847 && $asc <= -11056) return 'Y';
if ($asc >= -11055 && $asc <= -10247) return 'Z';
return '#';
$first = mb_substr($str, 0, 1);
if (preg_match("/^\d$/", $first)) {
return '#';
}
if (!preg_match("/^[a-zA-Z]$/", $first)) {
$pinyin = new Pinyin();
$first = $pinyin->abbr($first, '', PINYIN_NAME);
}
return $first ? strtoupper($first) : '#';
}
/**
* 中文转拼音
* @param $str
* @return string
*/
public static function cn2pinyin($str)
{
if (empty($str)) {
return '';
}
if (!preg_match("/^[a-zA-Z0-9_.]+$/", $str)) {
$str = Cache::rememberForever("cn2pinyin:" . md5($str), function() use ($str) {
$pinyin = new Pinyin();
return $pinyin->permalink($str, '');
});
}
return $str;
}
/**
@@ -2868,4 +3046,54 @@ class Base
$matrix = array_unique($matrix, SORT_REGULAR);
return array_merge($matrix);
}
/**
* 字节转格式
* @param $bytes
* @return string
*/
public static function readableBytes($bytes)
{
$i = floor(log($bytes) / log(1024));
$sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
return sprintf('%.02F', $bytes / pow(1024, $i)) * 1 . ' ' . $sizes[$i];
}
/**
* 去除emoji表情
* @param $str
* @return string|string[]|null
*/
public static function filterEmoji($str)
{
return preg_replace_callback(
'/./u',
function (array $match) {
return strlen($match[0]) >= 4 ? '' : $match[0];
},
$str);
}
/**
* 统一验证器
* @param $data
* @param $messages
*/
public static function validator($data, $messages) {
$rules = [];
foreach ($messages as $key => $item) {
$keys = explode(".", $key);
if (isset($keys[1])) {
if (isset($rules[$keys[0]])) {
$rules[$keys[0]] = $rules[$keys[0]] . '|' . $keys[1];
} else {
$rules[$keys[0]] = $keys[1];
}
}
}
$validator = Validator::make($data, $rules, $messages);
if ($validator->fails()) {
throw new ApiException($validator->errors()->first());
}
}
}

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

@@ -2,8 +2,6 @@
namespace App\Module;
use Exception;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
class Ihttp
@@ -88,7 +86,7 @@ class Ihttp
return Base::retError($error);
} else {
if ($isGb2312) {
try { $data = iconv('GB2312', 'UTF-8', $data); }catch (Exception $e) { }
try { $data = iconv('GB2312', 'UTF-8', $data); }catch (\Throwable) { }
}
$response = self::ihttp_response_parse($data);
Base::addLog([
@@ -148,7 +146,7 @@ class Ihttp
$content .= fgets($fp, 512);
fclose($fp);
if ($isGb2312) {
try { $content = iconv('GB2312', 'UTF-8', $content); }catch (Exception $e) { }
try { $content = iconv('GB2312', 'UTF-8', $content); }catch (\Throwable) { }
}
$response = self::ihttp_response_parse($content, true);
Base::addLog([

390
app/Module/RandomColor.php Normal file
View File

@@ -0,0 +1,390 @@
<?php
/**
* RandomColor 1.0.5
*
* PHP port of David Merfield JavaScript randomColor
* https://github.com/davidmerfield/randomColor
*
*
* The MIT License (MIT)
*
* Copyright (c) 2014-2022 Damien "Mistic" Sorel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace App\Module;
class RandomColor
{
static public $dictionary = null;
private function __construct() {}
static public function one($options = array())
{
$h = self::_pickHue($options);
$s = self::_pickSaturation($h, $options);
$v = self::_pickBrightness($h, $s, $options);
return self::format(compact('h','s','v'), @$options['format']);
}
static public function many($count, $options = array())
{
$colors = array();
for ($i = 0; $i < $count; $i++)
{
$colors[] = self::one($options);
}
return $colors;
}
static public function format($hsv, $format='hex')
{
switch ($format)
{
case 'hsv':
return $hsv;
case 'hsl':
return self::hsv2hsl($hsv);
case 'hslCss':
$hsl = self::hsv2hsl($hsv);
return 'hsl(' . $hsl['h'] . ',' . $hsl['s'] . '%,' . $hsl['l'] . '%)';
case 'rgb':
return self::hsv2rgb($hsv);
case 'rgbCss':
return 'rgb(' . implode(',', self::hsv2rgb($hsv)) . ')';
case 'hex':
default:
return self::hsv2hex($hsv);
}
}
static private function _pickHue($options)
{
$range = self::_getHueRange($options);
if (empty($range))
{
return 0;
}
$hue = self::_rand($range, $options);
// Instead of storing red as two separate ranges,
// we group them, using negative numbers
if ($hue < 0)
{
$hue = 360 + $hue;
}
return $hue;
}
static private function _pickSaturation($h, $options)
{
if (@$options['hue'] === 'monochrome')
{
return 0;
}
if (@$options['luminosity'] === 'random')
{
return self::_rand(array(0, 100), $options);
}
$colorInfo = self::_getColorInfo($h);
$range = $colorInfo['s'];
switch (@$options['luminosity'])
{
case 'bright':
$range[0] = 55;
break;
case 'dark':
$range[0] = $range[1] - 10;
break;
case 'light':
$range[1] = 55;
break;
}
return self::_rand($range, $options);
}
static private function _pickBrightness($h, $s, $options)
{
if (@$options['luminosity'] === 'random')
{
$range = array(0, 100);
}
else
{
$range = array(
self::_getMinimumBrightness($h, $s),
100
);
switch (@$options['luminosity'])
{
case 'dark':
$range[1] = $range[0] + 20;
break;
case 'light':
$range[0] = round(($range[1] + $range[0]) / 2);
break;
}
}
return self::_rand($range, $options);
}
static private function _getHueRange($options)
{
$ranges = array();
if (isset($options['hue']))
{
if (!is_array($options['hue']))
{
$options['hue'] = array($options['hue']);
}
foreach ($options['hue'] as $hue)
{
if ($hue === 'random')
{
$ranges[] = array(0, 360);
}
else if (isset(self::$dictionary[$hue]))
{
$ranges[] = self::$dictionary[$hue]['h'];
}
else if (is_numeric($hue))
{
$hue = intval($hue);
if ($hue <= 360 && $hue >= 0)
{
$ranges[] = array($hue, $hue);
}
}
}
}
if (($l = count($ranges)) === 0)
{
return array(0, 360);
}
else if ($l === 1)
{
return $ranges[0];
}
else
{
return $ranges[self::_rand(array(0, $l-1), $options)];
}
}
static private function _getMinimumBrightness($h, $s)
{
$colorInfo = self::_getColorInfo($h);
$bounds = $colorInfo['bounds'];
for ($i = 0, $l = count($bounds); $i < $l - 1; $i++)
{
$s1 = $bounds[$i][0];
$v1 = $bounds[$i][1];
$s2 = $bounds[$i+1][0];
$v2 = $bounds[$i+1][1];
if ($s >= $s1 && $s <= $s2)
{
$m = ($v2 - $v1) / ($s2 - $s1);
$b = $v1 - $m * $s1;
return round($m * $s + $b);
}
}
return 0;
}
static private function _getColorInfo($h)
{
// Maps red colors to make picking hue easier
if ($h >= 334 && $h <= 360)
{
$h-= 360;
}
foreach (self::$dictionary as $color)
{
if ($color['h'] !== null && $h >= $color['h'][0] && $h <= $color['h'][1])
{
return $color;
}
}
}
static private function _rand($bounds, $options)
{
if (isset($options['prng']))
{
return $options['prng']($bounds[0], $bounds[1]);
}
else
{
return mt_rand($bounds[0], $bounds[1]);
}
}
static public function hsv2hex($hsv)
{
$rgb = self::hsv2rgb($hsv);
$hex = '#';
foreach ($rgb as $c)
{
$hex.= str_pad(dechex($c), 2, '0', STR_PAD_LEFT);
}
return $hex;
}
static public function hsv2hsl($hsv)
{
extract($hsv);
$s/= 100;
$v/= 100;
$k = (2-$s)*$v;
return array(
'h' => $h,
's' => round($s*$v / ($k < 1 ? $k : 2-$k), 4) * 100,
'l' => $k/2 * 100,
);
}
static public function hsv2rgb($hsv)
{
extract($hsv);
$h/= 360;
$s/= 100;
$v/= 100;
$i = floor($h * 6);
$f = $h * 6 - $i;
$m = $v * (1 - $s);
$n = $v * (1 - $s * $f);
$k = $v * (1 - $s * (1 - $f));
$r = 1;
$g = 1;
$b = 1;
switch ($i)
{
case 0:
list($r,$g,$b) = array($v,$k,$m);
break;
case 1:
list($r,$g,$b) = array($n,$v,$m);
break;
case 2:
list($r,$g,$b) = array($m,$v,$k);
break;
case 3:
list($r,$g,$b) = array($m,$n,$v);
break;
case 4:
list($r,$g,$b) = array($k,$m,$v);
break;
case 5:
case 6:
list($r,$g,$b) = array($v,$m,$n);
break;
}
return array(
'r' => floor($r*255),
'g' => floor($g*255),
'b' => floor($b*255),
);
}
}
/*
* h=hueRange
* s=saturationRange : bounds[0][0] ; bounds[-][0]
*/
RandomColor::$dictionary = array(
'monochrome' => array(
'bounds' => array(array(0,0), array(100,0)),
'h' => NULL,
's' => array(0,100)
),
'red' => array(
'bounds' => array(array(20,100),array(30,92),array(40,89),array(50,85),array(60,78),array(70,70),array(80,60),array(90,55),array(100,50)),
'h' => array(-26,18),
's' => array(20,100)
),
'orange' => array(
'bounds' => array(array(20,100),array(30,93),array(40,88),array(50,86),array(60,85),array(70,70),array(100,70)),
'h' => array(19,46),
's' => array(20,100)
),
'yellow' => array(
'bounds' => array(array(25,100),array(40,94),array(50,89),array(60,86),array(70,84),array(80,82),array(90,80),array(100,75)),
'h' => array(47,62),
's' => array(25,100)
),
'green' => array(
'bounds' => array(array(30,100),array(40,90),array(50,85),array(60,81),array(70,74),array(80,64),array(90,50),array(100,40)),
'h' => array(63,178),
's' => array(30,100)
),
'blue' => array(
'bounds' => array(array(20,100),array(30,86),array(40,80),array(50,74),array(60,60),array(70,52),array(80,44),array(90,39),array(100,35)),
'h' => array(179,257),
's' => array(20,100)
),
'purple' => array(
'bounds' => array(array(20,100),array(30,87),array(40,79),array(50,70),array(60,65),array(70,59),array(80,52),array(90,45),array(100,42)),
'h' => array(258,282),
's' => array(20,100)
),
'pink' => array(
'bounds' => array(array(20,100),array(30,90),array(40,86),array(60,84),array(80,80),array(90,75),array(100,73)),
'h' => array(283,334),
's' => array(20,100)
)
);

View File

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

View File

@@ -69,7 +69,7 @@ class WebSocketService implements WebSocketHandlerInterface
$server->push($fd, Base::array2json([
'type' => 'error',
'data' => [
'error' => '员不存在'
'error' => '员不存在'
],
]));
$server->close($fd);
@@ -127,9 +127,11 @@ class WebSocketService implements WebSocketHandlerInterface
case 'readMsg':
$ids = is_array($data['id']) ? $data['id'] : [$data['id']];
$userid = $this->getUserid($frame->fd);
$list = WebSocketDialogMsg::whereIn('id', $ids)->get();
$list->transform(function(WebSocketDialogMsg $item) use ($userid) {
$item->readSuccess($userid);
WebSocketDialogMsg::whereIn('id', $ids)->chunkById(20, function($list) use ($userid) {
/** @var WebSocketDialogMsg $item */
foreach ($list as $item) {
$item->readSuccess($userid);
}
});
return;
@@ -143,10 +145,10 @@ class WebSocketService implements WebSocketHandlerInterface
$pathOld = $row->path;
$row->path = $pathNew;
$row->save();
if (preg_match("/^file\/content\/\d+$/", $pathOld)) {
if (preg_match("/^\/single\/file\/\d+$/", $pathOld)) {
$this->pushPath($pathOld);
}
if (preg_match("/^file\/content\/\d+$/", $pathNew)) {
if (preg_match("/^\/single\/file\/\d+$/", $pathNew)) {
$this->pushPath($pathNew);
}
}
@@ -204,7 +206,25 @@ class WebSocketService implements WebSocketHandlerInterface
*/
private function deleteUser($fd)
{
WebSocket::whereFd($fd)->delete();
$array = [];
WebSocket::whereFd($fd)->chunk(10, function($list) use (&$array) {
/** @var WebSocket $item */
foreach ($list as $item) {
$item->delete();
if ($item->userid) {
// 离线时更新会员最后在线时间
User::whereUserid($item->userid)->update([
'line_at' => Carbon::now()
]);
}
if ($item->path && str_starts_with($item->path, "/single/file/")) {
$array[$item->path] = $item->path;
}
}
});
foreach ($array as $path) {
$this->pushPath($path);
}
}
/**

View File

@@ -27,7 +27,7 @@ abstract class AbstractTask extends Task
{
try {
$this->start();
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->info($e);
$this->failed($e);
}

View File

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

View File

@@ -0,0 +1,274 @@
<?php
namespace App\Tasks;
use App\Models\ProjectTask;
use App\Models\ProjectTaskMailLog;
use App\Models\Setting;
use App\Models\User;
use App\Models\WebSocketDialogMsg;
use App\Models\WebSocketDialogMsgRead;
use App\Module\Base;
use Carbon\Carbon;
use Guanguans\Notify\Factory;
use Guanguans\Notify\Messages\EmailMessage;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
class EmailNoticeTask extends AbstractTask
{
public function __construct()
{
//
}
public function start()
{
$setting = Base::setting('emailSetting');
// 任务通知
if ($setting['notice'] === 'open') {
$start = intval($setting['task_start_minute']);
$hours = floatval($setting['task_remind_hours']);
$hours2 = floatval($setting['task_remind_hours2']);
if ($start > -1) {
ProjectTask::whereNull("complete_at")
->whereNull("archived_at")
->whereBetween("start_at", [
Carbon::now()->subMinutes($start + 10),
Carbon::now()->subMinutes($start)
])->chunkById(100, function ($tasks) {
/** @var ProjectTask $task */
foreach ($tasks as $task) {
$this->taskEmail($task, 0);
}
});
}
if ($hours > -1) {
ProjectTask::whereNull("complete_at")
->whereNull("archived_at")
->whereBetween("end_at", [
Carbon::now()->addMinutes($hours * 60),
Carbon::now()->addMinutes($hours * 60 + 10)
])->chunkById(100, function ($tasks) {
/** @var ProjectTask $task */
foreach ($tasks as $task) {
$this->taskEmail($task, 1);
}
});
}
if ($hours2 > -1) {
ProjectTask::whereNull("complete_at")
->whereNull("archived_at")
->whereBetween("end_at", [
Carbon::now()->subMinutes($hours2 * 60 + 10),
Carbon::now()->subMinutes($hours2 * 60)
])->chunkById(100, function ($tasks) {
/** @var ProjectTask $task */
foreach ($tasks as $task) {
$this->taskEmail($task, 2);
}
});
}
}
// 消息通知
if ($setting['notice_msg'] === 'open') {
$userMinute = intval($setting['msg_unread_user_minute']);
$groupMinute = intval($setting['msg_unread_group_minute']);
\DB::statement("SET SQL_MODE=''");
$builder = WebSocketDialogMsg::select(['web_socket_dialog_msgs.*', 'r.id as r_id', 'r.userid as r_userid'])
->join('web_socket_dialog_msg_reads as r', 'web_socket_dialog_msgs.id', '=', 'r.msg_id')
->whereNull("r.read_at")
->where("r.email", 0);
if ($userMinute > -1) {
$builder->clone()
->where("web_socket_dialog_msgs.dialog_type", "user")
->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"])
->whereBetween("web_socket_dialog_msgs.created_at", [
Carbon::now()->subMinutes($userMinute + 10),
Carbon::now()->subMinutes($userMinute)
])
->groupBy('r_userid')
->chunkById(100, function ($rows) {
$this->unreadMsgEmail($rows, "user");
});
}
if ($groupMinute > -1) {
$builder->clone()
->where("web_socket_dialog_msgs.dialog_type", "group")
->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"])
->whereBetween("web_socket_dialog_msgs.created_at", [
Carbon::now()->subMinutes($groupMinute + 10),
Carbon::now()->subMinutes($groupMinute)
])
->groupBy('r_userid')
->chunkById(100, function ($rows) {
$this->unreadMsgEmail($rows, "group");
});
}
}
}
/**
* 任务过期前、超期后提醒
* @param ProjectTask $task
* @param int $type
* @return void
*/
private function taskEmail(ProjectTask $task, int $type)
{
$userids = $task->taskUser->where('owner', 1)->pluck('userid')->toArray();
if (empty($userids)) {
return;
}
$users = User::whereIn('userid', $userids)->get();
if (empty($users)) {
return;
}
$setting = Base::setting('emailSetting');
/** @var User $user */
foreach ($users as $user) {
$data = [
'type' => $type,
'userid' => $user->userid,
'task_id' => $task->id,
];
$emailLog = ProjectTaskMailLog::where($data)->exists();
if ($emailLog) {
continue;
}
try {
if (!Base::isEmail($user->email)) {
throw new \Exception("User email '{$user->email}' address error");
}
$subject = match ($type) {
1 => "任务提醒",
2 => "任务过期提醒",
default => "任务开始提醒",
};
$content = view('email.task', [
'type' => str_replace([0, 1, 2], ['start', 'before', 'after'], $type),
'user' => $user,
'task' => $task,
'setting' => $setting,
])->render();
Setting::validateAddr($user->email, function($to) use ($content, $subject, $setting) {
Factory::mailer()
->setDsn("smtp://{$setting['account']}:{$setting['password']}@{$setting['smtp_server']}:{$setting['port']}?verify_peer=0")
->setMessage(EmailMessage::create()
->from(env('APP_NAME', 'Task') . " <{$setting['account']}>")
->to($to)
->subject($subject)
->html($content))
->send();
});
$data['is_send'] = 1;
} catch (\Throwable $e) {
$data['send_error'] = $e->getMessage();
}
$data['email'] = $user->email;
ProjectTaskMailLog::createInstance($data)->save();
}
}
/**
* 未读消息通知
* @param $rows
* @param $dialogType
* @return void
*/
private function unreadMsgEmail($rows, $dialogType)
{
$array = $rows->groupBy('r_userid');
foreach ($array as $userid => $data) {
$data = WebSocketDialogMsg::select(['web_socket_dialog_msgs.*', 'r.id as r_id', 'r.userid as r_userid'])
->join('web_socket_dialog_msg_reads as r', 'web_socket_dialog_msgs.id', '=', 'r.msg_id')
->whereNull("r.read_at")
->where("r.email", 0)
->where("r.userid", $userid)
->where("web_socket_dialog_msgs.dialog_type", $dialogType)
->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"])
->take(100)
->get();
if (empty($data)) {
continue;
}
$user = User::find($userid);
if (empty($user)) {
continue;
}
if (!Base::isEmail($user->email)) {
continue;
}
$setting = Base::setting('emailSetting');
$msgType = $dialogType === "group" ? "群聊" : "成员";
$subject = null;
$content = view('email.unread', [
'type' => 'head',
'nickname' => $user->nickname,
'msgType' => $msgType,
'count' => count($data),
])->render();
$lists = $data->groupBy('dialog_id');
/** @var WebSocketDialogMsg[] $items */
foreach ($lists as $items) {
$dialogId = 0;
$dialogName = null;
foreach ($items as $item) {
$item->cancelAppend();
$item->userInfo = User::userid2basic($item->userid);
$item->preview = $item->previewMsg(true);
$item->preview = str_replace('<p>', '<p style="margin:0;padding:0">', $item->preview);
if (empty($dialogId)) {
$dialogId = $item->dialog_id;
}
if ($dialogName === null) {
if ($dialogType === "user" && $item->userInfo) {
if ($item->userInfo->profession) {
$dialogName = $item->userInfo->nickname . " ({$item->userInfo->profession})";
} else {
$dialogName = $item->userInfo->nickname;
}
} else {
$dialogName = $item->webSocketDialog?->getGroupName();
}
}
}
if ($subject === null) {
$count = count($lists);
if ($count > 1) {
$subject = "来自{$count}{$msgType}未读消息提醒";
} else {
$subject = "来自{$dialogName}未读消息提醒";
}
}
$content .= view('email.unread', [
'type' => 'content',
'dialogUrl' => config("app.url") . "/manage/messenger?dialog_id={$dialogId}",
'dialogName' => $dialogName,
'unread' => count($items),
'items' => $items,
])->render();
$content = str_replace("{{RemoteURL}}", config("app.url") . "/", $content);
}
try {
Setting::validateAddr($user->email, function($to) use ($content, $subject, $setting) {
Factory::mailer()
->setDsn("smtp://{$setting['account']}:{$setting['password']}@{$setting['smtp_server']}:{$setting['port']}?verify_peer=0")
->setMessage(EmailMessage::create()
->from(env('APP_NAME', 'Task') . " <{$setting['account']}>")
->to($to)
->subject($subject)
->html($content))
->send();
});
} catch (\Throwable $e) {
info("unreadMsgEmail: " . $e->getMessage());
}
WebSocketDialogMsgRead::whereIn('id', $data->pluck('r_id'))->update([
'email' => 1
]);
}
}
}

79
app/Tasks/LoopTask.php Normal file
View File

@@ -0,0 +1,79 @@
<?php
namespace App\Tasks;
use App\Models\ProjectFlow;
use App\Models\ProjectFlowItem;
use App\Models\ProjectTask;
use App\Models\ProjectTaskUser;
use Carbon\Carbon;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
/**
* 任务重复周期
*/
class LoopTask extends AbstractTask
{
public function __construct()
{
}
public function start()
{
ProjectTask::whereBetween('loop_at', [
Carbon::now()->subMinutes(10),
Carbon::now()
])->chunkById(100, function ($list) {
/** @var ProjectTask $item */
foreach ($list as $item) {
try {
$task = $item->copyTask();
// 工作流
$projectFlow = ProjectFlow::whereProjectId($task->project_id)->orderByDesc('id')->first();
if ($projectFlow) {
$projectFlowItem = ProjectFlowItem::whereFlowId($projectFlow->id)->orderBy('sort')->get();
// 赋一个开始状态
foreach ($projectFlowItem as $flowItem) {
if ($flowItem->status == 'start') {
$task->flow_item_id = $flowItem->id;
$task->flow_item_name = $flowItem->status . "|" . $flowItem->name;
if ($flowItem->userids) {
$userids = array_values(array_unique($flowItem->userids));
foreach ($userids as $uid) {
ProjectTaskUser::updateInsert([
'task_id' => $task->id,
'userid' => $uid,
], [
'project_id' => $task->project_id,
'task_pid' => $task->id,
'owner' => 1,
]);
}
}
break;
}
}
}
// 新任务时间、周期
if ($task->start_at) {
$diffSecond = Carbon::parse($task->start_at)->diffInSeconds(Carbon::parse($task->end_at), true);
$task->start_at = Carbon::parse($task->loop_at);
$task->end_at = $task->start_at->clone()->addSeconds($diffSecond);
}
$task->refreshLoop(true);
$task->addLog("创建任务来自周期任务ID{$item->id}", [], $task->userid);
// 清空旧周期
$item->loop = '';
$item->loop_at = null;
$item->save();
$item->addLog("已创建新的周期任务ID{$task->id},此任务关闭周期", [], $task->userid);
} catch (\Throwable $e) {
$item->addLog("生成重复任务失败:" . $e->getMessage(), [], $item->userid);
}
}
});
}
}

View File

@@ -172,7 +172,7 @@ class PushTask extends AbstractTask
try {
$swoole->push($fid, Base::array2json($msg));
$tmpMsgId > 0 && WebSocketTmpMsg::whereId($tmpMsgId)->update(['send' => 1]);
} catch (\Exception $e) {
} catch (\Throwable) {
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Tasks;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
use App\Models\UmengAlias;
use App\Module\Base;
/**
* 推送友盟消息
*/
class PushUmengMsg extends AbstractTask
{
protected $userid = 0;
protected $array = [];
/**
* @param array|int $userid
* @param array $array
*/
public function __construct($userid, $array = [])
{
$this->userid = $userid;
$this->array = is_array($array) ? $array : [];
}
public function start()
{
if (empty($this->userid) || empty($this->array)) {
return;
}
$setting = Base::setting('appPushSetting');
if ($setting['push'] !== 'open') {
return;
}
UmengAlias::pushMsgToUserid($this->userid, $this->array);
}
}

View File

@@ -8,11 +8,12 @@ use App\Models\User;
use App\Models\WebSocketDialog;
use App\Models\WebSocketDialogMsg;
use App\Models\WebSocketDialogMsgRead;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Request;
/**
* 推送话消息
* 推送话消息
* Class WebSocketDialogMsgTask
* @package App\Tasks
*/
@@ -20,21 +21,59 @@ class WebSocketDialogMsgTask extends AbstractTask
{
protected $id;
protected $ignoreFd;
protected $msgNotExistRetry = false; // 推送失败后重试
protected $silence = false; // 静默推送1:前端不通知、2:App不推送
/**
* WebSocketDialogMsgTask constructor.
* @param int $id 消息ID
* @param mixed $ignoreFd
*/
public function __construct($id)
public function __construct($id, $ignoreFd = null)
{
$this->id = $id;
$this->ignoreFd = Request::header('fd');
$this->ignoreFd = $ignoreFd === null ? Request::header('fd') : $ignoreFd;
}
/**
* @param $ignoreFd
*/
public function setIgnoreFd($ignoreFd)
{
$this->ignoreFd = $ignoreFd;
}
/**
* @param bool $msgNotExistRetry
*/
public function setMsgNotExistRetry(bool $msgNotExistRetry): void
{
$this->msgNotExistRetry = $msgNotExistRetry;
}
/**
* @param bool $silence
*/
public function setSilence(bool $silence): void
{
$this->silence = $silence;
}
public function start()
{
global $_A;
$_A = [
'__fill_url_remote_url' => true,
];
//
$msg = WebSocketDialogMsg::find($this->id);
if (empty($msg)) {
if ($this->msgNotExistRetry) {
$task = new WebSocketDialogMsgTask($this->id, $this->ignoreFd || '');
$task->delay(1);
$this->addTask($task);
}
return;
}
$dialog = WebSocketDialog::find($msg->dialog_id);
@@ -42,31 +81,81 @@ class WebSocketDialogMsgTask extends AbstractTask
return;
}
// 推送目标①:群成
// 提及会
$mentions = [];
if ($msg->type === 'text') {
preg_match_all("/<span class=\"mention user\" data-id=\"(\d+)\">/", $msg->msg['text'], $matchs);
if ($matchs) {
$mentions = array_values(array_filter(array_unique($matchs[1])));
}
}
// 将会话以外的成员加入会话内
$userids = $dialog->dialogUser->pluck('userid')->toArray();
$diffids = array_values(array_diff($mentions, $userids));
if ($diffids) {
// 仅(群聊)且(是群主或没有群主)才可以@成员以外的人
if ($dialog->type === 'group' && in_array($dialog->owner_id, [0, $msg->userid])) {
$dialog->joinGroup($diffids, $msg->userid);
$dialog->pushMsg("groupJoin", null, $diffids);
$userids = array_values(array_unique(array_merge($mentions, $userids)));
}
}
// 推送目标①:会话成员/群成员
$array = [];
foreach ($userids AS $userid) {
if ($userid == $msg->userid) {
continue;
$array[$userid] = false;
} else {
$mention = array_intersect([0, $userid], $mentions) ? 1 : 0;
WebSocketDialogMsgRead::createInstance([
'dialog_id' => $msg->dialog_id,
'msg_id' => $msg->id,
'userid' => $userid,
'mention' => $mention,
])->saveOrIgnore();
$array[$userid] = $mention;
}
WebSocketDialogMsgRead::createInstance([
'dialog_id' => $msg->dialog_id,
'msg_id' => $msg->id,
'userid' => $userid,
])->saveOrIgnore();
}
// 更新已发送数量
$msg->send = WebSocketDialogMsgRead::whereMsgId($msg->id)->count();
$msg->save();
// 开始推送消息
PushTask::push([
'userid' => $userids,
'ignoreFd' => $this->ignoreFd,
'msg' => [
'type' => 'dialog',
'mode' => 'add',
'data' => $msg->toArray(),
]
]);
foreach ($array as $userid => $mention) {
PushTask::push([
'userid' => $userid,
'ignoreFd' => $this->ignoreFd,
'msg' => [
'type' => 'dialog',
'mode' => 'add',
'silence' => $this->silence ? 1 : 0,
'data' => array_merge($msg->toArray(), [
'mention' => $mention,
]),
]
]);
}
// umeng推送app
if (!$this->silence) {
$umengUserid = $array;
if (isset($umengUserid[$msg->userid])) {
unset($umengUserid[$msg->userid]);
}
$umengUserid = array_keys($umengUserid);
$umengTitle = User::userid2nickname($msg->userid);
if ($dialog->type == 'group') {
$umengTitle = "{$dialog->getGroupName()} ($umengTitle)";
}
$umengMsg = new PushUmengMsg($umengUserid, [
'title' => $umengTitle,
'body' => $msg->previewMsg(),
'description' => "MID:{$msg->id}",
'seconds' => 3600,
'badge' => 1,
]);
Task::deliver($umengMsg);
}
// 推送目标②:正在打开这个任务会话的会员
if ($dialog->type == 'group' && $dialog->group_type == 'task') {
@@ -85,6 +174,7 @@ class WebSocketDialogMsgTask extends AbstractTask
'msg' => [
'type' => 'dialog',
'mode' => 'chat',
'silence' => $this->silence ? 1 : 0,
'data' => $msg->toArray(),
]
]);

60
cliff.toml Normal file
View File

@@ -0,0 +1,60 @@
# configuration file for git-cliff (0.1.0)
[changelog]
# changelog header
header = """
# Changelog\n
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://tera.netlify.app/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}]
{% else %}\
## [Unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
footer = """
"""
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^doc", group = "Documentation"},
{ message = "^perf", group = "Performance"},
{ message = "^pref", group = "Performance"},
{ message = "^refactor", group = "Refactor"},
{ message = "^style", group = "Styling"},
{ message = "^test", group = "Testing"},
{ message = "^chore\\(release\\): prepare for", skip = true},
{ message = "^chore", group = "Miscellaneous Tasks"},
{ body = ".*security", group = "Security"},
]
# filter out the commits that are not matched by commit parsers
filter_commits = true
# glob pattern for matching git tags
tag_pattern = "v[0-9]*"
# regex for skipping tags
skip_tags = "v0.1.0-beta.1"
# regex for ignoring tags
ignore_tags = ""
# sort the tags chronologically
date_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"

180
cmd
View File

@@ -12,6 +12,8 @@ OK="${Green}[OK]${Font}"
Error="${Red}[错误]${Font}"
cur_path="$(pwd)"
cur_arg=$@
COMPOSE="docker-compose"
judge() {
if [[ 0 -eq $? ]]; then
@@ -30,6 +32,15 @@ rand() {
echo $(($num%$max+$min))
}
rand_string() {
local lan=$1
if [[ `uname` == 'Linux' ]]; then
echo "$(date +%s%N | md5sum | cut -c 1-${lan})"
else
echo "$(docker run -it --rm alpine sh -c "date +%s%N | md5sum | cut -c 1-${lan}")"
fi
}
supervisorctl_restart() {
local RES=`run_exec php "supervisorctl update $1"`
if [ -z "$RES" ]; then
@@ -45,15 +56,24 @@ check_docker() {
echo -e "${Error} ${RedBG} 未安装 Docker${Font}"
exit 1
fi
docker-compose --version &> /dev/null
docker-compose version &> /dev/null
if [ $? -ne 0 ]; then
echo -e "${Error} ${RedBG} 未安装 Docker-compose${Font}"
docker compose version &> /dev/null
if [ $? -ne 0 ]; then
echo -e "${Error} ${RedBG} 未安装 Docker-compose${Font}"
exit 1
fi
COMPOSE="docker compose"
fi
if [[ -n `$COMPOSE version | grep -E "\sv*1"` ]]; then
$COMPOSE version
echo -e "${Error} ${RedBG} Docker-compose 版本过低请升级至v2+${Font}"
exit 1
fi
}
check_node() {
npm --version > /dev/null
npm --version &> /dev/null
if [ $? -ne 0 ]; then
echo -e "${Error} ${RedBG} 未安装nodejs${Font}"
exit 1
@@ -61,7 +81,7 @@ check_node() {
}
docker_name() {
echo `docker-compose ps | awk '{print $1}' | grep "\-$1\-"`
echo `$COMPOSE ps | awk '{print $1}' | grep "\-$1\-"`
}
run_compile() {
@@ -76,6 +96,7 @@ run_compile() {
if [ "$type" = "prod" ]; then
rm -rf "./public/js/build"
npx mix --production
echo "$(rand_string 16)" > ./public/js/hash
else
npx mix watch --hot
fi
@@ -105,6 +126,10 @@ run_electron() {
if [ "$argv" != "dev" ] && [ "$argv" != "--nobuild" ]; then
npx mix --production -- --env --electron
fi
if [ "$argv" == "dev" ]; then
run_exec php "php bin/run --mode=$argv"
supervisorctl_restart php
fi
node ./electron/build.js $argv
}
@@ -116,11 +141,7 @@ run_exec() {
echo -e "${Error} ${RedBG} 没有找到 $container 容器! ${Font}"
exit 1
fi
if [ "$container" = "mariadb" ] || [ "$container" = "nginx" ] || [ "$container" = "redis" ]; then
docker exec -it "$name" /bin/sh -c "$cmd"
else
docker exec -it "$name" /bin/bash -c "$cmd"
fi
docker exec -it "$name" /bin/sh -c "$cmd"
}
run_mysql() {
@@ -159,6 +180,7 @@ run_mysql() {
fi
docker cp $filename $container_name:/
run_exec mariadb "gunzip < /$inputname | mysql -u$username -p$password $database"
run_exec php "php artisan migrate"
judge "还原数据库"
fi
}
@@ -176,8 +198,11 @@ env_set() {
if [ -z "$exist" ]; then
echo "$key=$val" >> $cur_path/.env
else
command="sed -i '/^$key=/c\\$key=$val' /www/.env"
docker run -it --rm -v ${cur_path}:/www alpine sh -c "$command"
if [[ `uname` == 'Linux' ]]; then
sed -i "/^${key}=/c\\${key}=${val}" ${cur_path}/.env
else
docker run -it --rm -v ${cur_path}:/www alpine sh -c "sed -i "/^${key}=/c\\${key}=${val}" /www/.env"
fi
if [ $? -ne 0 ]; then
echo -e "${Error} ${RedBG} 设置env参数失败${Font}"
exit 1
@@ -190,16 +215,43 @@ env_init() {
cp .env.docker .env
fi
if [ -z "$(env_get DB_ROOT_PASSWORD)" ]; then
env_set DB_ROOT_PASSWORD "$(docker run -it --rm alpine sh -c "date +%s%N | md5sum | cut -c 1-16")"
env_set DB_ROOT_PASSWORD "$(rand_string 16)"
fi
if [ -z "$(env_get APP_ID)" ]; then
env_set APP_ID "$(docker run -it --rm alpine sh -c "date +%s%N | md5sum | cut -c 1-6")"
env_set APP_ID "$(rand_string 6)"
fi
if [ -z "$(env_get APP_IPPR)" ]; then
env_set APP_IPPR "10.$(rand 50 100).$(rand 100 200)"
fi
}
arg_get() {
local find="n"
local value=""
for var in $cur_arg; do
if [[ "$find" == "y" ]]; then
if [[ ! $var =~ "--" ]]; then
value=$var
fi
break
fi
if [[ "--$1" == "$var" ]] || [[ "-$1" == "$var" ]]; then
find="y"
value="yes"
fi
done
echo $value
}
is_arm() {
local get_arch=`arch`
if [[ $get_arch =~ "aarch" ]] || [[ $get_arch =~ "arm" ]]; then
echo "yes"
else
echo "no"
fi
}
####################################################################################
####################################################################################
####################################################################################
@@ -212,35 +264,71 @@ fi
if [ $# -gt 0 ]; then
if [[ "$1" == "init" ]] || [[ "$1" == "install" ]]; then
shift 1
rm -rf composer.lock
rm -rf package-lock.json
mkdir -p ${cur_path}/docker/mysql/data
chmod -R 777 ${cur_path}/docker/mysql/data
docker-compose up -d
docker-compose restart php
# 判断架构
if [[ "$(is_arm)" == "yes" ]] && [[ -z "$(arg_get force)" ]]; then
echo -e "${Error} ${RedBG}暂不支持arm架构强制安装请使用./cmd install --force${Font}"
exit 1
fi
# 初始化文件
if [[ -n "$(arg_get relock)" ]]; then
rm -rf node_modules
rm -rf package-lock.json
rm -rf vendor
rm -rf composer.lock
fi
mkdir -p "${cur_path}/docker/log/supervisor"
mkdir -p "${cur_path}/docker/mysql/data"
chmod -R 775 "${cur_path}/docker/log/supervisor"
chmod -R 775 "${cur_path}/docker/mysql/data"
# 启动容器
[[ "$(arg_get port)" -gt 0 ]] && env_set APP_PORT "$(arg_get port)"
$COMPOSE up php -d
# 安装composer依赖
run_exec php "composer install"
if [ ! -f "${cur_path}/vendor/autoload.php" ]; then
run_exec php "composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/"
run_exec php "composer config repo.packagist composer https://packagist.phpcomposer.com"
run_exec php "composer install"
run_exec php "composer config --unset repos.packagist"
fi
[ -z "$(env_get APP_KEY)" ] && run_exec php "php artisan key:generate"
run_exec php "php artisan migrate --seed"
if [ ! -f "${cur_path}/vendor/autoload.php" ]; then
echo -e "${Error} ${RedBG}composer install 失败,请重试! ${Font}"
exit 1
fi
[[ -z "$(env_get APP_KEY)" ]] && run_exec php "php artisan key:generate"
run_exec php "php bin/run --mode=prod"
# 检查数据库
remaining=20
while [ ! -f "${cur_path}/docker/mysql/data/$(env_get DB_DATABASE)/db.opt" ]; do
((remaining=$remaining-1))
if [ $remaining -lt 0 ]; then
echo -e "${Error} ${RedBG} 数据库初始化失败! ${Font}"
exit 1
fi
chmod -R 775 "${cur_path}/docker/mysql/data"
sleep 3
done
run_exec php "php artisan migrate --seed"
if [ ! -f "${cur_path}/docker/mysql/data/$(env_get DB_DATABASE)/$(env_get DB_PREFIX)migrations.ibd" ]; then
echo -e "${Error} ${RedBG} 数据库安装失败! ${Font}"
exit 1
fi
# 设置初始化密码
res=`run_exec mariadb "sh /etc/mysql/repassword.sh"`
docker-compose stop
docker-compose start
$COMPOSE up -d
supervisorctl_restart php
echo -e "${OK} ${GreenBG} 安装完成 ${Font}"
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
echo -e "$res"
elif [[ "$1" == "update" ]]; then
shift 1
run_mysql backup
git fetch --all
git reset --hard origin/$(git branch | sed -n -e 's/^\* \(.*\)/\1/p')
git pull
run_exec php "composer update"
run_exec php "php artisan migrate"
supervisorctl_restart php
docker-compose up -d
$COMPOSE up -d
elif [[ "$1" == "uninstall" ]]; then
shift 1
read -rp "确定要卸载(含:删除容器、数据库、日志)吗?(y/n): " uninstall
@@ -254,11 +342,34 @@ if [ $# -gt 0 ]; then
exit 2
;;
esac
docker-compose down
$COMPOSE down
rm -rf "./docker/mysql/data"
rm -rf "./docker/log/supervisor"
find "./storage/logs" -name "*.log" | xargs rm -rf
echo -e "${OK} ${GreenBG} 卸载完成 ${Font}"
elif [[ "$1" == "reinstall" ]]; then
shift 1
./cmd uninstall $@
sleep 3
./cmd install $@
elif [[ "$1" == "port" ]]; then
shift 1
env_set APP_PORT "$1"
$COMPOSE up -d
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
elif [[ "$1" == "url" ]]; then
shift 1
env_set APP_URL "$1"
supervisorctl_restart php
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
elif [[ "$1" == "env" ]]; then
shift 1
if [ -n "$1" ]; then
env_set $1 "$2"
fi
supervisorctl_restart php
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
elif [[ "$1" == "repassword" ]]; then
shift 1
run_exec mariadb "sh /etc/mysql/repassword.sh \"$@\""
@@ -268,6 +379,9 @@ if [ $# -gt 0 ]; then
elif [[ "$1" == "prod" ]] || [[ "$1" == "production" ]]; then
shift 1
run_compile prod
elif [[ "$1" == "appbuild" ]] || [[ "$1" == "buildapp" ]]; then
shift 1
run_electron app $@
elif [[ "$1" == "electron" ]]; then
shift 1
run_electron $@
@@ -315,10 +429,10 @@ if [ $# -gt 0 ]; then
elif [[ "$1" == "composer" ]]; then
shift 1
e="composer $@" && run_exec php "$e"
elif [[ "$1" == "super" ]]; then
elif [[ "$1" == "service" ]]; then
shift 1
supervisorctl_restart "$@"
elif [[ "$1" == "supervisorctl" ]]; then
e="service $@" && run_exec php "$e"
elif [[ "$1" == "super" ]] || [[ "$1" == "supervisorctl" ]]; then
shift 1
e="supervisorctl $@" && run_exec php "$e"
elif [[ "$1" == "models" ]]; then
@@ -329,11 +443,11 @@ if [ $# -gt 0 ]; then
e="./vendor/bin/phpunit $@" && run_exec php "$e"
elif [[ "$1" == "restart" ]]; then
shift 1
docker-compose stop "$@"
docker-compose start "$@"
$COMPOSE stop "$@"
$COMPOSE start "$@"
else
docker-compose "$@"
$COMPOSE "$@"
fi
else
docker-compose ps
$COMPOSE ps
fi

View File

@@ -16,14 +16,19 @@
"ext-simplexml": "*",
"fideloper/proxy": "^4.4.1",
"fruitcake/laravel-cors": "^2.0.4",
"guanguans/notify": "^1.21.1",
"guzzlehttp/guzzle": "^7.3.0",
"hedeqiang/umeng": "^2.1",
"laravel/framework": "^v8.48.1",
"laravel/tinker": "^v2.6.1",
"lasserafn/php-initial-avatar-generator": "^4.2",
"maatwebsite/excel": "^3.1.31",
"madnest/madzipper": "^v1.1.0",
"mews/captcha": "^3.2.6",
"orangehill/iseed": "^3.0.1",
"predis/predis": "^1.1.7"
"overtrue/pinyin": "^4.0",
"predis/predis": "^1.1.7",
"symfony/mailer": "^6.0"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^v2.10.0",

10224
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -300,9 +300,9 @@ return [
'log_level' => 4,
'log_file' => storage_path(sprintf('logs/swoole-%s.log', date('Y-m'))),
'document_root' => base_path('public'),
'buffer_output_size' => 200 * 1024 * 1024,
'socket_buffer_size' => 256 * 1024 * 1024,
'package_max_length' => 200 * 1024 * 1024,
'buffer_output_size' => 2 * 1024 * 1024,
'socket_buffer_size' => 8 * 1024 * 1024,
'package_max_length' => 1024 * 1024 * 1024, // 1GB
'reload_async' => true,
'max_wait_time' => 60,
'enable_reuse_port' => true,

View File

@@ -17,7 +17,7 @@ class CreateProjectLogsTable extends Migration
$table->bigIncrements('id');
$table->bigInteger('project_id')->nullable()->default(0)->comment('项目ID');
$table->bigInteger('column_id')->nullable()->default(0)->comment('列表ID');
$table->bigInteger('task_id')->nullable()->default(0)->comment('项目ID');
$table->bigInteger('task_id')->nullable()->default(0)->comment('任务ID');
$table->bigInteger('userid')->nullable()->default(0)->comment('会员ID');
$table->string('detail', 500)->nullable()->default('')->comment('详细信息');
$table->timestamps();

View File

@@ -20,7 +20,7 @@ class CreateWebSocketDialogMsgReadsTable extends Migration
$table->bigInteger('userid')->nullable()->default(0)->comment('发送会员ID');
$table->tinyInteger('after')->nullable()->default(0)->comment('在阅读之后才添加的记录');
$table->timestamp('read_at')->nullable()->comment('阅读时间');
$table->unique(['msg_id', 'userid'], 'IDEX_msg_id_userid');
$table->unique(['msg_id', 'userid']);
});
}

View File

@@ -15,7 +15,7 @@ class CreateFileLinksTable extends Migration
{
Schema::create('file_links', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('file_id')->nullable()->default(0)->comment('项目ID');
$table->bigInteger('file_id')->nullable()->default(0)->comment('文件ID');
$table->integer('num')->nullable()->default(0)->comment('累计访问');
$table->string('code')->nullable()->default('')->comment('链接码');
$table->timestamps();

View File

@@ -49,7 +49,7 @@ class FileUsersAddPermission extends Migration
public function down()
{
Schema::table('file_users', function (Blueprint $table) {
//
$table->dropColumn("permission");
});
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class WebSocketDialogMsgsAddDeletes extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_msgs', 'deleted_at')) {
$table->softDeletes();
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropSoftDeletes();
});
}
}

View File

@@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class InsertSettingColumnTemplate extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$array = \App\Module\Base::setting('columnTemplate');
if (empty($array)) {
\App\Module\Base::setting('columnTemplate', [
[
'name' => '软件开发',
'columns' => ['产品规划', '前端开发', '后端开发', '测试', '发布', '其他'],
],
[
'name' => '产品开发',
'columns' => ['产品计划', '正在设计', '正在研发', '测试', '准备发布', '发布成功'],
],
]);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class WebSocketDialogUsersAddTopAt extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_users', 'top_at')) {
$table->timestamp('top_at')->nullable()->after('userid')->comment('置顶时间');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
$table->dropColumn("top_at");
});
}
}

View File

@@ -0,0 +1,30 @@
<?php
use App\Models\File;
use Illuminate\Database\Migrations\Migration;
class FilesUpdateType extends Migration
{
/**
* 更改流程图文件类型
* @return void
*/
public function up()
{
File::whereType('flow')->update([
'type' => 'drawio'
]);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
File::whereType('drawio')->update([
'type' => 'flow'
]);
}
}

View File

@@ -0,0 +1,75 @@
<?php
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
use App\Models\File;
use App\Models\FileContent;
use App\Module\Base;
use Carbon\Carbon;
use Illuminate\Database\Migrations\Migration;
class FilesUpdateExt extends Migration
{
/**
* 更新后缀
* @return void
*/
public function up()
{
File::whereIn('type', ['mind', 'drawio', 'document'])->where('ext', '')->orderBy('id')->chunk(100, function($files) {
/** @var File $file */
foreach ($files as $file) {
$fileContent = FileContent::whereFid($file->id)->orderByDesc('id')->first();
$contentArray = Base::json2array($fileContent?->content);
$contentString = '';
//
switch ($file->type) {
case 'document':
$file->ext = $contentArray['type'] ?: 'md';
$contentString = $contentArray['content'];
break;
case 'drawio':
$file->ext = 'drawio';
$contentString = $contentArray['xml'];
break;
case 'mind':
$file->ext = 'mind';
$contentString = $fileContent?->content;
break;
}
$file->save();
//
$path = 'uploads/file/' . $file->type . '/' . date("Ym", Carbon::parse($file->created_at)->timestamp) . '/' . $file->id . '/' . md5($contentString);
$save = public_path($path);
Base::makeDir(dirname($save));
file_put_contents($save, $contentString);
$content = [
'type' => $file->ext,
'url' => $path
];
//
$content = FileContent::createInstance([
'fid' => $file->id,
'content' => $content,
'text' => $fileContent?->text,
'size' => $file->size,
'userid' => $file->userid,
]);
$content->save();
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
File::whereIn('ext', ['mind', 'drawio', 'md'])->update([
'ext' => ''
]);
// ... 退回去意义不大,文件内容不做回滚操作
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ProjectUsersAddTopAt extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('project_users', function (Blueprint $table) {
if (!Schema::hasColumn('project_users', 'top_at')) {
$table->timestamp('top_at')->nullable()->after('owner')->comment('置顶时间');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('project_users', function (Blueprint $table) {
$table->dropColumn("top_at");
});
}
}

View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProjectTaskFlowChangesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('project_task_flow_changes', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('task_id')->nullable()->default(0)->comment('任务ID');
$table->bigInteger('userid')->nullable()->default(0)->comment('会员ID');
$table->bigInteger('before_item_id')->nullable()->default(0)->comment('变化前工作流状态ID');
$table->string('before_item_name', 50)->nullable()->default('')->comment('(变化前)工作流状态名称');
$table->bigInteger('after_item_id')->nullable()->default(0)->comment('变化后工作流状态ID');
$table->string('after_item_name', 50)->nullable()->default('')->comment('(变化后)工作流状态名称');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('project_task_flow_changes');
}
}

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