Compare commits

...

982 Commits

Author SHA1 Message Date
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
623585344@qq.com
320f183b49 add:新增暗黑皮肤样式 2021-12-31 16:59:42 +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
2514 changed files with 92854 additions and 372386 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,61 +0,0 @@
name: Build
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
if: startsWith(github.event.ref, 'refs/tags/v')
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Create changelog text
id: changelog
uses: loopwerk/tag-changelog@v1
with:
token: ${{ secrets.GH_PAT }}
exclude_types: other,chore
- name: Create release
uses: actions/create-release@latest
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: ${{ steps.changelog.outputs.changes }}
build:
runs-on: ${{ matrix.os }}
environment: build
strategy:
matrix:
os: [macos-11]
platform: [
build-mac,
build-mac-arm,
build-win
]
if: startsWith(github.event.ref, 'refs/tags/v')
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: Build
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
EP_PRE_RELEASE: true
run: ./cmd electron ${{ matrix.platform }}

2
.gitignore vendored
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

@@ -13,7 +13,7 @@ Group No.: `546574618`
- `Docker` & `Docker Compose v2.0+` must be installed
- System: `Centos/Debian/Ubuntu/macOS`
- Hardware suggestion: 2 cores and above 2G memory
- Hardware suggestion: 2 cores and above 4G memory
### Deployment project
@@ -21,9 +21,9 @@ Group No.: `546574618`
# 1、Clone the repository
# Clone projects on github
git clone https://github.com/kuaifan/dootask.git
git clone --depth=1 https://github.com/kuaifan/dootask.git
# Or you can use gitee
git clone https://gitee.com/aipaw/dootask.git
git clone --depth=1 https://gitee.com/aipaw/dootask.git
# 2、Enter directory
cd dootask
@@ -45,6 +45,16 @@ cd dootask
./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
```bash
@@ -86,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
```
@@ -95,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
@@ -104,12 +114,32 @@ git pull
./cmd uninstall
./cmd install
./cmd mysql recovery
./cmd artisan migrate
```
If 502 after the upgrade please run `./cmd restart` restart the service.
## Transfer
Follow these steps to complete the project migration after the new project is installed:
1. Backup original database
```bash
# Run command under old project
./cmd mysql backup
```
2. Copy `database backup file` and `public/uploads` directory to the new project.
3. Restore database to new project
```bash
# Run command under new project
./cmd mysql recovery
```
## Uninstall
```bash
# Enter directory and run command
# Running commands in a project
./cmd uninstall
```

View File

@@ -13,7 +13,7 @@
- 必须安装:`Docker``Docker Compose v2.0+`
- 支持环境:`Centos/Debian/Ubuntu/macOS`
- 硬件建议2核2G以上
- 硬件建议2核4G以上
### 部署项目
@@ -21,9 +21,9 @@
# 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
@@ -45,6 +45,16 @@ cd dootask
./cmd port 2222
```
### 更换URL
```bash
# 此地址仅影响邮件回复功能
./cmd url {域名地址}
# 例如:
./cmd url https://domain.com
```
### 停止服务
```bash
@@ -87,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
```
@@ -96,7 +106,7 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
**注意:在升级之前请备份好你的数据!**
```bash
# 方法1进入项目所在目录,运行以下命令
# 方法1在项目下运行命令
./cmd update
# 或者方法2如果方法1失败请使用此方法
@@ -105,12 +115,32 @@ git pull
./cmd uninstall
./cmd install
./cmd mysql recovery
./cmd artisan migrate
```
如果升级后出现502请运行 `./cmd restart` 重启服务即可。
## 迁移项目
在新项目安装好之后按照以下步骤完成项目迁移:
1、备份原数据库
```bash
# 在旧的项目下运行命令
./cmd mysql backup
```
2、将`数据库备份文件`及`public/uploads`目录拷贝至新项目
3、还原数据库至新项目
```bash
# 在新的项目下运行命令
./cmd mysql recovery
```
## 卸载项目
```bash
# 进入项目所在目录,运行以下命令
# 在项目下运行命令
./cmd uninstall
```

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

@@ -15,3 +15,10 @@ if (!function_exists('seeders_at')) {
return date("Y-m-d H:i:s", $time);
}
}
if (!function_exists('md5s')) {
function md5s($val, $len = 16)
{
return substr(md5($val), 32 - $len);
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api;
use App\Models\File;
use App\Models\ProjectTask;
use App\Models\ProjectTaskFile;
use App\Models\User;
@@ -10,7 +11,10 @@ use App\Models\WebSocketDialogMsg;
use App\Models\WebSocketDialogMsgRead;
use App\Models\WebSocketDialogUser;
use App\Module\Base;
use Carbon\Carbon;
use DB;
use Request;
use Response;
/**
* @apiDefine dialog
@@ -27,6 +31,7 @@ class DialogController extends AbstractController
* @apiGroup dialog
* @apiName lists
*
* @apiParam {String} [at_after] 只读取在这个时间之后更新的对话
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:100最大:200
*
@@ -38,13 +43,18 @@ class DialogController extends AbstractController
{
$user = User::auth();
//
$list = WebSocketDialog::select(['web_socket_dialogs.*'])
$builder = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('u.userid', $user->userid)
->where('u.userid', $user->userid);
if (Request::exists('at_after')) {
$builder->where('web_socket_dialogs.last_at', '>', Carbon::parse(Request::input('at_after')));
}
$list = $builder
->orderByDesc('u.top_at')
->orderByDesc('web_socket_dialogs.last_at')
->paginate(Base::getPaginate(200, 100));
$list->transform(function (WebSocketDialog $item) use ($user) {
return WebSocketDialog::formatData($item, $user->userid);
return $item->formatData($user->userid);
});
//
return Base::retSuccess('success', $list);
@@ -70,20 +80,64 @@ class DialogController extends AbstractController
//
$dialog_id = intval(Request::input('dialog_id'));
//
$item = WebSocketDialog::select(['web_socket_dialogs.*'])
$item = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('web_socket_dialogs.id', $dialog_id)
->where('u.userid', $user->userid)
->first();
if ($item) {
$item = WebSocketDialog::formatData($item, $user->userid);
if (empty($item)) {
return Base::retError('会话不存在或已被删除', ['dialog_id' => $dialog_id], -4003);
}
//
return Base::retSuccess('success', $item);
return Base::retSuccess('success', $item->formatData($user->userid));
}
/**
* @api {get} api/dialog/msg/user 03. 打开会话
* @api {get} api/dialog/user 03. 获取会话成员
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName user
*
* @apiParam {Number} dialog_id 会话ID
* @apiParam {Number} [getuser] 获取会员详情1: 返回会员昵称、邮箱等基本信息0: 默认不返回)
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function user()
{
User::auth();
//
$dialog_id = intval(Request::input('dialog_id'));
$getuser = intval(Request::input('getuser', 0));
//
$dialog = WebSocketDialog::checkDialog($dialog_id);
//
$data = $dialog->dialogUser->toArray();
if ($getuser === 1) {
$array = [];
foreach ($data as $item) {
$res = User::userid2basic($item['userid']);
if ($res) {
$array[] = array_merge($item, $res->toArray());
}
}
$data = $array;
}
//
$array = [];
foreach ($data as $item) {
if ($item['userid'] > 0) {
$array[] = $item;
}
}
return Base::retSuccess('success', $array);
}
/**
* @api {get} api/dialog/open/user 04. 打开会话
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -101,23 +155,20 @@ class DialogController extends AbstractController
$user = User::auth();
//
$userid = intval(Request::input('userid'));
if ($userid == $user->userid) {
return Base::retError('不能对话自己');
if (empty($userid)) {
return Base::retError('错误的会话');
}
//
$dialog = WebSocketDialog::checkUserDialog($user->userid, $userid);
if (empty($dialog)) {
return Base::retError('打开会话失败');
}
$data = WebSocketDialog::formatData(WebSocketDialog::find($dialog->id), $user->userid);
if (empty($data)) {
return Base::retError('打开会话错误');
}
$data = WebSocketDialog::find($dialog->id)?->formatData($user->userid);
return Base::retSuccess('success', $data);
}
/**
* @api {get} api/dialog/msg/lists 04. 获取消息列表
* @api {get} api/dialog/msg/lists 05. 获取消息列表
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -141,42 +192,69 @@ class DialogController extends AbstractController
//
$dialog = WebSocketDialog::checkDialog($dialog_id);
//
$list = WebSocketDialogMsg::whereDialogId($dialog_id)->orderByDesc('id')->paginate(Base::getPaginate(100, 50));
$list->transform(function (WebSocketDialogMsg $item) use ($user) {
$item->is_read = $item->userid === $user->userid || WebSocketDialogMsgRead::whereMsgId($item->id)->whereUserid($user->userid)->value('read_at');
return $item;
});
$list = WebSocketDialogMsg::select([
'web_socket_dialog_msgs.*',
'read.mention',
'read.read_at',
])->leftJoin('web_socket_dialog_msg_reads as read', function ($leftJoin) use ($user) {
$leftJoin
->on('read.userid', '=', DB::raw($user->userid))
->on('read.msg_id', '=', 'web_socket_dialog_msgs.id');
})->where('web_socket_dialog_msgs.dialog_id', $dialog_id)->orderByDesc('web_socket_dialog_msgs.id')->paginate(Base::getPaginate(100, 50));
//
if ($dialog->type == 'group' && $dialog->group_type == 'task') {
$user->task_dialog_id = $dialog->id;
$user->save();
}
//去掉标记未读
$isMarkDialogUser = WebSocketDialogUser::whereDialogId($dialog->id)->whereUserid($user->userid)->whereMarkUnread(1)->first();
if ($isMarkDialogUser) {
$isMarkDialogUser->mark_unread = 0;
$isMarkDialogUser->save();
}
//
$data = $list->toArray();
if ($list->currentPage() === 1) {
$data['dialog'] = WebSocketDialog::formatData($dialog, $user->userid);
$data['dialog'] = $dialog->formatData($user->userid);
}
return Base::retSuccess('success', $data);
}
/**
* @api {get} api/dialog/msg/sendtext 05. 未读消息
* @api {get} api/dialog/msg/unread 06. 获取未读消息数量
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__sendtext
* @apiName msg__unread
*
* @apiParam {Number} [dialog_id] 对话ID留空获取总未读消息数量
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
* @apiSuccessExample {json} data:
{
"unread": 43, // 未读消息数
"last_umid": 308 // 最新的一条未读消息ID用于判断是否更新前端的未读数量
}
*/
public function msg__unread()
{
$unread = WebSocketDialogMsgRead::whereUserid(User::userid())->whereReadAt(null)->count();
$dialog_id = intval(Request::input('dialog_id'));
//
$builder = WebSocketDialogMsgRead::whereUserid(User::userid())->whereReadAt(null);
if ($dialog_id > 0) {
$builder->whereDialogId($dialog_id);
}
return Base::retSuccess('success', [
'unread' => $unread,
'unread' => $builder->count(),
'last_umid' => intval($builder->orderByDesc('msg_id')->value('msg_id')),
]);
}
/**
* @api {get} api/dialog/msg/sendtext 06. 发送消息
* @api {post} api/dialog/msg/sendtext 07. 发送消息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -192,6 +270,7 @@ class DialogController extends AbstractController
*/
public function msg__sendtext()
{
Base::checkClientVersion('0.13.33');
$user = User::auth();
//
$chat_nickname = Base::settingFind('system', 'chat_nickname');
@@ -202,36 +281,103 @@ class DialogController extends AbstractController
}
}
//
$dialog_id = intval(Request::input('dialog_id'));
$text = trim(Request::input('text'));
//
if (mb_strlen($text) < 1) {
return Base::retError('消息内容不能为空');
} elseif (mb_strlen($text) > 20000) {
return Base::retError('消息内容最大不能超过20000字');
}
$dialog_id = Base::getPostInt('dialog_id');
$text = trim(Base::getPostValue('text'));
//
WebSocketDialog::checkDialog($dialog_id);
//
$msg = [
'text' => $text
];
$text = WebSocketDialogMsg::formatMsg($text, $dialog_id);
$strlen = mb_strlen($text);
if ($strlen < 1) {
return Base::retError('消息内容不能为空');
} elseif ($strlen > 200000) {
return Base::retError('消息内容最大不能超过200000字');
}
if ($strlen > 2000) {
// 内容过长转成文件发送
$path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
Base::makeDir(public_path($path));
$path = $path . md5($text) . ".txt";
$file = public_path($path);
file_put_contents($file, $text);
$size = filesize(public_path($path));
if (empty($size)) {
return Base::retError('消息发送保存失败');
}
$fileData = [
'name' => "LongText-{$strlen}.txt",
'size' => $size,
'file' => $file,
'path' => $path,
'url' => Base::fillUrl($path),
'thumb' => '',
'width' => -1,
'height' => -1,
'ext' => 'txt',
];
return WebSocketDialogMsg::sendMsg($dialog_id, 'file', $fileData, $user->userid);
}
//
return WebSocketDialogMsg::sendMsg($dialog_id, 'text', $msg, $user->userid);
return WebSocketDialogMsg::sendMsg($dialog_id, 'text', ['text' => $text], $user->userid);
}
/**
* @api {post} api/dialog/msg/sendfile 07. 文件上传
* @api {post} api/dialog/msg/sendrecord 08. 发送语音
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__sendrecord
*
* @apiParam {Number} dialog_id 对话ID
* @apiParam {String} base64 语音base64
* @apiParam {Number} duration 语音时长(毫秒)
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__sendrecord()
{
$user = User::auth();
//
$dialog_id = Base::getPostInt('dialog_id');
//
WebSocketDialog::checkDialog($dialog_id);
//
$path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
$base64 = Base::getPostValue('base64');
$duration = Base::getPostInt('duration');
if ($duration < 600) {
return Base::retError('说话时间太短');
}
$data = Base::record64save([
"base64" => $base64,
"path" => $path,
]);
if (Base::isError($data)) {
return Base::retError($data['msg']);
} else {
$recordData = $data['data'];
$recordData['size'] *= 1024;
$recordData['duration'] = $duration;
return WebSocketDialogMsg::sendMsg($dialog_id, 'record', $recordData, $user->userid);
}
}
/**
* @api {post} api/dialog/msg/sendfile 09. 文件上传
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__sendfile
*
* @apiParam {Number} dialog_id 对话ID
* @apiParam {String} [filename] post-文件名称
* @apiParam {String} [image64] post-base64图片二选一
* @apiParam {File} [files] post-文件对象(二选一)
* @apiParam {Number} dialog_id 对话ID
* @apiParam {Number} [image_attachment] 图片是否也存到附件
* @apiParam {String} [filename] post-文件名称
* @apiParam {String} [image64] post-base64图片(二选一)
* @apiParam {File} [files] post-文件对象(二选一)
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -242,10 +388,11 @@ class DialogController extends AbstractController
$user = User::auth();
//
$dialog_id = Base::getPostInt('dialog_id');
$image_attachment = Base::getPostInt('image_attachment');
//
$dialog = WebSocketDialog::checkDialog($dialog_id);
//
$path = "uploads/chat/" . $user->userid . "/";
$path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
$image64 = Base::getPostValue('image64');
$fileName = Base::getPostValue('filename');
if ($image64) {
@@ -257,7 +404,7 @@ class DialogController extends AbstractController
} else {
$data = Base::upload([
"file" => Request::file('files'),
"type" => 'file',
"type" => 'more',
"path" => $path,
"fileName" => $fileName,
]);
@@ -270,8 +417,8 @@ class DialogController extends AbstractController
$fileData['thumb'] = Base::unFillUrl($fileData['thumb']);
$fileData['size'] *= 1024;
//
if ($dialog->type === 'group') {
if ($dialog->group_type === 'task') {
if ($dialog->type === 'group' && $dialog->group_type === 'task') { // 任务群组保存文件
if ($image_attachment || !in_array($fileData['ext'], File::imageExt)) { // 如果是图片不保存
$task = ProjectTask::whereDialogId($dialog->id)->first();
if ($task) {
$file = ProjectTaskFile::createInstance([
@@ -300,7 +447,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/readlist 08. 获取消息阅读情况
* @api {get} api/dialog/msg/readlist 10. 获取消息阅读情况
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -327,4 +474,456 @@ class DialogController extends AbstractController
$read = WebSocketDialogMsgRead::whereMsgId($msg_id)->get();
return Base::retSuccess('success', $read ?: []);
}
/**
* @api {get} api/dialog/msg/detail 11. 消息详情
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__detail
*
* @apiParam {Number} msg_id 消息ID
* @apiParam {String} only_update_at 仅获取update_at字段
* - no (默认)
* - yes
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__detail()
{
User::auth();
//
$msg_id = intval(Request::input('msg_id'));
$only_update_at = Request::input('only_update_at', 'no');
//
$dialogMsg = WebSocketDialogMsg::whereId($msg_id)->first();
if (empty($dialogMsg)) {
return Base::retError("文件不存在");
}
//
if ($only_update_at == 'yes') {
return Base::retSuccess('success', [
'id' => $dialogMsg->id,
'update_at' => Carbon::parse($dialogMsg->updated_at)->toDateTimeString()
]);
}
//
$data = $dialogMsg->toArray();
//
if ($data['type'] == 'file') {
$msg = Base::json2array($dialogMsg->getRawOriginal('msg'));
$msg = File::formatFileData($msg);
$data['content'] = $msg['content'];
$data['file_mode'] = $msg['file_mode'];
}
//
return Base::retSuccess('success', $data);
}
/**
* @api {get} api/dialog/msg/download 12. 文件下载
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__download
*
* @apiParam {Number} msg_id 消息ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__download()
{
User::auth();
//
$msg_id = intval(Request::input('msg_id'));
//
$msg = WebSocketDialogMsg::whereId($msg_id)->first();
if (empty($msg)) {
abort(403, "This file not exist.");
}
if ($msg->type != 'file') {
abort(403, "This file not support download.");
}
$array = Base::json2array($msg->getRawOriginal('msg'));
//
return Response::download(public_path($array['path']), $array['name']);
}
/**
* @api {get} api/dialog/msg/withdraw 13. 聊天消息撤回
*
* @apiDescription 消息撤回限制24小时内需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__withdraw
*
* @apiParam {Number} msg_id 消息ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__withdraw()
{
$user = User::auth();
$msg_id = intval(Request::input("msg_id"));
$msg = WebSocketDialogMsg::whereId($msg_id)->whereUserid($user->userid)->first();
if (empty($msg)) {
return Base::retError("消息不存在或已被删除");
}
$msg->deleteMsg();
return Base::retSuccess("success");
}
/**
* @api {get} api/dialog/msg/mark 14. 消息标记操作
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__mark
*
* @apiParam {Number} dialog_id 会话ID
* @apiParam {String} type 类型
* - read
* - unread
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__mark()
{
$user = User::auth();
$dialogId = intval(Request::input('dialog_id'));
$type = Request::input('type');
$dialogUser = WebSocketDialogUser::whereUserid($user->userid)->whereDialogId($dialogId)->first();
if (!$dialogUser) {
return Base::retError("会话不存在");
}
switch ($type) {
case 'read':
WebSocketDialogMsgRead::whereUserid($user->userid)
->whereReadAt(null)
->whereDialogId($dialogId)
->chunkById(100, function ($list) {
WebSocketDialogMsgRead::onlyMarkRead($list);
});
$dialogUser->mark_unread = 0;
$dialogUser->save();
break;
case 'unread':
$dialogUser->mark_unread = 1;
$dialogUser->save();
break;
default:
return Base::retError("参数错误");
}
return Base::retSuccess("success", [
'id' => $dialogId,
'mark_unread' => $dialogUser->mark_unread,
]);
}
/**
* @api {get} api/dialog/msg/forward 15. 转发消息给
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__forward
*
* @apiParam {Number} msg_id 消息ID
* @apiParam {Array} userids 转发给的成员
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__forward()
{
$user = User::auth();
//
$msg_id = intval(Request::input("msg_id"));
$userids = Request::input('userids');
//
if (empty($userids)) {
return Base::retError("请选择转发成员");
}
//
$msg = WebSocketDialogMsg::whereId($msg_id)->first();
if (empty($msg)) {
return Base::retError("消息不存在或已被删除");
}
WebSocketDialog::checkDialog($msg->dialog_id);
//
return $msg->forwardMsg($userids, $user->userid);
}
/**
* @api {get} api/dialog/msg/emoji 16. emoji回复
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__forward
*
* @apiParam {Number} msg_id 消息ID
* @apiParam {String} emoji 回复或取消的emoji表情
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__emoji()
{
$user = User::auth();
//
$msg_id = intval(Request::input("msg_id"));
$emoji = Request::input("emoji");
//
if (!preg_match("/^[\u{d800}-\u{dbff}]|[\u{dc00}-\u{dfff}]$/", $emoji)) {
return Base::retError("参数错误");
}
//
$msg = WebSocketDialogMsg::whereId($msg_id)->first();
if (empty($msg)) {
return Base::retError("消息不存在或已被删除");
}
WebSocketDialog::checkDialog($msg->dialog_id);
//
return $msg->emojiMsg($emoji, $user->userid);
}
/**
* @api {get} api/dialog/top 17. 会话置顶
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName top
*
* @apiParam {Number} dialog_id 会话ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function top()
{
$user = User::auth();
$dialogId = intval(Request::input('dialog_id'));
$dialogUser = WebSocketDialogUser::whereUserid($user->userid)->whereDialogId($dialogId)->first();
if (!$dialogUser) {
return Base::retError("会话不存在");
}
$dialogUser->top_at = $dialogUser->top_at ? null : Carbon::now();
$dialogUser->save();
return Base::retSuccess("success", [
'id' => $dialogUser->dialog_id,
'top_at' => $dialogUser->top_at?->toDateTimeString(),
]);
}
/**
* @api {get} api/dialog/group/add 18. 新增群组
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName group__add
*
* @apiParam {Array} userids 群成员,格式: [userid1, userid2, userid3]
* @apiParam {String} chat_name 群名称
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function group__add()
{
$user = User::auth();
//
$userids = Request::input('userids');
$chatName = trim(Request::input('chat_name'));
//
if (!is_array($userids)) {
return Base::retError('请选择群成员');
}
$userids = array_merge([$user->userid], $userids);
$userids = array_values(array_filter(array_unique($userids)));
if (count($userids) < 2) {
return Base::retError('群成员至少2人');
}
//
if (empty($chatName)) {
$array = [];
foreach ($userids as $userid) {
$array[] = User::userid2nickname($userid);
if (count($array) >= 8 || strlen(implode(", ", $array)) > 100) {
$array[] = "...";
break;
}
}
$chatName = implode(", ", $array);
}
$dialog = WebSocketDialog::createGroup($chatName, $userids, 'user', $user->userid);
if (empty($dialog)) {
return Base::retError('创建群组失败');
}
$data = WebSocketDialog::find($dialog->id)?->formatData($user->userid);
$dialog->pushMsg("groupAdd", $data, $userids);
return Base::retSuccess('创建成功', $data);
}
/**
* @api {get} api/dialog/group/edit 19. 修改群组
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName group__edit
*
* @apiParam {Number} dialog_id 会话ID
* @apiParam {String} chat_name 群名称
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function group__edit()
{
$user = User::auth();
//
$dialog_id = intval(Request::input('dialog_id'));
$chatName = trim(Request::input('chat_name'));
//
if (mb_strlen($chatName) < 2) {
return Base::retError('群名称至少2个字');
}
if (mb_strlen($chatName) > 100) {
return Base::retError('群名称最长限制100个字');
}
//
$dialog = WebSocketDialog::checkDialog($dialog_id, true);
//
$dialog->name = $chatName;
$dialog->save();
return Base::retSuccess('修改成功', [
'id' => $dialog->id,
'name' => $dialog->name,
]);
}
/**
* @api {get} api/dialog/group/adduser 20. 添加群成员
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName group__adduser
*
* @apiParam {Number} dialog_id 会话ID
* @apiParam {Array} userids 新增的群成员,格式: [userid1, userid2, userid3]
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function group__adduser()
{
$user = User::auth();
//
$dialog_id = intval(Request::input('dialog_id'));
$userids = Request::input('userids');
//
if (!is_array($userids)) {
return Base::retError('请选择群成员');
}
//
$dialog = WebSocketDialog::checkDialog($dialog_id, true);
//
$dialog->checkGroup();
$dialog->joinGroup($userids);
$dialog->pushMsg("groupJoin", $dialog->formatData($user->userid), $userids);
return Base::retSuccess('添加成功');
}
/**
* @api {get} api/dialog/group/deluser 21. 移出(退出)群成员
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName group__adduser
*
* @apiParam {Number} dialog_id 会话ID
* @apiParam {Array} userids 移出的群成员,格式: [userid1, userid2, userid3]
* - 留空表示自己退出
* - 有值表示移出,仅限群主操作
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function group__deluser()
{
$user = User::auth();
//
$dialog_id = intval(Request::input('dialog_id'));
$userids = Request::input('userids');
//
$type = 'remove';
if (empty($userids)) {
$type = 'exit';
$userids = [$user->userid];
}
//
if (!is_array($userids)) {
return Base::retError('请选择群成员');
}
//
$dialog = WebSocketDialog::checkDialog($dialog_id, $type === 'remove');
//
$dialog->checkGroup();
$dialog->exitGroup($userids);
$dialog->pushMsg("groupExit", null, $userids);
return Base::retSuccess($type === 'remove' ? '移出成功' : '退出成功');
}
/**
* @api {get} api/dialog/group/disband 22. 解散群组
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName group__disband
*
* @apiParam {Number} dialog_id 会话ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function group__disband()
{
User::auth();
//
$dialog_id = intval(Request::input('dialog_id'));
//
$dialog = WebSocketDialog::checkDialog($dialog_id, true);
//
$dialog->checkGroup();
$dialog->deleteDialog();
$dialog->pushMsg("groupDelete");
return Base::retSuccess('解散成功');
}
}

View File

@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api;
use App\Exceptions\ApiException;
use App\Models\AbstractModel;
use App\Models\File;
use App\Models\FileContent;
@@ -11,6 +11,7 @@ use App\Models\FileUser;
use App\Models\User;
use App\Module\Base;
use App\Module\Ihttp;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Request;
@@ -89,6 +90,10 @@ class FileController extends AbstractController
}
}
}
// 图片直接返回预览地址
foreach ($array as &$item) {
$item = File::handleImageUrl($item);
}
return Base::retSuccess('success', $array);
}
@@ -205,22 +210,30 @@ class FileController extends AbstractController
'folder',
'document',
'mind',
'sheet',
'flow',
'drawio',
'word',
'excel',
'ppt',
])) {
return Base::retError('类型错误');
}
$ext = '';
if (in_array($type, [
$ext = str_replace([
'folder',
'document',
'mind',
'drawio',
'word',
'excel',
'ppt',
])) {
$ext = str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $type);
}
], [
'',
'md',
'mind',
'drawio',
'docx',
'xlsx',
'pptx',
], $type);
//
$userid = $user->userid;
if ($pid > 0) {
@@ -243,7 +256,7 @@ class FileController extends AbstractController
'userid' => $userid,
'created_id' => $user->userid,
]);
$file->save();
$file->saveBeforePids();
//
$data = File::find($file->id);
$data->pushMsg('add', $data);
@@ -293,9 +306,19 @@ class FileController extends AbstractController
'userid' => $userid,
'created_id' => $user->userid,
]);
$file->save();
$data = AbstractModel::transaction(function() use ($file) {
$content = FileContent::select(['content', 'text', 'size'])->whereFid($file->cid)->orderByDesc('id')->first();
$file->size = $content?->size ?: 0;
$file->saveBeforePids();
if ($content) {
$content = $content->toArray();
$content['fid'] = $file->id;
$content['userid'] = $file->userid;
FileContent::createInstance($content)->save();
}
return File::find($file->id);
});
//
$data = File::find($file->id);
$data->pushMsg('add', $data);
return Base::retSuccess('复制成功', $data);
}
@@ -308,7 +331,7 @@ class FileController extends AbstractController
* @apiGroup file
* @apiName move
*
* @apiParam {Number} id 文件ID
* @apiParam {Numbers} ids 文件ID(格式:[id1, id2]
* @apiParam {Number} pid 移动到的文件夹ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
@@ -317,32 +340,56 @@ class FileController extends AbstractController
*/
public function move()
{
$user = User::auth();
User::auth();
//
$id = intval(Request::input('id'));
$ids = Request::input('ids');
$pid = intval(Request::input('pid'));
//
$file = File::permissionFind($id, 1000);
//
if (!is_array($ids) || empty($ids)) {
return Base::retError('请选择移动的文件或文件夹');
}
if (count($ids) > 100) {
return Base::retError('一次最多只能移动100个文件或文件夹');
}
$toShareFile = false;
if ($pid > 0) {
if (!File::whereUserid($user->userid)->whereId($pid)->exists()) {
return Base::retError('参数错误');
}
$arr = [];
$tid = $pid;
while ($tid > 0) {
$arr[] = $tid;
$tid = intval(File::whereId($tid)->value('pid'));
}
if (in_array($id, $arr)) {
return Base::retError('位置错误');
}
$tmpFile = File::permissionFind($pid, 1);
$toShareFile = $tmpFile->getShareInfo();
}
//
$file->pid = $pid;
$file->save();
$file->pushMsg('update', $file);
return Base::retSuccess('操作成功', $file);
$files = [];
AbstractModel::transaction(function() use ($pid, $ids, $toShareFile, &$files) {
foreach ($ids as $id) {
$file = File::permissionFind($id, 1000);
//
if ($pid > 0) {
if ($toShareFile) {
if ($file->share) {
throw new ApiException("{$file->name} 当前正在共享,无法移动到另一个共享文件夹内");
}
if ($file->isSubShare()) {
throw new ApiException("{$file->name} 内含有共享文件,无法移动到另一个共享文件夹内");
}
}
//
$tmpId = $pid;
while ($tmpId > 0) {
if ($id == $tmpId) {
throw new ApiException('移动位置错误');
}
$tmpId = intval(File::whereId($tmpId)->value('pid'));
}
}
//
$file->pid = $pid;
$file->saveBeforePids();
$files[] = $file;
}
});
foreach ($files as $file) {
$file->pushMsg('update', $file);
}
return Base::retSuccess('操作成功', $files);
}
/**
@@ -353,7 +400,7 @@ class FileController extends AbstractController
* @apiGroup file
* @apiName remove
*
* @apiParam {Number} id 文件ID
* @apiParam {Numbers} ids 文件ID(格式:[id1, id2]
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -363,12 +410,25 @@ class FileController extends AbstractController
{
User::auth();
//
$id = intval(Request::input('id'));
$ids = Request::input('ids');
//
$file = File::permissionFind($id, 1000);
if (!is_array($ids) || empty($ids)) {
return Base::retError('请选择删除的文件或文件夹');
}
if (count($ids) > 100) {
return Base::retError('一次最多只能删除100个文件或文件夹');
}
//
$file->deleteFile();
return Base::retSuccess('删除成功', $file);
$files = [];
AbstractModel::transaction(function() use ($ids, &$files) {
foreach ($ids as $id) {
$file = File::permissionFind($id, 1000);
$file->deleteFile();
$files[] = $file;
}
});
//
return Base::retSuccess('删除成功', $files);
}
/**
@@ -380,8 +440,15 @@ class FileController extends AbstractController
* @apiName content
*
* @apiParam {Number|String} id
* - Number 文件ID需要登录
* - String 链接码(不需要登录,用于预览)
* - Number: 文件ID需要登录
* - String: 链接码(不需要登录,用于预览)
* @apiParam {String} only_update_at 仅获取update_at字段
* - no (默认)
* - yes
* @apiParam {String} down 直接下载
* - no: 浏览(默认)
* - yes: 下载office文件直接下载
* @apiParam {Number} [history_id] 读取历史记录ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -390,6 +457,9 @@ class FileController extends AbstractController
public function content()
{
$id = Request::input('id');
$down = Request::input('down', 'no');
$only_update_at = Request::input('only_update_at', 'no');
$history_id = intval(Request::input('history_id'));
//
if (Base::isNumber($id)) {
User::auth();
@@ -404,8 +474,19 @@ class FileController extends AbstractController
return Base::retError('参数错误');
}
//
$content = FileContent::whereFid($file->id)->orderByDesc('id')->first();
return FileContent::formatContent($file->type, $content ? $content->content : []);
if ($only_update_at == 'yes') {
return Base::retSuccess('success', [
'id' => $file->id,
'update_at' => Carbon::parse($file->updated_at)->toDateTimeString()
]);
}
//
$builder = FileContent::whereFid($file->id);
if ($history_id > 0) {
$builder->whereId($history_id);
}
$content = $builder->orderByDesc('id')->first();
return FileContent::formatContent($file, $content?->content, $down == 'yes');
}
/**
@@ -416,7 +497,7 @@ class FileController extends AbstractController
* @apiGroup file
* @apiName content__save
*
* @apiParam {Number} id 文件ID
* @apiParam {Number} id 文件ID
* @apiParam {Object} [D] Request Payload 提交
* - content: 内容
*
@@ -426,6 +507,7 @@ class FileController extends AbstractController
*/
public function content__save()
{
Base::checkClientVersion('0.13.68');
$user = User::auth();
//
$id = Base::getPostInt('id');
@@ -437,14 +519,14 @@ class FileController extends AbstractController
if ($file->type == 'document') {
$data = Base::json2array($content);
$isRep = false;
preg_match_all("/<img\s*src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"/s", $data['content'], $matchs);
preg_match_all("/<img\s+src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"/s", $data['content'], $matchs);
foreach ($matchs[2] as $key => $text) {
$p = "uploads/files/document/" . $id . "/";
Base::makeDir(public_path($p));
$p.= md5($text) . "." . $matchs[1][$key];
$r = file_put_contents(public_path($p), base64_decode($text));
if ($r) {
$data['content'] = str_replace($matchs[0][$key], '<img src="' . Base::fillUrl($p) . '"', $data['content']);
$tmpPath = "uploads/file/document/" . date("Ym") . "/" . $id . "/attached/";
Base::makeDir(public_path($tmpPath));
$tmpPath .= md5($text) . "." . $matchs[1][$key];
if (file_put_contents(public_path($tmpPath), base64_decode($text))) {
$paramet = getimagesize(public_path($tmpPath));
$data['content'] = str_replace($matchs[0][$key], '<img src="' . Base::fillUrl($tmpPath) . '" original-width="' . $paramet[0] . '" original-height="' . $paramet[1] . '"', $data['content']);
$isRep = true;
}
}
@@ -454,11 +536,42 @@ class FileController extends AbstractController
}
}
//
switch ($file->type) {
case 'document':
$contentArray = Base::json2array($content);
$contentString = $contentArray['content'];
$file->ext = $contentArray['type'] == 'md' ? 'md' : 'text';
break;
case 'drawio':
$contentArray = Base::json2array($content);
$contentString = $contentArray['xml'];
$file->ext = 'drawio';
break;
case 'mind':
$contentString = $content;
$file->ext = 'mind';
break;
case 'txt':
case 'code':
$contentArray = Base::json2array($content);
$contentString = $contentArray['content'];
break;
default:
return Base::retError('参数错误');
}
$path = "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $id . "/" . md5($contentString);
$save = public_path($path);
Base::makeDir(dirname($save));
file_put_contents($save, $contentString);
//
$content = FileContent::createInstance([
'fid' => $file->id,
'content' => $content,
'content' => [
'type' => $file->ext,
'url' => $path
],
'text' => $text,
'size' => strlen($content),
'size' => filesize($save),
'userid' => $user->userid,
]);
$content->save();
@@ -498,7 +611,7 @@ class FileController extends AbstractController
if ($status === 2) {
$parse = parse_url($url);
$from = 'http://' . env('APP_IPPR') . '.3' . $parse['path'] . '?' . $parse['query'];
$path = 'uploads/office/' . date("Ym") . '/' . $file->id . '/' . $user->userid . '-' . $key;
$path = 'uploads/file/' . $file->type . '/' . date("Ym") . '/' . $file->id . '/' . $key;
$save = public_path($path);
Base::makeDir(dirname($save));
$res = Ihttp::download($from, $save);
@@ -516,6 +629,7 @@ class FileController extends AbstractController
$content->save();
//
$file->size = $content->size;
$file->updated_at = Carbon::now();
$file->save();
$file->pushMsg('update', $file);
}
@@ -531,7 +645,7 @@ class FileController extends AbstractController
* @apiGroup file
* @apiName content__upload
*
* @apiParam {Number} [pid] 父级ID
* @apiParam {Number} [pid] 父级ID
* @apiParam {String} [files] 文件名
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
@@ -543,6 +657,7 @@ class FileController extends AbstractController
$user = User::auth();
//
$pid = intval(Request::input('pid'));
$webkitRelativePath = Request::input('webkitRelativePath');
//
$userid = $user->userid;
if ($pid > 0) {
@@ -557,7 +672,37 @@ class FileController extends AbstractController
}
}
//
$path = 'uploads/office/' . date("Ym") . '/u' . $user->userid . '/';
$dirs = explode("/", $webkitRelativePath);
$addItem = [];
while (count($dirs) > 1) {
$dirName = array_shift($dirs);
if ($dirName) {
AbstractModel::transaction(function () use ($dirName, $user, $userid, &$pid, &$addItem) {
$dirRow = File::wherePid($pid)->whereType('folder')->whereName($dirName)->lockForUpdate()->first();
if (empty($dirRow)) {
$dirRow = File::createInstance([
'pid' => $pid,
'type' => 'folder',
'name' => $dirName,
'userid' => $userid,
'created_id' => $user->userid,
]);
if ($dirRow->saveBeforePids()) {
$addItem[] = File::find($dirRow->id);
}
}
if (empty($dirRow)) {
throw new ApiException('创建文件夹失败');
}
$pid = $dirRow->id;
});
foreach ($addItem as $tmpRow) {
$tmpRow->pushMsg('add', $tmpRow);
}
}
}
//
$path = 'uploads/tmp/' . date("Ym") . '/';
$data = Base::upload([
"file" => Request::file('files'),
"type" => 'more',
@@ -570,6 +715,9 @@ class FileController extends AbstractController
$data = $data['data'];
//
$type = match ($data['ext']) {
'text', 'md', 'markdown' => 'document',
'drawio' => 'drawio',
'mind' => 'mind',
'doc', 'docx' => "word",
'xls', 'xlsx' => "excel",
'ppt', 'pptx' => "ppt",
@@ -581,12 +729,22 @@ class FileController extends AbstractController
'ofd' => "ofd",
'pdf' => "pdf",
'txt' => "txt",
'html', 'htm', 'asp', 'jsp', 'xml', 'json', 'properties', 'md', 'gitignore', 'log', 'java', 'py', 'c', 'cpp', 'sql', 'sh', 'bat', 'm', 'bas', 'prg', 'cmd',
'php', 'go', 'python', 'js', 'ftl', 'css', 'lua', 'rb', 'yaml', 'yml', 'h', 'cs', 'aspx' => "code",
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css',
'dockerfile', 'go', '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' => "code",
'mp3', 'wav', 'mp4', 'flv',
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm' => "media",
'xmind' => "xmind",
'rp' => "axure",
default => "",
};
if ($data['ext'] == 'markdown') {
$data['ext'] = 'md';
}
$file = File::createInstance([
'pid' => $pid,
'name' => Base::rightDelete($data['name'], '.' . $data['ext']),
@@ -596,34 +754,113 @@ class FileController extends AbstractController
'created_id' => $user->userid,
]);
// 开始创建
return AbstractModel::transaction(function () use ($type, $user, $data, $file) {
$file->save();
return AbstractModel::transaction(function () use ($addItem, $webkitRelativePath, $type, $user, $data, $file) {
$file->size = $data['size'] * 1024;
$file->saveBeforePids();
//
$data = Base::uploadMove($data, "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $file->id . "/");
$content = [
'from' => '',
'type' => $type,
'ext' => $data['ext'],
'url' => $data['path'],
];
if (isset($data['width'])) {
$content['width'] = $data['width'];
$content['height'] = $data['height'];
}
$content = FileContent::createInstance([
'fid' => $file->id,
'content' => [
'from' => '',
'type' => $type,
'ext' => $data['ext'],
'url' => $data['path']
],
'content' => $content,
'text' => '',
'size' => $data['size'] * 1024,
'size' => $file->size,
'userid' => $user->userid,
]);
$content->save();
//
$file->size = $content->size;
$file->save();
$tmpRow = File::find($file->id);
$tmpRow->pushMsg('add', $tmpRow);
//
$data = File::find($file->id);
$data->pushMsg('add', $data);
return Base::retSuccess($data['name'] . ' 上传成功', $data);
$data = File::handleImageUrl($tmpRow->toArray());
$data['full_name'] = $webkitRelativePath ?: $data['name'];
//
$addItem[] = $data;
return Base::retSuccess($data['name'] . ' 上传成功', $addItem);
});
}
/**
* @api {get} api/file/share 12. 获取共享信息
* @api {get} api/file/content/history 12. 获取内容历史
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName content__history
*
* @apiParam {Number} id 文件ID
*
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:20最大:100
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function content__history()
{
$id = Request::input('id');
//
$file = File::permissionFind(intval($id));
//
$data = FileContent::select(['id', 'size', 'userid', 'created_at'])
->whereFid($file->id)
->orderByDesc('id')
->paginate(Base::getPaginate(100, 20));
return Base::retSuccess('success', $data);
}
/**
* @api {get} api/file/content/restore 13. 恢复文件历史
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName content__restore
*
* @apiParam {Number} id 文件ID
* @apiParam {Number} history_id 历史数据ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function content__restore()
{
$user = User::auth();
//
$id = intval(Request::input('id'));
$history_id = intval(Request::input('history_id'));
//
$file = File::permissionFind($id);
//
$history = FileContent::whereFid($file->id)->whereId($history_id)->first();
if (empty($history)) {
return Base::retError('历史数据不存在或已被删除');
}
//
$content = $history->replicate();
$content->userid = $user->userid;
$content->save();
//
$file->size = $content->size;
$file->save();
$file->pushMsg('content');
//
return Base::retSuccess('还原成功');
}
/**
* @api {get} api/file/share 14. 获取共享信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -659,7 +896,7 @@ class FileController extends AbstractController
}
/**
* @api {get} api/file/share/update 13. 设置共享
* @api {get} api/file/share/update 15. 设置共享
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -672,6 +909,9 @@ class FileController extends AbstractController
* - 0只读
* - 1读写
* - -1: 删除
* @apiParam {Number} [force] 设置共享时是否忽略提醒
* - 0如果子文件夹已存在共享则ret返回-3001默认
* - 1忽略提醒
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -684,6 +924,7 @@ class FileController extends AbstractController
$id = intval(Request::input('id'));
$userids = Request::input('userids');
$permission = intval(Request::input('permission'));
$force = intval(Request::input('force'));
//
if (!in_array($permission, [-1, 0, 1])) {
return Base::retError('参数错误');
@@ -697,8 +938,10 @@ class FileController extends AbstractController
return Base::retError('仅限所有者操作');
}
//
if ($file->isNnShare()) {
return Base::retError('已经处于共享文件夹中');
$share = $file->isNnShare();
if ($share) {
$typeCn = $file->type === 'folder' ? '文件夹' : '文件';
return Base::retError("{$typeCn}已经处于【{$share->name}】共享文件夹中,无法重复共享");
}
//
if (!is_array($userids) || empty($userids)) {
@@ -710,16 +953,18 @@ class FileController extends AbstractController
// 取消共享
$action = "delete";
foreach ($userids as $userid) {
if (FileUser::where([
'file_id' => $file->id,
'userid' => $userid,
])->delete()) {
if (FileUser::deleteFileUser($file->id, $userid)) {
$array[] = $userid;
}
}
} else {
// 设置共享
$action = "update";
if ($force === 0) {
if ($file->isSubShare()) {
return Base::retError('此文件夹内已有共享文件夹', [], -3001);
}
}
if (FileUser::whereFileId($file->id)->count() + count($userids) > 100) {
return Base::retError('共享人数上限100个成员');
}
@@ -735,13 +980,13 @@ class FileController extends AbstractController
}
}
//
$file->setShare();
$file->updataShare();
$file->pushMsg($action, $action == "delete" ? null : $file, $array);
return Base::retSuccess($action == "delete" ? "删除成功" : "设置成功", $file);
}
/**
* @api {get} api/file/share/out 14. 退出共享
* @api {get} api/file/share/out 16. 退出共享
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -765,23 +1010,17 @@ class FileController extends AbstractController
if ($file->userid == $user->userid) {
return Base::retError('不能退出自己共享的文件');
}
if (FileUser::where([
'file_id' => $file->id,
'userid' => 0,
])->exists()) {
if (FileUser::whereFileId($file->id)->whereUserid(0)->exists()) {
return Base::retError('无法退出共享所有人的文件或文件夹');
}
FileUser::where([
'file_id' => $file->id,
'userid' => $user->userid,
])->delete();
FileUser::deleteFileUser($file->id, $user->userid);
//
$file->setShare();
$file->updataShare();
return Base::retSuccess("退出成功");
}
/**
* @api {get} api/file/link 15. 获取链接
* @api {get} api/file/link 17. 获取链接
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -799,20 +1038,21 @@ class FileController extends AbstractController
*/
public function link()
{
User::auth();
$user = User::auth();
//
$id = intval(Request::input('id'));
$refresh = Request::input('refresh', 'no');
//
$file = File::permissionFind($id, 1000);
$file = File::permissionFind($id);
if ($file->type == 'folder') {
return Base::retError('文件夹暂不支持此功能');
}
//
$fileLink = FileLink::whereFileId($file->id)->first();
$fileLink = FileLink::whereFileId($file->id)->whereUserid($user->userid)->first();
if (empty($fileLink)) {
$fileLink = FileLink::createInstance([
'file_id' => $file->id,
'userid' => $user->userid,
'code' => Base::generatePassword(64),
]);
$fileLink->save();

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
use App\Exceptions\ApiException;
use App\Models\AbstractModel;
use App\Models\File;
use App\Models\Project;
use App\Models\ProjectColumn;
use App\Models\ProjectFlow;
@@ -12,13 +13,18 @@ use App\Models\ProjectInvite;
use App\Models\ProjectLog;
use App\Models\ProjectTask;
use App\Models\ProjectTaskFile;
use App\Models\ProjectTaskFlowChange;
use App\Models\ProjectUser;
use App\Models\User;
use App\Models\WebSocketDialog;
use App\Module\Base;
use App\Module\BillExport;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Madzipper;
use Request;
use Response;
use Session;
/**
* @apiDefine project
@@ -203,6 +209,10 @@ class ProjectController extends AbstractController
* @apiParam {String} name 项目名称
* @apiParam {String} [desc] 项目介绍
* @apiParam {String} [columns] 列表格式列表名称1,列表名称2
* @apiParam {String} [flow] 开启流程
* - open: 开启
* - close: 关闭(默认)
* @apiParam {Number} [personal] 个人项目,注册成功时创建(仅支持创建一个个人项目)
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -211,68 +221,8 @@ class ProjectController extends AbstractController
public function add()
{
$user = User::auth();
// 项目名称
$name = trim(Request::input('name', ''));
$desc = trim(Request::input('desc', ''));
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(",", Request::input('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' => $user->userid,
]);
AbstractModel::transaction(function() use ($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(null, $project->userid, 'project');
if (empty($dialog)) {
throw new ApiException('创建项目聊天室失败');
}
$project->dialog_id = $dialog->id;
$project->save();
});
//
$data = Project::find($project->id);
$data->addLog("创建项目");
$data->pushMsg('add', $data);
return Base::retSuccess('添加成功', $data);
return Project::createProject(Request::all(), $user->userid);
}
/**
@@ -368,6 +318,8 @@ class ProjectController extends AbstractController
}
$project->syncDialogUser();
$project->addLog("修改项目成员");
$project->user_simple = count($array) . "|" . implode(",", array_slice($array, 0, 3));
$project->save();
return $deleteUser->toArray();
});
//
@@ -594,13 +546,14 @@ class ProjectController extends AbstractController
if (!is_array($item['task'])) continue;
$index = 0;
foreach ($item['task'] as $task_id) {
ProjectTask::whereId($task_id)->whereProjectId($project->id)->update([
if (ProjectTask::whereId($task_id)->whereProjectId($project->id)->whereCompleteAt(null)->update([
'column_id' => $item['id'],
'sort' => $index
]);
ProjectTask::whereParentId($task_id)->whereProjectId($project->id)->update([
'column_id' => $item['id'],
]);
])) {
ProjectTask::whereParentId($task_id)->whereProjectId($project->id)->update([
'column_id' => $item['id'],
]);
}
$index++;
}
}
@@ -876,8 +829,13 @@ class ProjectController extends AbstractController
* - yes已完成
* - no未完成
* @apiParam {String} [archived] 归档状态
* - all所有
* - yes已归档
* - no未归档默认
* @apiParam {String} [deleted] 是否读取已删除
* - all所有
* - yes已删除
* - no未删除默认
* @apiParam {Object} sorts 排序方式
* - sorts.complete_at 完成时间asc|desc
* - sorts.archived_at 归档时间asc|desc
@@ -900,6 +858,7 @@ class ProjectController extends AbstractController
$time_before = Request::input('time_before');
$complete = Request::input('complete', 'all');
$archived = Request::input('archived', 'no');
$deleted = Request::input('deleted', 'no');
$keys = Request::input('keys');
$sorts = Request::input('sorts');
$keys = is_array($keys) ? $keys : [];
@@ -911,7 +870,9 @@ class ProjectController extends AbstractController
//
$scopeAll = false;
if ($parent_id > 0) {
ProjectTask::userTask($parent_id);
$isArchived = str_replace(['all', 'yes', 'no'], [null, false, true], $archived);
$isDeleted = str_replace(['all', 'yes', 'no'], [null, false, true], $deleted);
ProjectTask::userTask($parent_id, $isArchived, $isDeleted);
$scopeAll = true;
$builder->where('project_tasks.parent_id', $parent_id);
} elseif ($parent_id === -1) {
@@ -954,8 +915,14 @@ class ProjectController extends AbstractController
$builder->whereNull('project_tasks.archived_at');
}
//
if ($deleted == 'all') {
$builder->withTrashed();
} elseif ($deleted == 'yes') {
$builder->onlyTrashed();
}
//
foreach ($sorts as $column => $direction) {
if (!in_array($column, ['complete_at', 'archived_at', 'end_at'])) continue;
if (!in_array($column, ['complete_at', 'archived_at', 'end_at', 'deleted_at'])) continue;
if (!in_array($direction, ['asc', 'desc'])) continue;
$builder->orderBy('project_tasks.' . $column, $direction);
}
@@ -966,7 +933,208 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/one 19. 获取单个任务信息
* @api {get} api/project/task/export 19. 导出任务(限管理员)
*
* @apiDescription 导出指定范围任务已完成、未完成、已归档返回下载地址需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__export
*
* @apiParam {Array} [userid] 指定会员,如:[1, 2]
* @apiParam {Array} [time] 指定时间范围,如:['2020-12-12', '2020-12-30']
* @apiParam {String} [type]
* - createdTime 任务创建时间
* - taskTime 任务计划时间(默认)
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function task__export()
{
$user = User::auth('admin');
//
$userid = Base::arrayRetainInt(Request::input('userid'), true);
$time = Request::input('time');
$type = Request::input('type', 'taskTime');
if (empty($userid) || empty($time)) {
return Base::retError('参数错误');
}
if (count($userid) > 20) {
return Base::retError('导出会员限制最多20个');
}
if (!(is_array($time) && Base::isDateOrTime($time[0]) && Base::isDateOrTime($time[1]))) {
return Base::retError('时间选择错误');
}
if (Carbon::parse($time[1])->timestamp - Carbon::parse($time[0])->timestamp > 90 * 86400) {
return Base::retError('时间范围限制最大90天');
}
$headings = [];
$headings[] = '任务ID';
$headings[] = '父级任务ID';
$headings[] = '所属项目';
$headings[] = '任务标题';
$headings[] = '任务开始时间';
$headings[] = '任务结束时间';
$headings[] = '完成时间';
$headings[] = '归档时间';
$headings[] = '任务计划用时';
$headings[] = '实际完成用时';
$headings[] = '超时时间';
$headings[] = '开发用时';
$headings[] = '验收/测试用时';
$headings[] = '负责人';
$headings[] = '创建人';
$headings[] = '状态';
$datas = [];
//
$builder = ProjectTask::select(['project_tasks.*', 'project_task_users.userid as ownerid'])
->join('project_task_users', 'project_tasks.id', '=', 'project_task_users.task_id')
->where('project_task_users.owner', 1)
->whereIn('project_task_users.userid', $userid)
->betweenTime(Carbon::parse($time[0])->startOfDay(), Carbon::parse($time[1])->endOfDay(), $type);
$builder->orderByDesc('project_tasks.id')->chunk(100, function ($tasks) use (&$datas) {
/** @var ProjectTask $task */
foreach ($tasks as $task) {
$flowChanges = ProjectTaskFlowChange::whereTaskId($task->id)->get();
$testTime = 0;//验收/测试时间
$taskStartTime = $task->start_at ? Carbon::parse($task->start_at)->timestamp : Carbon::parse($task->created_at)->timestamp;
$taskCompleteTime = $task->complete_at ? Carbon::parse($task->complete_at)->timestamp : time();
$totalTime = $taskCompleteTime - $taskStartTime; //开发测试总用时
foreach ($flowChanges as $change) {
if (!str_contains($change->before_flow_item_name, 'end')) {
$upOne = ProjectTaskFlowChange::where('id', '<', $change->id)->whereTaskId($task->id)->orderByDesc('id')->first();
if ($upOne) {
if (str_contains($change->before_flow_item_name, 'test') || str_contains($change->before_flow_item_name, '测试') || strpos($change->before_flow_item_name, '验收') !== false) {
$testCtime = Carbon::parse($change->created_at)->timestamp;
$tTime = Carbon::parse($upOne->created_at)->timestamp;
$tMinusNum = $testCtime - $tTime;
$testTime += $tMinusNum;
}
}
}
}
if (!$task->complete_at) {
$lastChange = ProjectTaskFlowChange::whereTaskId($task->id)->orderByDesc('id')->first();
$nowTime = time();
$unFinishTime = $nowTime - Carbon::parse($lastChange->created_at)->timestamp;
if (str_contains($lastChange->after_flow_item_name, 'test') || str_contains($lastChange->after_flow_item_name, '测试') || strpos($lastChange->after_flow_item_name, '验收') !== false) {
$testTime += $unFinishTime;
}
}
$developTime = $totalTime - $testTime;//开发时间
$planTime = '-';//任务计划用时
$overTime = '-';//超时时间
if ($task->end_at) {
$startTime = Carbon::parse($task->start_at)->timestamp;
$endTime = Carbon::parse($task->end_at)->timestamp;
$planTotalTime = $endTime - $startTime;
$residueTime = $planTotalTime - $totalTime;
if ($residueTime < 0) {
$overTime = Base::timeFormat(abs($residueTime));
}
$planTime = Base::timeDiff($startTime, $endTime);
}
$actualTime = $task->complete_at ? $totalTime : 0;//实际完成用时
$statusText = '未完成';
if ($task->flow_item_name) {
if (str_contains($task->flow_item_name, '已取消')) {
$statusText = '已取消';
$actualTime = 0;
$testTime = 0;
$developTime = 0;
$overTime = '-';
} elseif (str_contains($task->flow_item_name, '已完成')) {
$statusText = '已完成';
}
} elseif ($task->complete_at) {
$statusText = '已完成';
}
$datas[] = [
$task->id,
$task->parent_id ?: '-',
Base::filterEmoji($task->project?->name) ?: '-',
Base::filterEmoji($task->name),
$task->start_at ?: '-',
$task->end_at ?: '-',
$task->complete_at ?: '-',
$task->archived_at ?: '-',
$planTime ?: '-',
$actualTime ? Base::timeFormat($actualTime) : '-',
$overTime,
$developTime > 0 ? Base::timeFormat($developTime) : '-',
$testTime > 0 ? Base::timeFormat($testTime) : '-',
Base::filterEmoji(User::userid2nickname($task->ownerid)) . " (ID: {$task->ownerid})",
Base::filterEmoji(User::userid2nickname($task->userid)) . " (ID: {$task->userid})",
$statusText
];
}
});
//
$fileName = User::userid2nickname($userid[0]) ?: $userid[0];
if (count($userid) > 1) {
$fileName .= "" . count($userid) . "位成员";
}
$fileName .= '任务统计_' . Base::time() . '.xls';
$filePath = "temp/task/export/" . date("Ym", Base::time());
$res = BillExport::create()->setHeadings($headings)->setData($datas)->store($filePath . "/" . $fileName);
if ($res != 1) {
return Base::retError('导出失败,' . $fileName . '');
}
$xlsPath = storage_path("app/" . $filePath . "/" . $fileName);
$zipFile = "app/" . $filePath . "/" . Base::rightDelete($fileName, '.xls') . ".zip";
$zipPath = storage_path($zipFile);
if (file_exists($zipPath)) {
Base::deleteDirAndFile($zipPath, true);
}
try {
Madzipper::make($zipPath)->add($xlsPath)->close();
} catch (\Throwable) {
}
//
if (file_exists($zipPath)) {
$base64 = base64_encode(Base::array2string([
'file' => $zipFile,
]));
Session::put('task::export:userid', $user->userid);
return Base::retSuccess('success', [
'size' => Base::twoFloat(filesize($zipPath) / 1024, true),
'url' => Base::fillUrl('api/project/task/down?key=' . urlencode($base64)),
]);
} else {
return Base::retError('打包失败,请稍后再试...');
}
}
/**
* @api {get} api/project/task/down 20. 导出任务(限管理员)
*
* @apiDescription 导出指定范围任务已完成、未完成、已归档返回下载地址需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__down
*
* @apiParam {String} key 通过export接口得到的下载钥匙
*
* @apiSuccess {File} data 返回数据(直接下载文件)
*/
public function task__down()
{
$userid = Session::get('task::export:userid');
if (empty($userid)) {
return Base::ajaxError("请求已过期,请重新导出!", [], 0, 502);
}
//
$array = Base::string2array(base64_decode(urldecode(Request::input('key'))));
$file = $array['file'];
if (empty($file) || !file_exists(storage_path($file))) {
return Base::ajaxError("文件不存在!", [], 0, 502);
}
return response()->download(storage_path($file));
}
/**
* @api {get} api/project/task/one 21. 获取单个任务信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -974,6 +1142,10 @@ class ProjectController extends AbstractController
* @apiName task__one
*
* @apiParam {Number} task_id 任务ID
* @apiParam {String} [archived] 归档状态
* - all所有
* - yes已归档
* - no未归档默认
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -984,8 +1156,10 @@ class ProjectController extends AbstractController
User::auth();
//
$task_id = intval(Request::input('task_id'));
$archived = Request::input('archived', 'no');
//
$task = ProjectTask::userTask($task_id, true, false, ['taskUser', 'taskTag']);
$isArchived = str_replace(['all', 'yes', 'no'], [null, false, true], $archived);
$task = ProjectTask::userTask($task_id, $isArchived, true, false, ['taskUser', 'taskTag']);
//
$data = $task->toArray();
$data['project_name'] = $task->project?->name;
@@ -994,7 +1168,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/content 20. 获取任务详细描述
* @api {get} api/project/task/content 22. 获取任务详细描述
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1013,13 +1187,16 @@ class ProjectController extends AbstractController
//
$task_id = intval(Request::input('task_id'));
//
$task = ProjectTask::userTask($task_id);
$task = ProjectTask::userTask($task_id, null);
//
return Base::retSuccess('success', $task->content ?: json_decode('{}'));
if (empty($task->content)) {
return Base::retSuccess('success', json_decode('{}'));
}
return Base::retSuccess('success', $task->content->getContentInfo());
}
/**
* @api {get} api/project/task/files 21. 获取任务文件列表
* @api {get} api/project/task/files 23. 获取任务文件列表
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1038,13 +1215,13 @@ class ProjectController extends AbstractController
//
$task_id = intval(Request::input('task_id'));
//
$task = ProjectTask::userTask($task_id);
$task = ProjectTask::userTask($task_id, null);
//
return Base::retSuccess('success', $task->taskFile);
}
/**
* @api {get} api/project/task/filedelete 22. 删除任务文件
* @api {get} api/project/task/filedelete 24. 删除任务文件
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@@ -1068,7 +1245,7 @@ class ProjectController extends AbstractController
return Base::retError('文件不存在或已被删除');
}
//
$task = ProjectTask::userTask($file->task_id, true, true);
$task = ProjectTask::userTask($file->task_id, true, true, true);
//
$task->pushMsg('filedelete', $file);
$file->delete();
@@ -1077,7 +1254,85 @@ class ProjectController extends AbstractController
}
/**
* @api {post} api/project/task/add 23. 添加任务
* @api {get} api/project/task/filedetail 25. 获取任务文件详情
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__filedetail
*
* @apiParam {Number} file_id 文件ID
* @apiParam {String} only_update_at 仅获取update_at字段
* - no (默认)
* - yes
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function task__filedetail()
{
User::auth();
//
$file_id = intval(Request::input('file_id'));
$only_update_at = Request::input('only_update_at', 'no');
//
$file = ProjectTaskFile::find($file_id);
if (empty($file)) {
return Base::retError("文件不存在");
}
//
if ($only_update_at == 'yes') {
return Base::retSuccess('success', [
'id' => $file->id,
'update_at' => Carbon::parse($file->updated_at)->toDateTimeString()
]);
}
//
$data = $file->toArray();
$data['path'] = $file->getRawOriginal('path');
//
ProjectTask::userTask($file->task_id, null);
//
return Base::retSuccess('success', File::formatFileData($data));
}
/**
* @api {get} api/project/task/filedown 26. 下载任务文件
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__filedown
*
* @apiParam {Number} file_id 文件ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function task__filedown()
{
User::auth();
//
$file_id = intval(Request::input('file_id'));
//
$file = ProjectTaskFile::find($file_id);
if (empty($file)) {
abort(403, "This file not exist.");
}
//
try {
ProjectTask::userTask($file->task_id, null);
} catch (\Throwable $e) {
abort(403, $e->getMessage() ?: "This file not support download.");
}
//
return Response::download(public_path($file->getRawOriginal('path')), $file->name);
}
/**
* @api {post} api/project/task/add 27. 添加任务
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1148,7 +1403,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/addsub 24. 添加子任务
* @api {get} api/project/task/addsub 28. 添加子任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@@ -1169,7 +1424,7 @@ class ProjectController extends AbstractController
$task_id = intval(Request::input('task_id'));
$name = Request::input('name');
//
$task = ProjectTask::userTask($task_id, true, true);
$task = ProjectTask::userTask($task_id, true, true, true);
if ($task->complete_at) {
return Base::retError('主任务已完成无法添加子任务');
}
@@ -1188,7 +1443,7 @@ class ProjectController extends AbstractController
}
/**
* @api {post} api/project/task/update 25. 修改任务、子任务
* @api {post} api/project/task/update 29. 修改任务、子任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@@ -1221,7 +1476,7 @@ class ProjectController extends AbstractController
parse_str(Request::getContent(), $data);
$task_id = intval($data['task_id']);
//
$task = ProjectTask::userTask($task_id, true, 2);
$task = ProjectTask::userTask($task_id, true, true, 2);
// 更新任务
$updateMarking = [];
$task->updateTask($data, $updateMarking);
@@ -1234,73 +1489,7 @@ class ProjectController extends AbstractController
}
/**
* @api {post} api/project/task/upload 26. 上传文件
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__upload
*
* @apiParam {Number} task_id 任务ID
* @apiParam {String} [filename] post-文件名称
* @apiParam {String} [image64] post-base64图片二选一
* @apiParam {File} [files] post-文件对象(二选一)
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function task__upload()
{
$user = User::auth();
//
$task_id = Base::getPostInt('task_id');
//
$task = ProjectTask::userTask($task_id, true, true);
//
$path = "uploads/task/" . $task->id . "/";
$image64 = Base::getPostValue('image64');
$fileName = Base::getPostValue('filename');
if ($image64) {
$data = Base::image64save([
"image64" => $image64,
"path" => $path,
"fileName" => $fileName,
]);
} else {
$data = Base::upload([
"file" => Request::file('files'),
"type" => 'file',
"path" => $path,
"fileName" => $fileName,
]);
}
//
if (Base::isError($data)) {
return Base::retError($data['msg']);
} else {
$fileData = $data['data'];
$file = ProjectTaskFile::createInstance([
'project_id' => $task->project_id,
'task_id' => $task->id,
'name' => $fileData['name'],
'size' => $fileData['size'] * 1024,
'ext' => $fileData['ext'],
'path' => $fileData['path'],
'thumb' => Base::unFillUrl($fileData['thumb']),
'userid' => $user->userid,
]);
$file->save();
//
$file = ProjectTaskFile::find($file->id);
$task->addLog("上传文件:" . $file->name);
$task->pushMsg('upload', $file);
return Base::retSuccess("上传成功", $file);
}
}
/**
* @api {get} api/project/task/dialog 27. 创建/获取聊天室
* @api {get} api/project/task/dialog 30. 创建/获取聊天室
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1347,7 +1536,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/archived 28. 归档任务
* @api {get} api/project/task/archived 31. 归档任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@@ -1370,7 +1559,7 @@ class ProjectController extends AbstractController
$task_id = intval(Request::input('task_id'));
$type = Request::input('type', 'add');
//
$task = ProjectTask::userTask($task_id, $type == 'add', true);
$task = ProjectTask::userTask($task_id, $type == 'add', true, true);
//
if ($task->parent_id > 0) {
return Base::retError('子任务不支持此功能');
@@ -1381,11 +1570,15 @@ class ProjectController extends AbstractController
} elseif ($type == 'add') {
$task->archivedTask(Carbon::now());
}
return Base::retSuccess('操作成功', ['id' => $task->id]);
return Base::retSuccess('操作成功', [
'id' => $task->id,
'archived_at' => $task->archived_at,
'archived_userid' => $task->archived_userid,
]);
}
/**
* @api {get} api/project/task/remove 29. 删除任务
* @api {get} api/project/task/remove 32. 删除任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@@ -1393,6 +1586,9 @@ class ProjectController extends AbstractController
* @apiName task__remove
*
* @apiParam {Number} task_id 任务ID
* @apiParam {String} type
* - recovery: 还原
* - delete: 删除(默认)
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -1403,15 +1599,20 @@ class ProjectController extends AbstractController
User::auth();
//
$task_id = intval(Request::input('task_id'));
$type = Request::input('type', 'delete');
//
$task = ProjectTask::userTask($task_id, null, true);
//
$task->deleteTask();
return Base::retSuccess('删除成功', ['id' => $task->id]);
$task = ProjectTask::userTask($task_id, null, $type !== 'recovery', true);
if ($type == 'recovery') {
$task->recoveryTask();
return Base::retSuccess('操作成功', ['id' => $task->id]);
} else {
$task->deleteTask();
return Base::retSuccess('删除成功', ['id' => $task->id]);
}
}
/**
* @api {get} api/project/task/resetfromlog 29. 根据日志重置任务
* @api {get} api/project/task/resetfromlog 33. 根据日志重置任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@@ -1435,7 +1636,7 @@ class ProjectController extends AbstractController
return Base::retError('记录不存在');
}
//
$task = ProjectTask::userTask($projectLog->task_id, null, true);
$task = ProjectTask::userTask($projectLog->task_id, true, true, true);
//
$record = $projectLog->record;
if ($record['flow'] && is_array($record['flow'])) {
@@ -1458,6 +1659,7 @@ class ProjectController extends AbstractController
$task->updateTask($data, $updateMarking);
//
$data = ProjectTask::oneTask($task->id)->toArray();
$data["flow_item_name"] = $newFlowItem->status . "|" . $newFlowItem->name;
$data['update_marking'] = $updateMarking ?: json_decode('{}');
$task->pushMsg('update', $data);
//
@@ -1469,7 +1671,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/flow 29. 任务工作流信息
* @api {get} api/project/task/flow 34. 任务工作流信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1488,7 +1690,7 @@ class ProjectController extends AbstractController
//
$task_id = intval(Request::input('task_id'));
//
$projectTask = ProjectTask::select(['id', 'project_id', 'complete_at', 'flow_item_id', 'flow_item_name'])->find($task_id);
$projectTask = ProjectTask::select(['id', 'project_id', 'complete_at', 'flow_item_id', 'flow_item_name'])->withTrashed()->find($task_id);
if (empty($projectTask)) {
return Base::retError('任务不存在', [ 'task_id' => $task_id ], -4002);
}
@@ -1551,9 +1753,9 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/flow/list 29. 工作流列表
* @api {get} api/project/flow/list 35. 工作流列表
*
* @apiDescription 需要token身份(限:项目负责人)
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName flow__list
@@ -1570,14 +1772,14 @@ class ProjectController extends AbstractController
//
$project_id = intval(Request::input('project_id'));
//
$project = Project::userProject($project_id, true, true);
$project = Project::userProject($project_id, true);
//
$list = ProjectFlow::with(['ProjectFlowItem'])->whereProjectId($project->id)->get();
return Base::retSuccess('success', $list);
}
/**
* @api {post} api/project/flow/save 29. 保存工作流
* @api {post} api/project/flow/save 36. 保存工作流
*
* @apiDescription 需要token身份项目负责人
* @apiVersion 1.0.0
@@ -1607,96 +1809,11 @@ class ProjectController extends AbstractController
//
$project = Project::userProject($project_id, true, true);
//
return AbstractModel::transaction(function() use ($project, $flows) {
$projectFlow = ProjectFlow::whereProjectId($project->id)->first();
if (empty($projectFlow)) {
$projectFlow = ProjectFlow::createInstance([
'project_id' => $project->id,
'name' => 'Default'
]);
if (!$projectFlow->save()) {
throw new ApiException('工作流创建失败');
}
}
//
$ids = [];
$idc = [];
$hasStart = false;
$hasEnd = false;
foreach ($flows as $item) {
$id = intval($item['id']);
$turns = Base::arrayRetainInt($item['turns'] ?: [], true);
$userids = Base::arrayRetainInt($item['userids'] ?: [], true);
$usertype = trim($item['usertype']);
$userlimit = intval($item['userlimit']);
if ($usertype == 'replace' && empty($userids)) {
throw new ApiException("状态[{$item['name']}]设置错误,设置流转模式时必须填写状态负责人");
}
if ($userlimit && empty($userids)) {
throw new ApiException("状态[{$item['name']}]设置错误,设置限制负责人时必须填写状态负责人");
}
$flow = ProjectFlowItem::updateInsert([
'id' => $id,
'project_id' => $project->id,
'flow_id' => $projectFlow->id,
], [
'name' => trim($item['name']),
'status' => trim($item['status']),
'sort' => intval($item['sort']),
'turns' => $turns,
'userids' => $userids,
'usertype' => trim($item['usertype']),
'userlimit' => $userlimit,
]);
if ($flow) {
$ids[] = $flow->id;
if ($flow->id != $id) {
$idc[$id] = $flow->id;
}
if ($flow->status == 'start') {
$hasStart = true;
}
if ($flow->status == 'end') {
$hasEnd = true;
}
}
}
if (!$hasStart) {
throw new ApiException('至少需要1个开始状态');
}
if (!$hasEnd) {
throw new ApiException('至少需要1个结束状态');
}
ProjectFlowItem::whereFlowId($projectFlow->id)->whereNotIn('id', $ids)->chunk(100, function($list) {
foreach ($list as $item) {
$item->deleteFlowItem();
}
});
//
$projectFlow = ProjectFlow::with(['projectFlowItem'])->whereProjectId($project->id)->find($projectFlow->id);
$itemIds = $projectFlow->projectFlowItem->pluck('id')->toArray();
foreach ($projectFlow->projectFlowItem as $item) {
$turns = $item->turns;
foreach ($idc as $oid => $nid) {
if (in_array($oid, $turns)) {
$turns = array_diff($turns, [$oid]);
$turns[] = $nid;
}
}
if (!in_array($item->id, $turns)) {
$turns[] = $item->id;
}
$turns = array_values(array_filter(array_unique(array_intersect($turns, $itemIds))));
sort($turns);
$item->turns = $turns;
ProjectFlowItem::whereId($item->id)->update([ 'turns' => Base::array2json($turns) ]);
}
return Base::retSuccess('保存成功', $projectFlow);
});
return Base::retSuccess('保存成功', $project->addFlow($flows));
}
/**
* @api {get} api/project/flow/delete 29. 删除工作流
* @api {get} api/project/flow/delete 37. 删除工作流
*
* @apiDescription 需要token身份项目负责人
* @apiVersion 1.0.0
@@ -1728,7 +1845,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/log/lists 30. 获取项目、任务日志
* @api {get} api/project/log/lists 38. 获取项目、任务日志
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1754,7 +1871,7 @@ class ProjectController extends AbstractController
//
$builder = ProjectLog::select(["*"]);
if ($task_id > 0) {
$task = ProjectTask::userTask($task_id);
$task = ProjectTask::userTask($task_id, null);
$builder->whereTaskId($task->id);
} else {
$project = Project::userProject($project_id);
@@ -1778,4 +1895,34 @@ class ProjectController extends AbstractController
//
return Base::retSuccess('success', $list);
}
/**
* @api {get} api/project/top 39. 项目置顶
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName top
*
* @apiParam {Number} project_id 项目ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function top()
{
$user = User::auth();
$projectId = intval(Request::input('project_id'));
$projectUser = ProjectUser::whereUserid($user->userid)->whereProjectId($projectId)->first();
if (!$projectUser) {
return Base::retError("项目不存在");
}
$projectUser->top_at = $projectUser->top_at ? null : Carbon::now();
$projectUser->save();
return Base::retSuccess("success", [
'id' => $projectUser->project_id,
'top_at' => $projectUser->top_at?->toDateTimeString(),
]);
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api;
use App\Exceptions\ApiException;
use App\Models\AbstractModel;
use App\Models\ProjectTask;
use App\Models\Report;
use App\Models\ReportReceive;
@@ -31,8 +32,9 @@ class ReportController extends AbstractController
* @apiGroup report
* @apiName my
*
* @apiParam {String} [type] 汇报类型weekly:周报daily:日报
* @apiParam {Array} [created_at] 汇报时间
* @apiParam {Object} [keys] 搜索条件
* - keys.type: 汇报类型weekly:周报daily:日报
* - keys.created_at: 汇报时间
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:20最大:50
*
@@ -43,17 +45,19 @@ class ReportController extends AbstractController
public function my(): array
{
$user = User::auth();
// 搜索当前用户
//
$builder = Report::with(['receivesUser'])->whereUserid($user->userid);
$type = trim(Request::input('type'));
$createAt = Request::input('created_at');
in_array($type, [Report::WEEKLY, Report::DAILY]) && $builder->whereType($type);
$whereArray = [];
if (is_array($createAt)) {
if ($createAt[0] > 0) $whereArray[] = ['created_at', '>=', date('Y-m-d H:i:s', Base::dayTimeF($createAt[0]))];
if ($createAt[1] > 0) $whereArray[] = ['created_at', '<=', date('Y-m-d H:i:s', Base::dayTimeE($createAt[1]))];
$keys = Request::input('keys');
if (is_array($keys)) {
if (in_array($keys['type'], [Report::WEEKLY, Report::DAILY])) {
$builder->whereType($keys['type']);
}
if (is_array($keys['created_at'])) {
if ($keys['created_at'][0] > 0) $builder->where('created_at', '>=', date('Y-m-d H:i:s', Base::dayTimeF($keys['created_at'][0])));
if ($keys['created_at'][1] > 0) $builder->where('created_at', '<=', date('Y-m-d H:i:s', Base::dayTimeE($keys['created_at'][1])));
}
}
$list = $builder->where($whereArray)->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
$list = $builder->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
return Base::retSuccess('success', $list);
}
@@ -64,9 +68,10 @@ class ReportController extends AbstractController
* @apiGroup report
* @apiName receive
*
* @apiParam {String} [username] 会员名
* @apiParam {String} [type] 汇报类型weekly:周报daily:日报
* @apiParam {Array} [created_at] 汇报时间
* @apiParam {Object} [keys] 搜索条件
* - keys.key: 关键词
* - keys.type: 汇报类型weekly:周报daily:日报
* - keys.created_at: 汇报时间
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:20最大:50
*
@@ -81,21 +86,24 @@ class ReportController extends AbstractController
$builder->whereHas("receivesUser", function ($query) use ($user) {
$query->where("report_receives.userid", $user->userid);
});
$type = trim(Request::input('type'));
$createAt = Request::input('created_at');
$username = trim(Request::input('username', ''));
$builder->whereHas('sendUser', function ($query) use ($username) {
if (!empty($username)) {
$query->where('users.email', 'LIKE', '%' . $username . '%');
$keys = Request::input('keys');
if (is_array($keys)) {
if ($keys['key']) {
$builder->where(function($query) use ($keys) {
$query->whereHas('sendUser', function ($q2) use ($keys) {
$q2->where("users.email", "LIKE", "%{$keys['key']}%");
})->orWhere("title", "LIKE", "%{$keys['key']}%");
});
}
if (in_array($keys['type'], [Report::WEEKLY, Report::DAILY])) {
$builder->whereType($keys['type']);
}
if (is_array($keys['created_at'])) {
if ($keys['created_at'][0] > 0) $builder->where('created_at', '>=', date('Y-m-d H:i:s', Base::dayTimeF($keys['created_at'][0])));
if ($keys['created_at'][1] > 0) $builder->where('created_at', '<=', date('Y-m-d H:i:s', Base::dayTimeE($keys['created_at'][1])));
}
});
in_array($type, [Report::WEEKLY, Report::DAILY]) && $builder->whereType($type);
$whereArray = [];
if (is_array($createAt)) {
if ($createAt[0] > 0) $whereArray[] = ['created_at', '>=', date('Y-m-d H:i:s', Base::dayTimeF($createAt[0]))];
if ($createAt[1] > 0) $whereArray[] = ['created_at', '<=', date('Y-m-d H:i:s', Base::dayTimeE($createAt[1]))];
}
$list = $builder->where($whereArray)->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
$list = $builder->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
if ($list->items()) {
foreach ($list->items() as $item) {
$item->receive_time = ReportReceive::query()->whereRid($item["id"])->whereUserid($user->userid)->value("receive_time");
@@ -173,7 +181,7 @@ class ReportController extends AbstractController
}
// 在事务中运行
Report::transaction(function () use ($input, $user) {
return AbstractModel::transaction(function () use ($input, $user) {
$id = $input["id"];
if ($id) {
// 编辑
@@ -188,7 +196,7 @@ class ReportController extends AbstractController
$sign = Report::generateSign($input["type"], $input["offset"]);
// 检查唯一标识是否存在
if (empty($input["id"])) {
if (Report::query()->whereSign($sign)->count() > 0)
if (Report::query()->whereSign($sign)->whereType($input["type"])->count() > 0)
throw new ApiException("请勿重复提交工作汇报");
}
$report = Report::createInstance([
@@ -224,8 +232,9 @@ class ReportController extends AbstractController
];
Task::deliver(new PushTask($params, false));
}
//
return Base::retSuccess('保存成功', $report);
});
return Base::retSuccess('保存成功');
}
/**
@@ -273,7 +282,7 @@ class ReportController extends AbstractController
}
// 生成唯一标识
$sign = Report::generateSign($type, 0, Carbon::instance($start_time));
$one = Report::query()->whereSign($sign)->first();
$one = Report::whereSign($sign)->whereType($type)->first();
// 如果已经提交了相关汇报
if ($one && $id > 0) {
return Base::retSuccess('success', [
@@ -283,7 +292,6 @@ class ReportController extends AbstractController
]);
}
// 已完成的任务
$completeContent = "";
$complete_task = ProjectTask::query()
@@ -362,6 +370,7 @@ class ReportController extends AbstractController
*/
public function detail(): array
{
$user = User::auth();
$id = intval(trim(Request::input("id")));
if (empty($id))
return Base::retError("缺少ID参数");
@@ -369,7 +378,6 @@ class ReportController extends AbstractController
$one = Report::getOne($id);
$one->type_val = $one->getRawOriginal("type");
$user = User::auth();
// 标记为已读
if (!empty($one->receivesUser)) {
foreach ($one->receivesUser as $item) {

View File

@@ -4,6 +4,8 @@ namespace App\Http\Controllers\Api;
use App\Models\User;
use App\Module\Base;
use Guanguans\Notify\Factory;
use Guanguans\Notify\Messages\EmailMessage;
use Request;
use Response;
@@ -25,7 +27,7 @@ class SystemController extends AbstractController
* @apiParam {String} type
* - get: 获取(默认)
* - all: 获取所有(需要管理员权限)
* - save: 保存设置参数regreg_invitelogin_codepassword_policyproject_invitechat_nicknameauto_archivedarchived_day
* - save: 保存设置(参数:['reg', 'reg_invite', 'login_code', 'password_policy', 'project_invite', 'chat_nickname', 'auto_archived', 'archived_day', 'start_home', 'home_footer']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -41,7 +43,18 @@ class SystemController extends AbstractController
User::auth('admin');
$all = Request::input();
foreach ($all AS $key => $value) {
if (!in_array($key, ['reg', 'reg_invite', 'login_code', 'password_policy', 'project_invite', 'chat_nickname', 'auto_archived', 'archived_day'])) {
if (!in_array($key, [
'reg',
'reg_invite',
'login_code',
'password_policy',
'project_invite',
'chat_nickname',
'auto_archived',
'archived_day',
'start_home',
'home_footer'
])) {
unset($all[$key]);
}
}
@@ -72,12 +85,164 @@ class SystemController extends AbstractController
$setting['chat_nickname'] = $setting['chat_nickname'] ?: 'optional';
$setting['auto_archived'] = $setting['auto_archived'] ?: 'close';
$setting['archived_day'] = floatval($setting['archived_day']) ?: 7;
$setting['start_home'] = $setting['start_home'] ?: 'close';
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/demo 02. 获取演示账号
* @api {get} api/system/setting/email 02. 获取邮箱设置、保存邮箱设置(限管理员)
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName setting__email
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['smtp_server', 'port', 'account', 'password', 'reg_verify', 'notice', 'task_start_minute', 'task_remind_hours', 'task_remind_hours2', 'notice_msg', 'msg_unread_user_minute', 'msg_unread_group_minute']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function setting__email()
{
User::auth('admin');
//
$type = trim(Request::input('type'));
if ($type == 'save') {
if (env("SYSTEM_SETTING") == 'disabled') {
return Base::retError('当前环境禁止修改');
}
$all = Request::input();
foreach ($all as $key => $value) {
if (!in_array($key, [
'smtp_server',
'port',
'account',
'password',
'reg_verify',
'notice',
'task_start_minute',
'task_remind_hours',
'task_remind_hours2',
'notice_msg',
'msg_unread_user_minute',
'msg_unread_group_minute'
])) {
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);
//
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
@@ -101,12 +266,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错误
@@ -146,7 +315,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
@@ -175,7 +391,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
@@ -190,7 +406,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
@@ -207,7 +423,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
@@ -224,7 +440,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
@@ -241,15 +457,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 返回信息(错误描述)
@@ -260,11 +483,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) {
@@ -291,7 +517,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
@@ -309,7 +535,7 @@ class SystemController extends AbstractController
if (User::userid() === 0) {
return Base::retError('身份失效,等重新登录');
}
$publicPath = "uploads/picture/" . User::userid() . "/";
$publicPath = "uploads/user/picture/" . User::userid() . "/";
$dirPath = public_path($publicPath);
$dirs = $files = [];
//
@@ -387,7 +613,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/fileupload 11. 上传文件
* @api {post} api/system/fileupload 15. 上传文件
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -407,7 +633,7 @@ class SystemController extends AbstractController
if (User::userid() === 0) {
return Base::retError('身份失效,等重新登录');
}
$path = "uploads/files/" . User::userid() . "/" . date("Ym") . "/";
$path = "uploads/user/file/" . User::userid() . "/" . date("Ym") . "/";
$image64 = trim(Base::getPostValue('image64'));
$fileName = trim(Base::getPostValue('filename'));
if ($image64) {
@@ -427,4 +653,66 @@ 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 {
Factory::mailer()
->setDsn("smtp://{$all['account']}:{$all['password']}@{$all['smtp_server']}:{$all['port']}?verify_peer=0")
->setMessage(EmailMessage::create()
->from(env('APP_NAME', 'Task') . " <{$all['account']}>")
->to($all['to'])
->subject('Mail sending test')
->html('<p>收到此电子邮件意味着您的邮箱配置正确。</p><p>Receiving this email means that your mailbox is configured correctly.</p>'))
->send();
return Base::retSuccess('成功发送');
} catch (\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());
}
}
}
}

View File

@@ -2,7 +2,17 @@
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\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 +52,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 +64,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 +83,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 +114,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 +153,6 @@ class UsersController extends AbstractController
* @apiGroup users
* @apiName login__codeimg
*
* @apiParam {String} email 用户名
*
* @apiSuccess {Image} data 返回数据(直接输出图片)
*/
public function login__codeimg()
@@ -204,7 +230,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);
}
/**
@@ -235,7 +263,7 @@ class UsersController extends AbstractController
$userimg = is_array($userimg) ? $userimg[0]['path'] : $userimg;
$user->userimg = Base::unFillUrl($userimg);
} else {
$user->userimg = $user->getUserimgAttribute(null);
$user->userimg = Base::unFillUrl($user->getUserimgAttribute(null));
}
}
//昵称
@@ -416,9 +444,20 @@ class UsersController extends AbstractController
* @apiName lists
*
* @apiParam {Object} [keys] 搜索条件
* - keys.key 邮箱/昵称/职位赋值后keys.email、keys.nickname、keys.profession失效
* - keys.email 邮箱
* - keys.nickname 昵称
* - keys.profession 职位
* - keys.identity 身份admin、noadmin
* - keys.disable 是否离职
* - yes: 仅离职
* - all: 全部
* - 其他值: 仅在职(默认)
* - keys.email_verity 邮箱是否认证
* - yes: 已认证
* - no: 未认证
* - 其他值: 全部(默认)
*
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:20最大:50
*
@@ -434,14 +473,26 @@ class UsersController extends AbstractController
//
$keys = Request::input('keys');
if (is_array($keys)) {
if ($keys['email']) {
$builder->where("email", "like", "%{$keys['email']}%");
}
if ($keys['nickname']) {
$builder->where("nickname", "like", "%{$keys['nickname']}%");
}
if ($keys['profession']) {
$builder->where("profession", "like", "%{$keys['profession']}%");
if ($keys['key']) {
if (str_contains($keys['key'], "@")) {
$builder->where("email", "like", "%{$keys['key']}%");
} else {
$builder->where(function($query) use ($keys) {
$query->where("email", "like", "%{$keys['key']}%")
->orWhere("nickname", "like", "%{$keys['key']}%")
->orWhere("profession", "like", "%{$keys['key']}%");
});
}
} else {
if ($keys['email']) {
$builder->where("email", "like", "%{$keys['email']}%");
}
if ($keys['nickname']) {
$builder->where("nickname", "like", "%{$keys['nickname']}%");
}
if ($keys['profession']) {
$builder->where("profession", "like", "%{$keys['profession']}%");
}
}
if ($keys['identity']) {
if (Base::leftExists($keys['identity'], "no")) {
@@ -450,6 +501,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));
//
@@ -464,16 +527,18 @@ class UsersController extends AbstractController
* @apiGroup users
* @apiName operation
*
* @apiParam {Number} userid 会员ID
* @apiParam {String} [type] 操作
* @apiParam {Number} userid 会员ID
* @apiParam {String} [type] 操作
* - setadmin 设为管理员
* - clearadmin 取消管理员
* - setdisable 设为禁用
* - cleardisable 取消禁用
* - setdisable 设为离职(需要参数 disable_time、transfer_userid
* - cleardisable 取消离职
* - delete 删除会员
* @apiParam {String} [password] 新的密码
* @apiParam {String} [nickname] 昵称
* @apiParam {String} [profession] 职位
* @apiParam {String} [password] 新的密码
* @apiParam {String} [nickname] 昵称
* @apiParam {String} [profession] 职位
* @apiParam {String} [disable_time] 离职时间
* @apiParam {String} [transfer_userid] 离职交接人
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -481,7 +546,7 @@ class UsersController extends AbstractController
*/
public function operation()
{
User::auth('admin');
$user = User::auth('admin');
//
$data = Request::all();
$userid = intval($data['userid']);
@@ -494,6 +559,7 @@ class UsersController extends AbstractController
$userInfo->checkSystem(1);
//
$upArray = [];
$transferUser = null;
switch ($type) {
case 'setadmin':
$upArray['identity'] = array_diff($userInfo->identity, ['admin']);
@@ -505,9 +571,23 @@ 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::now();
$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':
@@ -516,7 +596,10 @@ class UsersController extends AbstractController
break;
case 'delete':
$userInfo->delete();
if ($userInfo->userid === $user->userid) {
return Base::retError('不能删除自己');
}
$userInfo->deleteUser();
break;
}
if (isset($upArray['identity'])) {
@@ -553,10 +636,280 @@ class UsersController extends AbstractController
}
}
if ($upArray) {
$userInfo->updateInstance($upArray);
$userInfo->save();
AbstractModel::transaction(function() use ($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();
}
});
}
//
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,
];
if (UmengAlias::where($inArray)->exists()) {
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($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($dialog->id, 'meeting', $data, $user->userid);
if (Base::isSuccess($res)) {
$msgs[] = $res['data'];
}
}
}
//
$data['msgs'] = $msgs;
return Base::retSuccess('发送邀请成功', $data);
}
}

View File

@@ -5,8 +5,11 @@ namespace App\Http\Controllers;
use App\Module\Base;
use App\Tasks\AutoArchivedTask;
use App\Tasks\DeleteTmpTask;
use App\Tasks\EmailNoticeTask;
use Arr;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Redirect;
use Request;
/**
@@ -22,6 +25,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';
}
@@ -30,11 +37,88 @@ 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.0-215/web-apps/vendor/requirejs/require.js",
"office/7.1.0-215/web-apps/apps/api/documents/api.js",
"office/7.1.0-215/sdkjs/common/AllFonts.js",
"office/7.1.0-215/web-apps/vendor/xregexp/xregexp-all-min.js",
"office/7.1.0-215/web-apps/vendor/sockjs/sockjs.min.js",
"office/7.1.0-215/web-apps/vendor/jszip/jszip.min.js",
"office/7.1.0-215/web-apps/vendor/jszip-utils/jszip-utils.min.js",
"office/7.1.0-215/sdkjs/common/libfont/wasm/fonts.js",
"office/7.1.0-215/sdkjs/common/Charts/ChartStyles.js",
"office/7.1.0-215/sdkjs/slide/themes//themes.js",
"office/7.1.0-215/web-apps/apps/presentationeditor/main/app.js",
"office/7.1.0-215/sdkjs/slide/sdk-all-min.js",
"office/7.1.0-215/sdkjs/slide/sdk-all.js",
"office/7.1.0-215/web-apps/apps/documenteditor/main/app.js",
"office/7.1.0-215/sdkjs/word/sdk-all-min.js",
"office/7.1.0-215/sdkjs/word/sdk-all.js",
"office/7.1.0-215/web-apps/apps/spreadsheeteditor/main/app.js",
"office/7.1.0-215/sdkjs/cell/sdk-all-min.js",
"office/7.1.0-215/sdkjs/cell/sdk-all.js",
];
foreach ($array as &$item) {
$item = url($item);
}
return implode(PHP_EOL, $array);
}
/**
* 获取版本号
* @return array
*/
public function version()
{
$url = url('');
$package = Base::getPackage();
$array = [
'version' => Base::getVersion(),
'publish' => Arr::get($package, 'app.0.publish'),
];
if (is_array($package['app'])) {
foreach ($package['app'] as $item) {
$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;
}
/**
@@ -56,15 +140,77 @@ 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 AutoArchivedTask());
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);
}
/**
* 提取所有中文
* @return array|string

View File

@@ -21,6 +21,9 @@ class VerifyCsrfToken extends Middleware
// 保存任务优先级
'api/system/priority/',
// 保存创建项目列表模板
'api/system/column/template/',
// 添加任务
'api/project/task/add/',
@@ -30,8 +33,11 @@ class VerifyCsrfToken extends Middleware
// 修改任务
'api/project/task/update/',
// 上传任务问题
'api/project/task/upload/',
// 聊天发文本
'api/dialog/msg/sendtext/',
// 聊天发语音
'api/dialog/msg/sendrecord/',
// 聊天发文件
'api/dialog/msg/sendfile/',
@@ -47,5 +53,8 @@ class VerifyCsrfToken extends Middleware
// 保存汇报
'api/report/store/',
// 发布桌面端
'desktop/publish/',
];
}

View File

@@ -10,7 +10,7 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
/**
* App\Model\AbstractModel
* App\Models\AbstractModel
*
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel newQuery()
@@ -18,6 +18,7 @@ use Illuminate\Support\Facades\DB;
* @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')
@@ -45,7 +46,7 @@ class AbstractModel extends Model
{
try {
return $this->save();
} catch (\Exception $e) {
} catch (\Throwable) {
return false;
}
}
@@ -72,6 +73,15 @@ class AbstractModel extends Model
return $this->setAppends([]);
}
/**
* 取消隐藏值
* @return static
*/
protected function scopeCancelHidden()
{
return $this->setHidden([]);
}
/**
* 为数组 / JSON 序列化准备日期。
* @param DateTimeInterface $date
@@ -143,9 +153,10 @@ class AbstractModel extends Model
* @param $where
* @param array $update 存在时更新的内容
* @param array $insert 不存在时插入的内容,如果没有则插入更新内容
* @param bool $isInsert 是否是插入数据
* @return AbstractModel|\Illuminate\Database\Eloquent\Builder|Model|object|static|null
*/
public static function updateInsert($where, $update = [], $insert = [])
public static function updateInsert($where, $update = [], $insert = [], &$isInsert = true)
{
$row = static::where($where)->first();
if (empty($row)) {
@@ -155,8 +166,10 @@ class AbstractModel extends Model
unset($array[$row->primaryKey]);
}
$row->updateInstance($array);
$isInsert = true;
} elseif ($update) {
$row->updateInstance($update);
$isInsert = false;
}
if (!$row->save()) {
return null;

View File

@@ -13,6 +13,7 @@ use Request;
* App\Models\File
*
* @property int $id
* @property string|null $pids 上级ID递归
* @property int|null $pid 上级ID
* @property int|null $cid 复制ID
* @property string|null $name 名称
@@ -37,6 +38,7 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|File whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|File wherePid($value)
* @method static \Illuminate\Database\Eloquent\Builder|File wherePids($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereShare($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereSize($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereType($value)
@@ -50,6 +52,46 @@ class File extends AbstractModel
{
use SoftDeletes;
/**
* 文件文件
*/
const codeExt = [
'txt',
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css',
'dockerfile', 'go', 'golang', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
'ocamlmakefile', 'make', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php',
'inc', 'phtml', 'shtml', 'php3', 'php4', 'php5', 'phps', 'phpt', 'aw', 'ctp', 'module', 'ps1', 'py', 'r', 'rb', 'ru', 'gemspec', 'rake', 'guardfile', 'rakefile',
'gemfile', 'rs', 'sass', 'scss', 'sh', 'bash', 'bashrc', 'sql', 'sqlserver', 'swift', 'ts', 'typescript', 'str', 'vbs', 'vb', 'v', 'vh', 'sv', 'svh', 'xml',
'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx', '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
@@ -100,7 +142,7 @@ class File extends AbstractModel
/**
* 是否处于共享文件夹内(不含自身)
* @return bool
* @return File|false
*/
public function isNnShare()
{
@@ -111,19 +153,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;
@@ -132,10 +183,13 @@ class File extends AbstractModel
AbstractModel::transaction(function () use ($share) {
$this->share = $share;
$this->save();
if ($share === 0) {
FileUser::deleteFileAll($this->id, $this->userid);
}
$list = self::wherePid($this->id)->get();
if ($list->isNotEmpty()) {
foreach ($list as $item) {
$item->setShare(0);
$item->updataShare(0);
}
}
});
@@ -143,6 +197,40 @@ class File extends AbstractModel
return true;
}
/**
* 保存前更新pids
* @return bool
*/
public function saveBeforePids()
{
$pid = $this->pid;
$array = [];
while ($pid > 0) {
$array[] = $pid;
$pid = intval(self::whereId($pid)->value('pid'));
}
$opids = $this->pids;
if ($array) {
$array = array_values(array_reverse($array));
$this->pids = ',' . implode(',', $array) . ',';
} else {
$this->pids = '';
}
if (!$this->save()) {
return false;
}
// 更新子文件(夹)
if ($opids != $this->pids) {
self::wherePid($this->id)->chunkById(100, function ($lists) {
/** @var self $item */
foreach ($lists as $item) {
$item->saveBeforePids();
}
});
}
return true;
}
/**
* 遍历删除文件(夹)
* @return bool
@@ -152,8 +240,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()) {
@@ -179,19 +266,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;
}
@@ -214,6 +289,65 @@ 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
@@ -232,11 +366,144 @@ class File extends AbstractModel
if ($permission < $limit) {
$msg = match ($limit) {
1000 => '仅限所有者或创建者操作',
1 => '没有读写权限',
default => '没有访问权限',
1 => '没有修改写入权限',
default => '没有查看访问权限',
};
throw new ApiException($msg);
}
return $file;
}
/**
* 格式化内容数据
* @param array $data [path, size, ext, name]
* @return array
*/
public static function formatFileData(array $data)
{
$filePath = $data['path'];
$fileSize = $data['size'];
$fileExt = $data['ext'];
$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
{
// 其他预览
if (in_array($fileExt, File::localExt)) {
$url = Base::fillUrl($filePath);
} else {
$url = 'http://' . env('APP_IPPR') . '.3/' . $filePath;
}
if ($fileExt != 'pdf') {
$fileDotExt = ".{$fileExt}";
$fileName = Base::rightDelete($data['name'], $fileDotExt) . $fileDotExt;
$url = Base::urlAddparameter($url, [
'fullfilename' => $fileName
]);
}
$data['content'] = [
'preview' => true,
'url' => base64_encode($url),
];
$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->saveBeforePids();
// 移交文件
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->saveBeforePids();
}
});
// 移交文件权限
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

@@ -41,43 +41,53 @@ class FileContent extends AbstractModel
use SoftDeletes;
/**
* 获取格式内容
* @param $type
* 获取格式内容(或下载)
* @param File $file
* @param $content
* @param $download
* @return array|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public static function formatContent($type, $content)
public static function formatContent($file, $content, $download = false)
{
$content = Base::json2array($content);
if (in_array($type, ['word', 'excel', 'ppt'])) {
$name = $file->ext ? "{$file->name}.{$file->ext}" : null;
$content = Base::json2array($content ?: []);
if (in_array($file->type, ['word', 'excel', 'ppt'])) {
if (empty($content)) {
return Response::download(resource_path('assets/statics/office/empty.' . str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $type)));
return Response::download(resource_path('assets/statics/office/empty.' . str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $file->type)), $name);
}
return Response::download(public_path($content['url']));
return Response::download(public_path($content['url']), $name);
}
if (empty($content)) {
$content = match ($type) {
$content = match ($file->type) {
'document' => [
"type" => "md",
"type" => $file->ext,
"content" => "",
],
'sheet' => [
[
"name" => "Sheet1",
"config" => json_decode('{}'),
]
],
default => json_decode('{}'),
};
if ($download) {
abort(403, "This file is empty.");
}
} else {
$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,7 +3,9 @@
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;
@@ -17,6 +19,8 @@ use Request;
* @property string|null $name 名称
* @property string|null $desc 描述、备注
* @property int|null $userid 创建人
* @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 归档会员
@@ -44,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()
@@ -112,6 +118,7 @@ class Project extends AbstractModel
->select([
'projects.*',
'project_users.owner',
'project_users.top_at',
])
->leftJoin('project_users', function ($leftJoin) use ($userid) {
$leftJoin
@@ -135,6 +142,7 @@ class Project extends AbstractModel
->select([
'projects.*',
'project_users.owner',
'project_users.top_at',
])
->join('project_users', 'projects.id', '=', 'project_users.project_id')
->where('project_users.userid', $userid);
@@ -211,7 +219,7 @@ class Project extends AbstractModel
*/
public function relationUserids()
{
return $this->projectUser->pluck('userid')->toArray();
return ProjectUser::whereProjectId($this->id)->orderBy('id')->pluck('userid')->toArray();
}
/**
@@ -350,6 +358,201 @@ class Project extends AbstractModel
}
}
/**
* 添加工作流
* @param $flows
* @return mixed
*/
public function addFlow($flows)
{
return AbstractModel::transaction(function() use ($flows) {
$projectFlow = ProjectFlow::whereProjectId($this->id)->first();
if (empty($projectFlow)) {
$projectFlow = ProjectFlow::createInstance([
'project_id' => $this->id,
'name' => 'Default'
]);
if (!$projectFlow->save()) {
throw new ApiException('工作流创建失败');
}
}
//
$ids = [];
$idc = [];
$hasStart = false;
$hasEnd = false;
$upTaskList = [];
foreach ($flows as $item) {
$id = intval($item['id']);
$turns = Base::arrayRetainInt($item['turns'] ?: [], true);
$userids = Base::arrayRetainInt($item['userids'] ?: [], true);
$usertype = trim($item['usertype']);
$userlimit = intval($item['userlimit']);
if ($usertype == 'replace' && empty($userids)) {
throw new ApiException("状态[{$item['name']}]设置错误,设置流转模式时必须填写状态负责人");
}
if ($usertype == 'merge' && empty($userids)) {
throw new ApiException("状态[{$item['name']}]设置错误,设置剔除模式时必须填写状态负责人");
}
if ($userlimit && empty($userids)) {
throw new ApiException("状态[{$item['name']}]设置错误,设置限制负责人时必须填写状态负责人");
}
$flow = ProjectFlowItem::updateInsert([
'id' => $id,
'project_id' => $this->id,
'flow_id' => $projectFlow->id,
], [
'name' => trim($item['name']),
'status' => trim($item['status']),
'sort' => intval($item['sort']),
'turns' => $turns,
'userids' => $userids,
'usertype' => trim($item['usertype']),
'userlimit' => $userlimit,
], [], $isInsert);
if ($flow) {
$ids[] = $flow->id;
if ($flow->id != $id) {
$idc[$id] = $flow->id;
}
if ($flow->status == 'start') {
$hasStart = true;
}
if ($flow->status == 'end') {
$hasEnd = true;
}
if (!$isInsert) {
$upTaskList[$flow->id] = $flow->status . "|" . $flow->name;
}
}
}
if (!$hasStart) {
throw new ApiException('至少需要1个开始状态');
}
if (!$hasEnd) {
throw new ApiException('至少需要1个结束状态');
}
ProjectFlowItem::whereFlowId($projectFlow->id)->whereNotIn('id', $ids)->chunk(100, function($list) {
foreach ($list as $item) {
$item->deleteFlowItem();
}
});
//
foreach ($upTaskList as $id => $value) {
ProjectTask::whereFlowItemId($id)->update([
'flow_item_name' => $value
]);
}
//
$projectFlow = ProjectFlow::with(['projectFlowItem'])->whereProjectId($this->id)->find($projectFlow->id);
$itemIds = $projectFlow->projectFlowItem->pluck('id')->toArray();
foreach ($projectFlow->projectFlowItem as $item) {
$turns = $item->turns;
foreach ($idc as $oid => $nid) {
if (in_array($oid, $turns)) {
$turns = array_diff($turns, [$oid]);
$turns[] = $nid;
}
}
if (!in_array($item->id, $turns)) {
$turns[] = $item->id;
}
$turns = array_values(array_filter(array_unique(array_intersect($turns, $itemIds))));
sort($turns);
$item->turns = $turns;
ProjectFlowItem::whereId($item->id)->update([ 'turns' => Base::array2json($turns) ]);
}
return $projectFlow;
});
}
/**
* 创建项目
* @param $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(null, $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

View File

@@ -13,7 +13,7 @@ use App\Module\Base;
* @property string|null $name 名称
* @property string|null $status 状态
* @property array $turns 可流转
* @property array $userids 自动负责人ID
* @property array $userids 状态负责人ID
* @property string|null $usertype 流转模式
* @property int|null $userlimit 限制负责人
* @property int|null $sort 排序

View File

@@ -10,7 +10,7 @@ use App\Module\Base;
* @property int $id
* @property int|null $project_id 项目ID
* @property int|null $column_id 列表ID
* @property int|null $task_id 项目ID
* @property int|null $task_id 任务ID
* @property int|null $userid 会员ID
* @property string|null $detail 详细信息
* @property array $record 记录数据

View File

@@ -8,7 +8,6 @@ use App\Tasks\PushTask;
use Arr;
use Carbon\Carbon;
use DB;
use Exception;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
use Request;
@@ -40,6 +39,7 @@ use Request;
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property int|null $deleted_userid 删除会员
* @property-read \App\Models\ProjectTaskContent|null $content
* @property-read int $file_num
* @property-read int $msg_num
@@ -58,7 +58,7 @@ use Request;
* @property-read int|null $task_user_count
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask allData($userid = null)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask authData($userid = null, $owner = null)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask betweenTime($start, $end)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask betweenTime($start, $end, $type = 'taskTime')
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newQuery()
* @method static \Illuminate\Database\Query\Builder|ProjectTask onlyTrashed()
@@ -71,6 +71,7 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereCompleteAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDeletedUserid($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDesc($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereEndAt($value)
@@ -235,7 +236,7 @@ class ProjectTask extends AbstractModel
*/
public function content(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(ProjectTaskContent::class, 'task_id', 'id');
return $this->hasOne(ProjectTaskContent::class, 'task_id', 'id')->orderByDesc('id');
}
/**
@@ -243,7 +244,7 @@ class ProjectTask extends AbstractModel
*/
public function taskFile(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(ProjectTaskFile::class, 'task_id', 'id')->orderBy('id');
return $this->hasMany(ProjectTaskFile::class, 'task_id', 'id')->orderByDesc('id')->limit(50);
}
/**
@@ -312,18 +313,33 @@ class ProjectTask extends AbstractModel
* @param $query
* @param $start
* @param $end
* @param $type
* @return mixed
*/
public function scopeBetweenTime($query, $start, $end)
public function scopeBetweenTime($query, $start, $end, $type = 'taskTime')
{
$query->where(function ($q1) use ($start, $end) {
$q1->where(function ($q2) use ($start) {
$q2->where('project_tasks.start_at', '<=', $start)->where('project_tasks.end_at', '>=', $start);
})->orWhere(function ($q2) use ($end) {
$q2->where('project_tasks.start_at', '<=', $end)->where('project_tasks.end_at', '>=', $end);
})->orWhere(function ($q2) use ($start, $end) {
$q2->where('project_tasks.start_at', '>', $start)->where('project_tasks.end_at', '<', $end);
});
$query->where(function ($q1) use ($start, $end, $type) {
switch ($type) {
case 'createdTime':
$q1->where(function ($q2) use ($start) {
$q2->where('project_tasks.created_at', '>=', $start);
})->orWhere(function ($q2) use ($end) {
$q2->where('project_tasks.created_at', '<=', $end);
})->orWhere(function ($q2) use ($start, $end) {
$q2->where('project_tasks.created_at', '>', $start)->where('project_tasks.created_at', '<', $end);
});
break;
default:
$q1->where(function ($q2) use ($start) {
$q2->where('project_tasks.start_at', '<=', $start)->where('project_tasks.end_at', '>=', $start);
})->orWhere(function ($q2) use ($end) {
$q2->where('project_tasks.start_at', '<=', $end)->where('project_tasks.end_at', '>=', $end);
})->orWhere(function ($q2) use ($start, $end) {
$q2->where('project_tasks.start_at', '>', $start)->where('project_tasks.end_at', '<', $end);
});
break;
}
});
return $query;
}
@@ -342,11 +358,13 @@ class ProjectTask extends AbstractModel
$content = $data['content'];
$times = $data['times'];
$owner = $data['owner'];
$add_assist = intval($data['add_assist']);
$subtasks = $data['subtasks'];
$p_level = intval($data['p_level']);
$p_name = $data['p_name'];
$p_color = $data['p_color'];
$top = intval($data['top']);
$userid = User::userid();
//
if (ProjectTask::whereProjectId($project_id)
->whereNull('project_tasks.complete_at')
@@ -411,8 +429,13 @@ class ProjectTask extends AbstractModel
$tmpArray[] = $uid;
}
$owner = $tmpArray;
// 协助人员
$assist = [];
if (!in_array($userid, $owner) && $add_assist) {
$assist = [$userid];
}
// 创建人
$task->userid = User::userid();
$task->userid = $userid;
// 排序位置
if ($top) {
$task->sort = intval(self::whereColumnId($task->column_id)->orderBy('sort')->value('sort')) - 1;
@@ -434,7 +457,7 @@ class ProjectTask extends AbstractModel
}
}
//
return AbstractModel::transaction(function() use ($times, $subtasks, $content, $owner, $task) {
return AbstractModel::transaction(function() use ($assist, $times, $subtasks, $content, $owner, $task) {
$task->save();
$owner = array_values(array_unique($owner));
foreach ($owner as $uid) {
@@ -446,11 +469,23 @@ class ProjectTask extends AbstractModel
'owner' => 1,
])->save();
}
$assist = array_values(array_unique(array_diff($assist, $owner)));
foreach ($assist as $uid) {
ProjectTaskUser::createInstance([
'project_id' => $task->project_id,
'task_id' => $task->id,
'task_pid' => $task->parent_id ?: $task->id,
'userid' => $uid,
'owner' => 0,
])->save();
}
if ($content) {
ProjectTaskContent::createInstance([
'project_id' => $task->project_id,
'task_id' => $task->id,
'content' => $content,
'content' => [
'url' => ProjectTaskContent::saveContent($task->id, $content)
],
])->save();
}
if ($task->parent_id == 0 && $subtasks && is_array($subtasks)) {
@@ -491,13 +526,15 @@ class ProjectTask extends AbstractModel
{
AbstractModel::transaction(function () use ($data, &$updateMarking) {
// 判断版本
if (version_compare(Base::getClientVersion(), '0.6.0', '<')) {
throw new ApiException('当前版本过低');
}
Base::checkClientVersion('0.6.0');
// 主任务
$mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null;
// 工作流
if (Arr::exists($data, 'flow_item_id')) {
$isProjectOwner = $this->useridInTheProject(User::userid()) === 2;
if (!$isProjectOwner && !$this->isOwner()) {
throw new ApiException('仅限项目或任务负责人修改任务状态');
}
if ($this->flow_item_id == $data['flow_item_id']) {
throw new ApiException('任务状态未发生改变');
}
@@ -518,8 +555,7 @@ class ProjectTask extends AbstractModel
throw new ApiException("当前状态[{$currentFlowItem->name}]不可流转到[{$newFlowItem->name}]");
}
if ($currentFlowItem->userlimit) {
if (!in_array(User::userid(), $currentFlowItem->userids)
&& !ProjectUser::whereProjectId($this->project_id)->whereOwner(1)->exists()) {
if (!$isProjectOwner && !in_array(User::userid(), $currentFlowItem->userids)) {
throw new ApiException("当前状态[{$currentFlowItem->name}]仅限状态负责人或项目负责人修改");
}
}
@@ -541,13 +577,17 @@ class ProjectTask extends AbstractModel
if ($newFlowItem->userids) {
// 判断自动添加负责人
$flowData['owner'] = $data['owner'] = $this->taskUser->where('owner', 1)->pluck('userid')->toArray();
if ($newFlowItem->usertype == "replace") {
// 流转模式
if (in_array($newFlowItem->usertype, ["replace", "merge"])) {
// 流转模式、剔除模式
if ($this->parent_id === 0) {
$flowData['assist'] = $data['assist'] = $this->taskUser->where('owner', 0)->pluck('userid')->toArray();
$data['assist'] = array_merge($data['assist'], $data['owner']);
}
$data['owner'] = $newFlowItem->userids;
// 判断剔除模式:保留操作状态的人员
if ($newFlowItem->usertype == "merge") {
$data['owner'][] = User::userid();
}
} else {
// 添加模式
$data['owner'] = array_merge($data['owner'], $newFlowItem->userids);
@@ -563,6 +603,14 @@ class ProjectTask extends AbstractModel
'flow' => $flowData,
'change' => [$currentFlowItem?->name, $newFlowItem->name]
]);
ProjectTaskFlowChange::createInstance([
'task_id' => $this->id,
'userid' => User::userid(),
'before_flow_item_id' => $flowData['flow_item_id'],
'before_flow_item_name' => $flowData['flow_item_name'],
'after_flow_item_id' => $this->flow_item_id,
'after_flow_item_name' => $this->flow_item_name,
])->save();
}
// 状态
if (Arr::exists($data, 'complete_at')) {
@@ -584,7 +632,6 @@ class ProjectTask extends AbstractModel
$this->completeTask(null);
}
$updateMarking['is_update_project'] = true;
return;
}
// 标题
if (Arr::exists($data, 'name') && $this->name != $data['name']) {
@@ -632,7 +679,7 @@ class ProjectTask extends AbstractModel
}
$rows = ProjectTaskUser::whereTaskId($this->id)->whereOwner(1)->whereNotIn('userid', $array)->get();
if ($rows->isNotEmpty()) {
$this->addLog("删除{任务}负责人", ['userid' => $rows->implode('userid', ',')]);
$this->addLog("删除{任务}负责人", ['userid' => $rows->pluck('userid')]);
foreach ($rows as $row) {
$row->delete();
}
@@ -643,6 +690,7 @@ class ProjectTask extends AbstractModel
// 计划时间(原则:子任务时间在主任务时间内)
if (Arr::exists($data, 'times')) {
$oldAt = [Carbon::parse($this->start_at), Carbon::parse($this->end_at)];
$oldStringAt = $this->start_at ? ($oldAt[0]->toDateTimeString() . '~' . $oldAt[1]->toDateTimeString()) : '';
$this->start_at = null;
$this->end_at = null;
$times = $data['times'];
@@ -686,14 +734,18 @@ class ProjectTask extends AbstractModel
$start_at = Carbon::parse($subTask->start_at);
$end_at = Carbon::parse($subTask->end_at);
$isUp = false;
if ($start_at->eq($oldAt[0]) || $start_at->lt(Carbon::parse($this->start_at))) {
if (empty($subTask->start_at) || $start_at->eq($oldAt[0]) || $start_at->lt(Carbon::parse($this->start_at))) {
$subTask->start_at = $this->start_at;
$isUp = true;
}
if ($end_at->eq($oldAt[1]) || $end_at->gt(Carbon::parse($this->end_at))) {
if (empty($subTask->end_at) || $end_at->eq($oldAt[1]) || $end_at->gt(Carbon::parse($this->end_at))) {
$subTask->end_at = $this->end_at;
$isUp = true;
}
if ($subTask->start_at && Carbon::parse($subTask->start_at)->gt($subTask->end_at)) {
$subTask->start_at = $this->start_at;
$isUp = true;
}
if ($isUp) {
$updateMarking['is_update_subtask'] = true;
$subTask->addLog("同步修改{任务}时间");
@@ -702,7 +754,13 @@ class ProjectTask extends AbstractModel
}
});
}
$this->addLog("修改{任务}时间");
$newStringAt = $this->start_at ? ($this->start_at->toDateTimeString() . '~' . $this->end_at->toDateTimeString()) : '';
$this->addLog("修改{任务}时间", [
'change' => [$oldStringAt, $newStringAt]
]);
//修改计划时间需要重置任务邮件提醒日志
ProjectTaskMailLog::whereTaskId($this->id)->delete();
}
// 以下紧顶级任务可修改
if ($this->parent_id === 0) {
@@ -732,7 +790,7 @@ class ProjectTask extends AbstractModel
}
$rows = ProjectTaskUser::whereTaskId($this->id)->whereOwner(0)->whereNotIn('userid', $array)->get();
if ($rows->isNotEmpty()) {
$this->addLog("删除{任务}协助人员", ['userid' => $rows->implode('userid', ',')]);
$this->addLog("删除{任务}协助人员", ['userid' => $rows->pluck('userid')]);
foreach ($rows as $row) {
$row->delete();
}
@@ -760,18 +818,20 @@ class ProjectTask extends AbstractModel
}
// 内容
if (Arr::exists($data, 'content')) {
ProjectTaskContent::updateInsert([
ProjectTaskContent::createInstance([
'project_id' => $this->project_id,
'task_id' => $this->id,
], [
'content' => $data['content'],
]);
'content' => [
'url' => ProjectTaskContent::saveContent($this->id, $data['content'])
],
])->save();
$this->desc = Base::getHtml($data['content'], 100);
$this->addLog("修改{任务}详细描述");
$updateMarking['is_update_content'] = true;
}
// 优先级
$p = false;
$oldPName = $this->p_name;
if (Arr::exists($data, 'p_level') && $this->p_level != $data['p_level']) {
$this->p_level = intval($data['p_level']);
$p = true;
@@ -785,7 +845,9 @@ class ProjectTask extends AbstractModel
$p = true;
}
if ($p) {
$this->addLog("修改{任务}优先级");
$this->addLog("修改{任务}优先级", [
'change' => [$oldPName, $this->p_name]
]);
}
}
$this->save();
@@ -826,7 +888,7 @@ class ProjectTask extends AbstractModel
*/
public function relationUserids()
{
$userids = $this->taskUser->pluck('userid')->toArray();
$userids = ProjectTaskUser::whereTaskId($this->id)->orderByDesc('owner')->orderByDesc('id')->pluck('userid')->toArray();
$items = ProjectTask::with(['taskUser'])->where('parent_id', $this->id)->whereNull('archived_at')->get();
foreach ($items as $item) {
$userids = array_merge($userids, $item->taskUser->pluck('userid')->toArray());
@@ -862,6 +924,62 @@ class ProjectTask extends AbstractModel
return $user->owner ? 2 : 1;
}
/**
* 权限版本
* @param int $level 1-负责人2-协助人/负责人3-创建人/协助人/负责人
* @return bool
*/
public function permission($level = 1)
{
if ($level >= 3 && $this->isCreater()) {
return true;
}
if ($level >= 2 && $this->isAssister()) {
return true;
}
return $this->isOwner();
}
/**
* 判断是否创建者
* @return bool
*/
public function isCreater()
{
return $this->userid == User::userid();
}
/**
* 判断是否协助人员
* @return bool
*/
public function isAssister()
{
$row = $this;
while ($row->parent_id > 0) {
$row = self::find($row->parent_id);
}
return ProjectTaskUser::whereTaskId($row->id)->whereUserid(User::userid())->whereOwner(0)->exists();
}
/**
* 判断是否负责人(或者是主任务的负责人)
* @return bool
*/
public function isOwner()
{
if ($this->owner) {
return true;
}
if ($this->parent_id > 0) {
$mainTask = self::allData()->find($this->parent_id);
if ($mainTask->owner) {
return true;
}
}
return false;
}
/**
* 是否有负责人
* @return bool
@@ -911,6 +1029,16 @@ class ProjectTask extends AbstractModel
*/
public function archivedTask($archived_at, $isAuto = false)
{
if (!$this->complete_at) {
$flowItems = ProjectFlowItem::whereProjectId($this->project_id)->whereStatus('end')->pluck('name');
if ($flowItems) {
$flowItems = implode(",", array_values(array_unique($flowItems->toArray())));
}
if (empty($flowItems)) {
$flowItems = "已完成";
}
throw new ApiException('仅限【' . $flowItems . '】状态的任务归档');
}
AbstractModel::transaction(function () use ($isAuto, $archived_at) {
if ($archived_at === null) {
// 取消归档
@@ -918,7 +1046,6 @@ class ProjectTask extends AbstractModel
$this->archived_userid = User::userid();
$this->archived_follow = 0;
$this->addLog("任务取消归档");
$this->pushMsg('add', ProjectTask::oneTask($this->id));
} else {
// 归档任务
if ($isAuto === true) {
@@ -932,8 +1059,12 @@ class ProjectTask extends AbstractModel
$this->archived_userid = $userid;
$this->archived_follow = 0;
$this->addLog($logText, [], $userid);
$this->pushMsg('archived');
}
$this->pushMsg('update', [
'id' => $this->id,
'archived_at' => $this->archived_at,
'archived_userid' => $this->archived_userid,
]);
self::whereParentId($this->id)->update([
'archived_at' => $this->archived_at,
'archived_userid' => $this->archived_userid,
@@ -957,6 +1088,8 @@ class ProjectTask extends AbstractModel
$dialog?->deleteDialog();
}
self::whereParentId($this->id)->delete();
$this->deleted_userid = User::userid();
$this->save();
$this->addLog("删除{任务}");
$this->delete();
});
@@ -966,6 +1099,28 @@ class ProjectTask extends AbstractModel
return true;
}
/**
* 还原任务
* @param bool $pushMsg 是否推送
* @return bool
*/
public function recoveryTask($pushMsg = true)
{
AbstractModel::transaction(function () {
if ($this->dialog_id) {
$dialog = WebSocketDialog::withTrashed()->find($this->dialog_id);
$dialog?->recoveryDialog();
}
self::whereParentId($this->id)->withTrashed()->restore();
$this->addLog("还原{任务}");
$this->restore();
});
if ($pushMsg) {
$this->pushMsg('restore');
}
return true;
}
/**
* 添加任务日志
* @param string $detail
@@ -1059,28 +1214,35 @@ class ProjectTask extends AbstractModel
/**
* 获取任务(会员有任务权限 或 会员存在项目内)
* @param int $task_id
* @param bool $archived true:仅限未归档, false:不限制, null:不限制
* @param int|bool $mustOwner 0|false:不限制, 1|true:限制任务或项目负责人, 2:已有负责人才限制任务或项目负责人
* @param bool $archived true:仅限未归档, false:仅限已归档, null:不限制
* @param bool $trashed true:仅限未删除, false:仅限已删除, null:不限制
* @param int|bool $permission 0|false:不限制, 1|true:限制项目负责人、任务负责人、协助人员及任务创建者, 2:已有负责人才限制true (子任务时如果是主任务负责人也可以)
* @param array $with
* @return self
*/
public static function userTask($task_id, $archived = true, $mustOwner = 0, $with = [])
public static function userTask($task_id, $archived = true, $trashed = true, $permission = false, $with = [])
{
$task = self::with($with)->allData()->where("project_tasks.id", intval($task_id))->first();
$builder = self::with($with)->allData()->where("project_tasks.id", intval($task_id));
if ($trashed === false) {
$builder->onlyTrashed();
} elseif ($trashed === null) {
$builder->withTrashed();
}
$task = $builder->first();
//
if (empty($task)) {
throw new ApiException('任务不存在', [ 'task_id' => $task_id ], -4002);
throw new ApiException('任务不存在', ['task_id' => $task_id], -4002);
}
if ($archived === true && $task->archived_at != null) {
throw new ApiException('任务已归档', [ 'task_id' => $task_id ], -4002);
throw new ApiException('任务已归档', ['task_id' => $task_id]);
}
if ($archived === false && $task->archived_at == null) {
throw new ApiException('任务未归档', [ 'task_id' => $task_id ]);
throw new ApiException('任务未归档', ['task_id' => $task_id]);
}
//
try {
$project = Project::userProject($task->project_id);
} catch (Exception $e) {
} catch (\Throwable $e) {
if ($task->owner === null) {
throw new ApiException($e->getMessage(), [ 'task_id' => $task_id ], -4002);
}
@@ -1090,11 +1252,11 @@ class ProjectTask extends AbstractModel
}
}
//
if ($mustOwner === 2) {
$mustOwner = $task->hasOwner() ? 1 : 0;
if ($permission === 2) {
$permission = $task->hasOwner() ? 1 : 0;
}
if (($mustOwner === 1 || $mustOwner === true) && !$task->owner && !$project->owner) {
throw new ApiException('仅限项目任务负责人操作');
if (($permission === 1 || $permission === true) && !$project->owner && !$task->permission(3)) {
throw new ApiException('仅限项目负责人、任务负责人、协助人员或任务创建者操作');
}
//
return $task;

View File

@@ -2,6 +2,8 @@
namespace App\Models;
use App\Module\Base;
/**
* App\Models\ProjectTaskContent
*
@@ -28,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,6 +3,7 @@
namespace App\Models;
use App\Module\Base;
use Cache;
/**
* App\Models\ProjectTaskFile
@@ -19,6 +20,8 @@ use App\Module\Base;
* @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()
@@ -38,6 +41,11 @@ use App\Module\Base;
*/
class ProjectTaskFile extends AbstractModel
{
protected $appends = [
'width',
'height',
];
/**
* 地址
* @param $value
@@ -57,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

@@ -13,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()
@@ -29,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

@@ -11,6 +11,7 @@ use App\Module\Base;
* @property int|null $project_id 项目ID
* @property int|null $userid 成员ID
* @property int|null $owner 是否负责人
* @property string|null $top_at 置顶时间
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Project|null $project
@@ -21,6 +22,7 @@ use App\Module\Base;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereOwner($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereProjectId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereTopAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereUserid($value)
* @mixin \Eloquent
@@ -36,6 +38,41 @@ 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();
}
}
});
}
/**
* 退出项目
*/
@@ -45,15 +82,13 @@ class ProjectUser extends AbstractModel
->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();
}
$item->delete();
}
$tasks = ProjectTask::whereIn('id', $tastIds)->get();
foreach ($tasks as $task) {
$task->syncDialogUser();
}
});
$this->delete();

View File

@@ -101,15 +101,13 @@ class Report extends AbstractModel
/**
* 获取单条记录
* @param $id
* @param User|null $user
* @return Report|Builder|Model|object|null
* @throw ApiException
*/
public static function getOne($id, User $user = null)
public static function getOne($id)
{
$user === null && $user = User::auth();
$one = self::whereUserid($user->userid)->whereId($id)->first();
if ( empty($one) )
$one = self::whereId($id)->first();
if (empty($one))
throw new ApiException("记录不存在");
return $one;
}
@@ -144,15 +142,15 @@ class Report extends AbstractModel
// 如果设置了周期偏移量
empty( $offset ) || $now_dt->subWeeks( abs( $offset ) );
$now_dt->startOfWeek(); // 设置为当周第一天
return $now_dt->year . $now_dt->weekOfYear . $now_dt->month . $now_dt->weekOfMonth;
return $now_dt->year . $now_dt->weekOfYear;
},
Report::DAILY => function() use ($now_dt, $offset) {
// 如果设置了周期偏移量
empty( $offset ) || $now_dt->subDays( abs( $offset ) );
return $now_dt->year . $now_dt->dayOfYear . $now_dt->month . $now_dt->daysInMonth;
return $now_dt->format("Ymd");
},
default => "",
};
return md5( $user->userid . ( is_callable($time_s) ? $time_s() : "" ) . $type );
return $user->userid . ( is_callable($time_s) ? $time_s() : "" );
}
}

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

@@ -28,7 +28,8 @@ 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 string|null $disable_at 禁用时间(离职时间)
* @property int|null $email_verity 邮箱是否已验证
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Database\Factories\UserFactory factory(...$parameters)
@@ -41,6 +42,7 @@ use Carbon\Carbon;
* @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedIp($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereDisableAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereEmailVerity($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereEncrypt($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereIdentity($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereLastAt($value)
@@ -62,7 +64,6 @@ class User extends AbstractModel
protected $primaryKey = 'userid';
protected $hidden = [
'disable_at',
'updated_at',
];
@@ -167,6 +168,16 @@ class User extends AbstractModel
}
}
/**
* 删除会员
* @return bool|null
*/
public function deleteUser()
{
UserEmailVerification::whereEmail($this->email)->delete();
return $this->delete();
}
/** ***************************************************************************************** */
/** ***************************************************************************************** */
/** ***************************************************************************************** */
@@ -181,10 +192,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('邮箱地址已存在');
}
//密码
@@ -402,7 +419,7 @@ class User extends AbstractModel
if (isset($_A["__static_userid2basic_" . $userid])) {
return $_A["__static_userid2basic_" . $userid];
}
$fields = ['userid', 'email', 'nickname', 'profession', 'userimg'];
$fields = ['userid', 'email', 'nickname', 'profession', 'userimg', 'line_at'];
$userInfo = self::whereUserid($userid)->select($fields)->first();
if ($userInfo) {
$userInfo->online = $userInfo->getOnlineStatus();

View File

@@ -0,0 +1,80 @@
<?php
namespace App\Models;
use App\Exceptions\ApiException;
use App\Module\Base;
use Carbon\Carbon;
use Guanguans\Notify\Factory;
use Guanguans\Notify\Messages\EmailMessage;
/**
* App\Models\UserEmailVerification
*
* @property int $id
* @property int|null $userid 用户id
* @property string|null $code 验证参数
* @property string|null $email 电子邮箱
* @property int|null $status 0-未验证1-已验证
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification query()
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereCode($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereStatus($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereUserid($value)
* @mixin \Eloquent
*/
class UserEmailVerification extends AbstractModel
{
/**
* 发验证邮箱
* @param User $user
*/
public static function userEmailSend(User $user)
{
$res = self::whereUserid($user->userid)->where('created_at', '>', Carbon::now()->subMinutes(30))->first();
if ($res) return;
//删除
self::whereUserid($user->userid)->delete();
$userEmailVerification = self::createInstance([
'userid' => $user->userid,
'email' => $user->email,
'code' => Base::generatePassword(64),
'status' => 0,
]);
$userEmailVerification->save();
$setting = Base::setting('emailSetting');
$url = Base::fillUrl('single/valid/email') . '?code=' . $userEmailVerification->code;
try {
if (!Base::isEmail($user->email)) {
throw new \Exception("User email '{$user->email}' address error");
}
$subject = env('APP_NAME') . " 绑定邮箱验证";
$content = "<p>{$user->nickname} 您好,您正在绑定 " . env('APP_NAME') . " 的邮箱请于30分钟之内点击以下链接完成验证 :</p><p style='display: flex; justify-content: center;'><a href='{$url}' target='_blank'>{$url}</a></p>";
Factory::mailer()
->setDsn("smtp://{$setting['account']}:{$setting['password']}@{$setting['smtp_server']}:{$setting['port']}?verify_peer=0")
->setMessage(EmailMessage::create()
->from(env('APP_NAME', 'Task') . " <{$setting['account']}>")
->to($user->email)
->subject($subject)
->html($content))
->send();
} catch (\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());
}
}
}
}

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

@@ -3,7 +3,9 @@
namespace App\Models;
use App\Exceptions\ApiException;
use App\Tasks\PushTask;
use Carbon\Carbon;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
@@ -14,6 +16,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @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
@@ -29,6 +32,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()
@@ -47,6 +51,101 @@ class WebSocketDialog extends AbstractModel
return $this->hasMany(WebSocketDialogUser::class, 'dialog_id', 'id');
}
/**
* 格式化对话
* @param int $userid 会员ID
* @return $this
*/
public function formatData($userid)
{
// 最后消息
$last_msg = WebSocketDialogMsg::whereDialogId($this->id)->orderByDesc('id')->first();
$this->last_msg = $last_msg;
// 未读信息
$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->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 = $builder->where('userid', '!=', $userid)->first();
if ($dialog_user->userid === 0) {
$dialog_user->userid = $userid;
}
$this->name = User::userid2nickname($dialog_user->userid);
$this->dialog_user = $dialog_user;
break;
case "group":
if ($this->group_type === 'project') {
$this->group_info = Project::withTrashed()->select(['id', 'name', 'archived_at', 'deleted_at'])->whereDialogId($this->id)->first()?->cancelAppend()->cancelHidden();
$this->name = $this->group_info ? $this->group_info->name : '';
} elseif ($this->group_type === 'task') {
$this->group_info = ProjectTask::withTrashed()->select(['id', 'name', 'complete_at', 'archived_at', 'deleted_at'])->whereDialogId($this->id)->first()?->cancelAppend()->cancelHidden();
$this->name = $this->group_info ? $this->group_info->name : '';
}
break;
}
return $this;
}
/**
* 加入聊天室
* @param int|array $userid 加入的会员ID或会员ID组
* @return bool
*/
public function joinGroup($userid)
{
AbstractModel::transaction(function () use ($userid) {
foreach (is_array($userid) ? $userid : [$userid] as $value) {
if ($value > 0) {
WebSocketDialogUser::updateInsert([
'dialog_id' => $this->id,
'userid' => $value,
]);
}
}
});
return true;
}
/**
* 退出聊天室
* @param int|array $userid 加入的会员ID或会员ID组
* @return bool
*/
public function exitGroup($userid)
{
$builder = WebSocketDialogUser::whereDialogId($this->id);
if (is_array($userid)) {
$builder->whereIn('userid', $userid);
} else {
$builder->whereUserid($userid);
}
$builder->chunkById(100, function($list) {
/** @var WebSocketDialogUser $item */
foreach ($list as $item) {
if ($item->userid == $this->owner_id) {
// 群主不可退出
continue;
}
$item->delete();
}
});
return true;
}
/**
* 删除会话
* @return bool
@@ -54,20 +153,96 @@ 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;
}
/**
* 检查群组类型
* @return void
*/
public function checkGroup($groupType = 'user')
{
if ($this->type !== 'group') {
throw new ApiException('仅限群组操作');
}
if ($this->group_type !== $groupType) {
throw new ApiException('操作的群组类型错误');
}
}
/**
* 获取群组名称
* @return mixed|string|null
*/
public function getGroupName()
{
if (!isset($this->appendattrs['groupName'])) {
$name = $this->name;
if ($this->type == "group") {
if ($this->group_type === 'project') {
$name = \DB::table('projects')->where('dialog_id', $this->id)->value('name');
} elseif ($this->group_type === 'task') {
$name = \DB::table('project_tasks')->where('dialog_id', $this->id)->value('name');
}
}
$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 $checkOwner 是否校验群组身份
* @return self
*/
public static function checkDialog($dialog_id)
public static function checkDialog($dialog_id, $checkOwner = false)
{
$dialog = WebSocketDialog::find($dialog_id);
if (empty($dialog)) {
@@ -75,6 +250,10 @@ class WebSocketDialog extends AbstractModel
}
//
$userid = User::userid();
if ($checkOwner === true && $dialog->owner_id != $userid) {
throw new ApiException('仅限群主操作');
}
//
if ($dialog->type === 'group' && $dialog->group_type === 'task') {
// 任务群对话校验是否在项目内
$project_id = intval(ProjectTask::whereDialogId($dialog->id)->value('project_id'));
@@ -85,48 +264,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;
}
@@ -134,17 +272,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' => $group_type === 'user' ? Carbon::now() : null,
]);
$dialog->save();
foreach (is_array($userid) ? $userid : [$userid] as $value) {
@@ -159,47 +300,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
@@ -208,6 +308,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')

View File

@@ -8,25 +8,34 @@ use App\Tasks\PushTask;
use App\Tasks\WebSocketDialogMsgTask;
use Carbon\Carbon;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* App\Models\WebSocketDialogMsg
*
* @property int $id
* @property int|null $dialog_id 对话ID
* @property string|null $dialog_type 对话类型
* @property int|null $userid 发送会员ID
* @property string|null $type 消息类型
* @property array|mixed $msg 详细消息
* @property array|mixed $emoji emoji回复
* @property int|null $read 已阅数量
* @property int|null $send 发送数量
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read int|mixed $percentage
* @property-read \App\Models\WebSocketDialog|null $webSocketDialog
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newQuery()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg query()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg 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 whereMsg($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereRead($value)
@@ -34,10 +43,14 @@ use Hhxsv5\LaravelS\Swoole\Task\Task;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg withTrashed()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg withoutTrashed()
* @mixin \Eloquent
*/
class WebSocketDialogMsg extends AbstractModel
{
use SoftDeletes;
protected $appends = [
'percentage',
];
@@ -46,6 +59,14 @@ class WebSocketDialogMsg extends AbstractModel
'updated_at',
];
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function webSocketDialog(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(WebSocketDialog::class, 'id', 'dialog_id');
}
/**
* 阅读占比
* @return int|mixed
@@ -53,11 +74,7 @@ class WebSocketDialogMsg extends AbstractModel
public function getPercentageAttribute()
{
if (!isset($this->appendattrs['percentage'])) {
if ($this->read > $this->send || empty($this->send)) {
$this->appendattrs['percentage'] = 100;
} else {
$this->appendattrs['percentage'] = intval($this->read / $this->send * 100);
}
$this->generatePercentage();
}
return $this->appendattrs['percentage'];
}
@@ -77,10 +94,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
@@ -110,13 +158,17 @@ class WebSocketDialogMsg extends AbstractModel
if (!$msgRead->read_at) {
$msgRead->read_at = Carbon::now();
$msgRead->save();
$this->increment('read');
$this->generatePercentage(true);
PushTask::push([
'userid' => $this->userid,
'msg' => [
'type' => 'dialog',
'mode' => 'update',
'data' => $this->toArray(),
'mode' => 'readed',
'data' => [
'id' => $this->id,
'read' => $this->read,
'percentage' => $this->percentage,
],
]
]);
}
@@ -124,6 +176,247 @@ class WebSocketDialogMsg extends AbstractModel
return true;
}
/**
* emoji回复
* @param $emoji
* @param int $sender 发送的会员ID
* @return mixed
*/
public function emojiMsg($emoji, $sender)
{
$exist = false;
$array = $this->emoji;
foreach ($array as $index => &$item) {
if ($item['symbol'] === $emoji) {
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' => $emoji,
'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 $userids
* @param int $sender 发送的会员ID
* @return mixed
*/
public function forwardMsg($userids, $sender)
{
return AbstractModel::transaction(function() use ($sender, $userids) {
$msgs = [];
foreach ($userids as $userid) {
if (!User::whereUserid($userid)->exists()) {
continue;
}
$dialog = WebSocketDialog::checkUserDialog($sender, $userid);
if ($dialog) {
$res = self::sendMsg($dialog->id, $this->type, $this->getOriginal('msg'), $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();
//
$last_msg = null;
if ($this->webSocketDialog) {
$last_msg = WebSocketDialogMsg::whereDialogId($this->dialog_id)->orderByDesc('id')->first();
$this->webSocketDialog->last_at = $last_msg->created_at;
$this->webSocketDialog->save();
}
//
$dialog = WebSocketDialog::find($this->dialog_id);
if ($dialog) {
$userids = $dialog->dialogUser->pluck('userid')->toArray();
PushTask::push([
'userid' => $userids,
'msg' => [
'type' => 'dialog',
'mode' => 'delete',
'data' => [
'id' => $this->id,
'dialog_id' => $this->dialog_id,
'last_msg' => $last_msg,
'update_read' => $deleteRead ? 1 : 0
],
]
]);
}
});
}
/**
* 预览消息
* @param bool $preserveHtml 保留html格式
* @return string
*/
public function previewMsg($preserveHtml = false)
{
switch ($this->type) {
case 'text':
return $this->previewTextMsg($this->msg['text'], $preserveHtml);
case 'record':
return "[语音]";
case 'meeting':
return "[会议] ${$this->msg['name']}";
case 'file':
if ($this->msg['type'] == 'img') {
return "[图片]";
}
return "[文件] {$this->msg['name']}";
default:
return "[未知的消息]";
}
}
/**
* 返回文本预览消息
* @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) {
return $text;
} else {
return strip_tags($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) {
$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 int $dialog_id 会话ID即 聊天室ID
@@ -149,6 +442,7 @@ class WebSocketDialogMsg extends AbstractModel
$dialog->save();
$dialogMsg->send = 1;
$dialogMsg->dialog_id = $dialog->id;
$dialogMsg->dialog_type = $dialog->type;
$dialogMsg->save();
});
Task::deliver(new WebSocketDialogMsgTask($dialogMsg->id));

View File

@@ -2,6 +2,8 @@
namespace App\Models;
use Carbon\Carbon;
/**
* App\Models\WebSocketDialogMsgRead
*
@@ -9,14 +11,19 @@ namespace App\Models;
* @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)
@@ -29,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

@@ -8,6 +8,8 @@ namespace App\Models;
* @property int $id
* @property int|null $dialog_id 对话ID
* @property int|null $userid 会员ID
* @property string|null $top_at 置顶时间
* @property int|null $mark_unread 是否标记为未读0否1是
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newModelQuery()
@@ -16,6 +18,8 @@ namespace App\Models;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereMarkUnread($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereTopAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUserid($value)
* @mixin \Eloquent

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,30 @@ class Base
])->save();
}
/**
* 获取package配置文件
* @return array
*/
public static function getPackage()
{
return Cache::remember("Base::package", now()->addSeconds(10), function () {
$file = base_path('package.json');
if (file_exists($file)) {
$package = json_decode(file_get_contents($file), true);
return is_array($package) ? $package : [];
}
return [];
});
}
/**
* 获取版本号
* @return string
*/
public static function getVersion()
{
return Cache::remember("Base::version", now()->addSeconds(10), function () {
$file = base_path('package.json');
if (file_exists($file)) {
$packageArray = json_decode(file_get_contents($file), true);
return $packageArray['version'] ?? '1.0.0';
}
return '1.0.0';
});
$package = self::getPackage();
return $package['version'] ?? '1.0.0';
}
/**
@@ -87,6 +99,18 @@ class Base
return $_A["__static_client_version"];
}
/**
* 检查客户端版本
* @param string $min 最小版本
* @return void
*/
public static function checkClientVersion($min)
{
if (version_compare(Base::getClientVersion(), $min, '<')) {
throw new ApiException('当前版本 (v' . Base::getClientVersion() . ') 过低');
}
}
/**
* 判断是否域名格式
* @param $domain
@@ -261,7 +285,7 @@ class Base
{
try {
Storage::makeDirectory($path);
} catch (Exception $e) {
} catch (\Throwable $e) {
}
if (!file_exists($path)) {
self::makeDir(dirname($path));
@@ -329,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;
}
@@ -466,7 +486,7 @@ class Base
try {
$array = json_decode($string, true);
return is_array($array) ? $array : [];
} catch (Exception $e) {
} catch (\Throwable) {
return [];
}
}
@@ -484,7 +504,7 @@ class Base
}
try {
return json_encode($array, $options);
} catch (Exception $e) {
} catch (\Throwable) {
return '';
}
}
@@ -697,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);
}
/**
@@ -799,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
@@ -951,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;
}
}
/**
@@ -1173,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)) {
@@ -1188,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;
@@ -1579,7 +1628,7 @@ class Base
}
/**
* 用户名、邮箱、手机号、银行卡号中间字符串以*隐藏
* 用户名、邮箱、手机号、银行卡号中间字符串以*隐藏
* @param $str
* @return string
*/
@@ -1678,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;
}
/**
@@ -2103,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)
@@ -2125,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]}";
}
}
}
@@ -2186,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);
@@ -2236,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'];
@@ -2244,8 +2353,14 @@ 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',
@@ -2254,10 +2369,17 @@ class Base
'ofd',
'pdf',
'txt',
'html', 'htm', 'asp', 'jsp', 'xml', 'json', 'properties', 'md', 'gitignore', 'log', 'java', 'py', 'c', 'cpp', 'sql', 'sh', 'bat', 'm', 'bas', 'prg', 'cmd',
'php', 'go', 'python', 'js', 'ftl', 'css', 'lua', 'rb', 'yaml', 'yml', 'h', 'cs', 'aspx',
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css',
'dockerfile', 'go', '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:
@@ -2272,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'])) {
@@ -2284,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]}";
}
}
}
@@ -2364,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";
@@ -2379,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 源图绝对完整地址{带文件名及后缀名}
@@ -2496,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')) {
@@ -2650,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;
}
@@ -2678,36 +2838,15 @@ 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) : '#';
}
/**
@@ -2888,4 +3027,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([

View File

@@ -127,9 +127,11 @@ class WebSocketService implements WebSocketHandlerInterface
case 'readMsg':
$ids = is_array($data['id']) ? $data['id'] : [$data['id']];
$userid = $this->getUserid($frame->fd);
$list = WebSocketDialogMsg::whereIn('id', $ids)->get();
$list->transform(function(WebSocketDialogMsg $item) use ($userid) {
$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,19 @@ class WebSocketService implements WebSocketHandlerInterface
*/
private function deleteUser($fd)
{
WebSocket::whereFd($fd)->delete();
$array = [];
WebSocket::whereFd($fd)->chunk(10, function($list) use (&$array) {
/** @var WebSocket $item */
foreach ($list as $item) {
$item->delete();
if ($item->path && str_starts_with($item->path, "/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,266 @@
<?php
namespace App\Tasks;
use App\Models\ProjectTask;
use App\Models\ProjectTaskMailLog;
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")
->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")
->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();
Factory::mailer()
->setDsn("smtp://{$setting['account']}:{$setting['password']}@{$setting['smtp_server']}:{$setting['port']}?verify_peer=0")
->setMessage(EmailMessage::create()
->from(env('APP_NAME', 'Task') . " <{$setting['account']}>")
->to($user->email)
->subject($subject)
->html($content))
->send();
$data['is_send'] = 1;
} catch (\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)
->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/{$dialogId}",
'dialogName' => $dialogName,
'unread' => count($items),
'items' => $items,
])->render();
$content = str_replace("{{RemoteURL}}", config("app.url") . "/", $content);
}
try {
Factory::mailer()
->setDsn("smtp://{$setting['account']}:{$setting['password']}@{$setting['smtp_server']}:{$setting['port']}?verify_peer=0")
->setMessage(EmailMessage::create()
->from(env('APP_NAME', 'Task') . " <{$setting['account']}>")
->to($user->email)
->subject($subject)
->html($content))
->send();
} catch (\Throwable $e) {
info("unreadMsgEmail: " . $e->getMessage());
}
WebSocketDialogMsgRead::whereIn('id', $data->pluck('r_id'))->update([
'email' => 1
]);
}
}
}

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,6 +8,7 @@ use App\Models\User;
use App\Models\WebSocketDialog;
use App\Models\WebSocketDialogMsg;
use App\Models\WebSocketDialogMsgRead;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Request;
@@ -47,31 +48,58 @@ class WebSocketDialogMsgTask extends AbstractTask
return;
}
// 推送目标①:群成员
// 推送目标①:会话成员/群成员
$array = [];
$userids = $dialog->dialogUser->pluck('userid')->toArray();
foreach ($userids AS $userid) {
if ($userid == $msg->userid) {
continue;
$array[$userid] = false;
} else {
$mention = preg_match("/<span class=\"mention user\" data-id=\"[0|{$userid}]\">/", $msg->type === 'text' ? $msg->msg['text'] : '');
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',
'data' => array_merge($msg->toArray(), [
'mention' => $mention,
]),
]
]);
}
// umeng推送app
$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') {

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"

75
cmd
View File

@@ -13,6 +13,7 @@ Error="${Red}[错误]${Font}"
cur_path="$(pwd)"
cur_arg=$@
COMPOSE="docker-compose"
judge() {
if [[ 0 -eq $? ]]; then
@@ -55,15 +56,24 @@ check_docker() {
echo -e "${Error} ${RedBG} 未安装 Docker${Font}"
exit 1
fi
docker-compose --version &> /dev/null
docker-compose version &> /dev/null
if [ $? -ne 0 ]; then
echo -e "${Error} ${RedBG} 未安装 Docker-compose${Font}"
docker compose version &> /dev/null
if [ $? -ne 0 ]; then
echo -e "${Error} ${RedBG} 未安装 Docker-compose${Font}"
exit 1
fi
COMPOSE="docker compose"
fi
if [[ -n `$COMPOSE version | grep -E "\sv*1"` ]]; then
$COMPOSE version
echo -e "${Error} ${RedBG} Docker-compose 版本过低请升级至v2+${Font}"
exit 1
fi
}
check_node() {
npm --version > /dev/null
npm --version &> /dev/null
if [ $? -ne 0 ]; then
echo -e "${Error} ${RedBG} 未安装nodejs${Font}"
exit 1
@@ -71,7 +81,7 @@ check_node() {
}
docker_name() {
echo `docker-compose ps | awk '{print $1}' | grep "\-$1\-"`
echo `$COMPOSE ps | awk '{print $1}' | grep "\-$1\-"`
}
run_compile() {
@@ -86,6 +96,7 @@ run_compile() {
if [ "$type" = "prod" ]; then
rm -rf "./public/js/build"
npx mix --production
echo "$(rand_string 16)" > ./public/js/hash
else
npx mix watch --hot
fi
@@ -115,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
}
@@ -255,16 +270,19 @@ if [ $# -gt 0 ]; then
exit 1
fi
# 初始化文件
rm -rf composer.lock
rm -rf package-lock.json
if [[ -n "$(arg_get relock)" ]]; then
rm -rf node_modules
rm -rf package-lock.json
rm -rf vendor
rm -rf composer.lock
fi
mkdir -p "${cur_path}/docker/log/supervisor"
mkdir -p "${cur_path}/docker/mysql/data"
chmod -R 775 "${cur_path}/docker/log/supervisor"
chmod -R 775 "${cur_path}/docker/mysql/data"
# 启动容器
[[ "$(arg_get port)" -gt 0 ]] && env_set APP_PORT "$(arg_get port)"
docker-compose up -d
docker-compose restart php
$COMPOSE up php -d
# 安装composer依赖
run_exec php "composer install"
if [ ! -f "${cur_path}/vendor/autoload.php" ]; then
@@ -279,21 +297,25 @@ if [ $# -gt 0 ]; then
[[ -z "$(env_get APP_KEY)" ]] && run_exec php "php artisan key:generate"
run_exec php "php bin/run --mode=prod"
# 检查数据库
remaining=10
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}"
echo -e "${Error} ${RedBG} 数据库初始化失败! ${Font}"
exit 1
fi
chmod -R 775 "${cur_path}/docker/mysql/data"
sleep 3
done
run_exec php "php artisan migrate --seed"
if [ ! -f "${cur_path}/docker/mysql/data/$(env_get DB_DATABASE)/$(env_get DB_PREFIX)migrations.ibd" ]; then
echo -e "${Error} ${RedBG} 数据库安装失败! ${Font}"
exit 1
fi
# 设置初始化密码
res=`run_exec mariadb "sh /etc/mysql/repassword.sh"`
docker-compose stop
docker-compose start
$COMPOSE up -d
supervisorctl_restart php
echo -e "${OK} ${GreenBG} 安装完成 ${Font}"
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
echo -e "$res"
@@ -306,7 +328,7 @@ if [ $# -gt 0 ]; then
run_exec php "composer update"
run_exec php "php artisan migrate"
supervisorctl_restart php
docker-compose up -d
$COMPOSE up -d
elif [[ "$1" == "uninstall" ]]; then
shift 1
read -rp "确定要卸载(含:删除容器、数据库、日志)吗?(y/n): " uninstall
@@ -320,7 +342,7 @@ if [ $# -gt 0 ]; then
exit 2
;;
esac
docker-compose down
$COMPOSE down
rm -rf "./docker/mysql/data"
rm -rf "./docker/log/supervisor"
find "./storage/logs" -name "*.log" | xargs rm -rf
@@ -333,9 +355,21 @@ if [ $# -gt 0 ]; then
elif [[ "$1" == "port" ]]; then
shift 1
env_set APP_PORT "$1"
docker-compose up -d
$COMPOSE up -d
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
elif [[ "$1" == "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 \"$@\""
@@ -345,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 $@
@@ -406,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,13 +16,16 @@
"ext-simplexml": "*",
"fideloper/proxy": "^4.4.1",
"fruitcake/laravel-cors": "^2.0.4",
"guanguans/notify": "^1.20",
"guzzlehttp/guzzle": "^7.3.0",
"hedeqiang/umeng": "^2.1",
"laravel/framework": "^v8.48.1",
"laravel/tinker": "^v2.6.1",
"maatwebsite/excel": "^3.1.31",
"madnest/madzipper": "^v1.1.0",
"mews/captcha": "^3.2.6",
"orangehill/iseed": "^3.0.1",
"overtrue/pinyin": "^4.0",
"predis/predis": "^1.1.7"
},
"require-dev": {

10067
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' => 512 * 1024 * 1024,
'socket_buffer_size' => 512 * 1024 * 1024,
'package_max_length' => 512 * 1024 * 1024,
'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

@@ -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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class RenamePreProjectTaskFlowChangesItem extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('project_task_flow_changes', function (Blueprint $table) {
if (Schema::hasColumn('project_task_flow_changes', 'before_item_id')) {
$table->renameColumn('before_item_id', 'before_flow_item_id');
$table->renameColumn('before_item_name', 'before_flow_item_name');
$table->renameColumn('after_item_id', 'after_flow_item_id');
$table->renameColumn('after_item_name', 'after_flow_item_name');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('project_task_flow_changes', function (Blueprint $table) {
//
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddIsEmailVerityToUsers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
if (!Schema::hasColumn('users', 'is_email_verity')) {
$table->boolean('is_email_verity')->default(0)->nullable()->after('disable_at')->comment('邮箱是否已验证');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn("is_email_verity");
});
}
}

View File

@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserEmailVerificationsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_email_verifications', function (Blueprint $table) {
$table->id();
$table->bigInteger('userid')->nullable()->default(0)->comment('用户id');
$table->string('code')->nullable()->default('')->comment('验证参数');
$table->string('email')->nullable()->default('')->comment('电子邮箱');
$table->integer('status')->nullable()->default(0)->comment('0-未验证1-已验证');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_email_verifications');
}
}

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProjectTaskMailLogsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('project_task_mail_logs', function (Blueprint $table) {
$table->id();
$table->bigInteger('userid')->nullable()->default(0)->comment('用户id');
$table->integer('task_id')->nullable()->default(0)->comment('任务id');
$table->string('email')->nullable()->default('')->comment('电子邮箱');
$table->tinyInteger('type')->nullable()->default(0)->comment('提醒类型0 任务开始提醒1 距离到期提醒2到期超时提醒');
$table->tinyInteger('is_send')->nullable()->default(0)->comment('邮件发送是否成功0否1是');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('project_task_mail_logs');
}
}

View File

@@ -0,0 +1,40 @@
<?php
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
use App\Models\ProjectTaskContent;
use App\Module\Base;
use Illuminate\Database\Migrations\Migration;
class ProjectTaskContentsUpdateContent extends Migration
{
/**
* 任务详细描述保存至文件
* @return void
*/
public function up()
{
ProjectTaskContent::orderBy('id')->chunk(100, function($items) {
/** @var ProjectTaskContent $item */
foreach ($items as $item) {
$content = Base::json2array($item->content);
if (!isset($content['url'])) {
$item->content = Base::array2json([
'url' => ProjectTaskContent::saveContent($item->task_id, $item->content)
]);
$item->save();
}
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
// ... 退回去意义不大,文件内容不做回滚操作
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogUsersAddIsMarkUnread extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_users', 'is_mark_unread')) {
$table->boolean('is_mark_unread')->default(0)->nullable()->after('top_at')->comment('是否标记为未读0否1是');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
$table->dropColumn("is_mark_unread");
});
}
}

View File

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

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddProjectTasksDeletedUserid extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('project_tasks', function (Blueprint $table) {
if (!Schema::hasColumn('project_tasks', 'deleted_userid')) {
$table->bigInteger('deleted_userid')->nullable()->default(0)->after('deleted_at')->comment('删除会员');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('project_tasks', function (Blueprint $table) {
$table->dropColumn("deleted_userid");
});
}
}

View File

@@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class RenamePreUsersIsEmailVerity extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('users', function (Blueprint $table) use (&$isAdd) {
if (Schema::hasColumn('users', 'is_email_verity')) {
$isAdd = true;
$table->renameColumn('is_email_verity', 'email_verity');
}
});
if ($isAdd) {
\App\Models\User::where("identity", "like", "%,admin,%")->chunkById(100, function ($lists) {
foreach ($lists as $item) {
if (!$item->email_verity) {
$item->email_verity = 1;
$item->save();
}
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
//
});
}
}

View File

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

View File

@@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddFilesPids extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('files', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('files', 'pids')) {
$isAdd = true;
$table->string('pids', 255)->nullable()->default('')->after('pid')->comment('上级ID递归');
}
});
if ($isAdd) {
// 更新数据
\App\Models\File::where('pid', '>', 0)->chunkById(100, function ($lists) {
/** @var \App\Models\File $item */
foreach ($lists as $item) {
$item->saveBeforePids();
}
});
\App\Models\File::whereShare(0)->chunkById(100, function ($lists) {
/** @var \App\Models\File $item */
foreach ($lists as $item) {
\App\Models\FileUser::whereFileId($item->id)->delete();
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('files', function (Blueprint $table) {
$table->dropColumn("pids");
});
}
}

View File

@@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddFileLinksUserid extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('file_links', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('file_links', 'userid')) {
$isAdd = true;
$table->bigInteger('userid')->nullable()->default(0)->after('code')->comment('会员ID');
}
});
if ($isAdd) {
// 更新数据
\App\Models\FileLink::chunkById(100, function ($lists) {
/** @var \App\Models\FileLink $item */
foreach ($lists as $item) {
$item->userid = intval(\App\Models\File::whereId($item->file_id)->value('userid'));
$item->save();
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('file_links', function (Blueprint $table) {
$table->dropColumn("userid");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddProjectTaskMailLogsSendError extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('project_task_mail_logs', function (Blueprint $table) {
if (!Schema::hasColumn('project_task_mail_logs', 'send_error')) {
$table->text('send_error')->nullable()->after('is_send')->comment('邮件发送错误详情');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('project_task_mail_logs', function (Blueprint $table) {
$table->dropColumn("send_error");
});
}
}

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
class UsersChangeDisableAt extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$pre = DB::connection()->getTablePrefix();
DB::statement("ALTER TABLE `{$pre}users` MODIFY COLUMN `disable_at` timestamp NULL DEFAULT NULL COMMENT '禁用时间(离职时间)' AFTER `created_ip`");
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

View File

@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserTransfersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_transfers', function (Blueprint $table) {
$table->id();
$table->bigInteger('original_userid')->nullable()->default(0)->comment('原作者');
$table->bigInteger('new_userid')->nullable()->default(0)->comment('交接人');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_transfers');
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogsOwnerId extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialogs', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialogs', 'owner_id')) {
$table->bigInteger('owner_id')->nullable()->default(0)->after('last_at')->comment('群主用户ID');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialogs', function (Blueprint $table) {
$table->dropColumn("owner_id");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogMsgReadsMention extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialog_msg_reads', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_msg_reads', 'mention')) {
$table->boolean('mention')->default(0)->after('userid')->nullable()->comment('是否提及(被@');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msg_reads', function (Blueprint $table) {
$table->dropColumn("mention");
});
}
}

View File

@@ -0,0 +1,45 @@
<?php
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
use App\Models\ProjectLog;
use App\Module\Base;
use Illuminate\Database\Migrations\Migration;
class ProjectLogsRecordUserid extends Migration
{
/**
* 处理日志格式
*
* @return void
*/
public function up()
{
ProjectLog::whereIn('detail', [
'删除任务负责人',
'删除子任务负责人',
'删除任务协助人员',
'删除子任务协助人员',
])->chunkById(100, function ($lists) {
/** @var ProjectLog $log */
foreach ($lists as $log) {
$record = $log->record;
if (is_string($record['userid']) && str_contains($record['userid'], ",")) {
$record['userid'] = Base::explodeInt(',', $record['userid']);
$log->record = Base::array2json($record);
$log->save();
}
}
});
}
/**
* 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 AddWebSocketDialogMsgsDialogType 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', 'dialog_type')) {
$table->string('dialog_type', 50)->nullable()->default('')->after('dialog_id')->comment('对话类型');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropColumn("dialog_type");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogMsgReadsEmail extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialog_msg_reads', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_msg_reads', 'email')) {
$table->boolean('email')->default(0)->after('mention')->nullable()->comment('是否发了邮件');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msg_reads', function (Blueprint $table) {
$table->dropColumn("email");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUmengAliasTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('umeng_alias', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('userid')->nullable()->default(0)->comment('会员ID');
$table->string('alias', 50)->nullable()->default('')->comment('别名');
$table->string('platform', 50)->nullable()->default('')->comment('平台类型');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('umeng_alias');
}
}

View File

@@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddProjectsPersonal extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('projects', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('projects', 'personal')) {
$isAdd = true;
$table->boolean('personal')->default(0)->after('userid')->nullable()->comment('是否个人项目');
}
});
if ($isAdd) {
// 更新数据
\App\Models\Project::whereName('个人项目')->chunkById(100, function ($lists) {
/** @var \App\Models\Project $item */
foreach ($lists as $item) {
if ($item->desc == '注册时系统自动创建项目,你可以自由删除。') {
$item->personal = 1;
$item->save();
}
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('projects', function (Blueprint $table) {
$table->dropColumn("personal");
});
}
}

View File

@@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddProjectsUserSimple extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('projects', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('projects', 'user_simple')) {
$isAdd = true;
$table->string('user_simple', 255)->nullable()->default('')->after('personal')->comment('成员总数|1,2,3');
}
});
if ($isAdd) {
\App\Models\Project::chunkById(100, function ($lists) {
/** @var \App\Models\Project $item */
foreach ($lists as $item) {
$array = $item->projectUser->pluck('userid')->toArray();
$item->user_simple = count($array) . "|" . implode(",", array_slice($array, 0, 3));
$item->save();
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('projects', function (Blueprint $table) {
$table->dropColumn("user_simple");
});
}
}

View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateMeetingsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('meetings', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('meetingid')->nullable()->default('')->unique()->comment('会议ID不是数字');
$table->string('name')->nullable()->default('')->comment('会议主题');
$table->string('channel')->nullable()->default('')->comment('频道');
$table->bigInteger('userid')->nullable()->default(0)->comment('创建人');
$table->timestamps();
$table->timestamp('end_at')->nullable();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('meetings');
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogMsgsEmoji 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', 'emoji')) {
$table->longText('emoji')->after('msg')->nullable()->comment('emoji回复');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropColumn("emoji");
});
}
}

View File

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

View File

@@ -2,6 +2,10 @@
namespace Database\Seeders;
use App\Models\File;
use App\Models\FileContent;
use App\Module\Base;
use Carbon\Carbon;
use Illuminate\Database\Seeder;
class FilesTableSeeder extends Seeder
@@ -203,7 +207,7 @@ class FilesTableSeeder extends Seeder
'pid' => 0,
'cid' => 0,
'name' => '流程图',
'type' => 'flow',
'type' => 'drawio',
'ext' => '',
'size' => 5418,
'userid' => 1,
@@ -280,5 +284,54 @@ class FilesTableSeeder extends Seeder
));
File::whereIn('type', ['mind', 'drawio', 'document'])->where('ext', '')->orderBy('id')->chunk(100, function($files) {
/** @var File $file */
foreach ($files as $file) {
$fileContent = FileContent::whereFid($file->id)->orderByDesc('id')->first();
$contentArray = Base::json2array($fileContent?->content);
$contentString = '';
//
switch ($file->type) {
case 'document':
$file->ext = $contentArray['type'] ?: 'md';
$contentString = $contentArray['content'];
break;
case 'drawio':
$file->ext = 'drawio';
$contentString = $contentArray['xml'];
break;
case 'mind':
$file->ext = 'mind';
$contentString = $fileContent?->content;
break;
}
$file->save();
//
$path = 'uploads/file/' . $file->type . '/' . date("Ym", Carbon::parse($file->created_at)->timestamp) . '/' . $file->id . '/' . md5($contentString);
$save = public_path($path);
Base::makeDir(dirname($save));
file_put_contents($save, $contentString);
$content = [
'type' => $file->ext,
'url' => $path
];
//
$content = FileContent::createInstance([
'fid' => $file->id,
'content' => $content,
'text' => $fileContent?->text,
'size' => $file->size,
'userid' => $file->userid,
]);
$content->save();
}
});
File::where('pid', '>', 0)->chunkById(100, function ($lists) {
/** @var File $item */
foreach ($lists as $item) {
$item->saveBeforePids();
}
});
}
}

View File

@@ -28,13 +28,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 1,
'name' => '待处理',
'status' => 'start',
'turns' => '[1,2,3,4]',
'turns' => '[1,2,3,4,26]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 0,
'created_at' => '2022-01-15 23:43:15',
'updated_at' => '2022-01-15 23:43:15',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
1 =>
array (
@@ -43,13 +43,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 1,
'name' => '进行中',
'status' => 'progress',
'turns' => '[1,2,3,4]',
'turns' => '[1,2,3,4,26]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 1,
'created_at' => '2022-01-15 23:43:15',
'updated_at' => '2022-01-15 23:43:15',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
2 =>
array (
@@ -58,13 +58,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 1,
'name' => '已完成',
'status' => 'end',
'turns' => '[1,2,3,4]',
'turns' => '[1,2,3,4,26]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => '2022-01-15 23:43:15',
'updated_at' => '2022-01-15 23:43:15',
'sort' => 3,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
3 =>
array (
@@ -73,13 +73,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 1,
'name' => '已取消',
'status' => 'end',
'turns' => '[1,2,3,4]',
'turns' => '[1,2,3,4,26]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 3,
'created_at' => '2022-01-15 23:43:15',
'updated_at' => '2022-01-15 23:43:15',
'sort' => 4,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
4 =>
array (
@@ -88,13 +88,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 2,
'name' => '待处理',
'status' => 'start',
'turns' => '[5,6,7,8]',
'turns' => '[5,6,7,8,27]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 0,
'created_at' => '2022-01-15 23:43:23',
'updated_at' => '2022-01-15 23:43:23',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
5 =>
array (
@@ -103,13 +103,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 2,
'name' => '进行中',
'status' => 'progress',
'turns' => '[5,6,7,8]',
'turns' => '[5,6,7,8,27]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 1,
'created_at' => '2022-01-15 23:43:23',
'updated_at' => '2022-01-15 23:43:23',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
6 =>
array (
@@ -118,13 +118,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 2,
'name' => '已完成',
'status' => 'end',
'turns' => '[5,6,7,8]',
'turns' => '[5,6,7,8,27]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => '2022-01-15 23:43:23',
'updated_at' => '2022-01-15 23:43:23',
'sort' => 3,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
7 =>
array (
@@ -133,13 +133,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 2,
'name' => '已取消',
'status' => 'end',
'turns' => '[5,6,7,8]',
'turns' => '[5,6,7,8,27]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 3,
'created_at' => '2022-01-15 23:43:23',
'updated_at' => '2022-01-15 23:43:23',
'sort' => 4,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
8 =>
array (
@@ -148,13 +148,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 3,
'name' => '待处理',
'status' => 'start',
'turns' => '[9,10,11,12]',
'turns' => '[9,10,11,12,28]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 0,
'created_at' => '2022-01-15 23:43:28',
'updated_at' => '2022-01-15 23:43:28',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
9 =>
array (
@@ -163,13 +163,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 3,
'name' => '进行中',
'status' => 'progress',
'turns' => '[9,10,11,12]',
'turns' => '[9,10,11,12,28]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 1,
'created_at' => '2022-01-15 23:43:28',
'updated_at' => '2022-01-15 23:43:28',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
10 =>
array (
@@ -178,13 +178,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 3,
'name' => '已完成',
'status' => 'end',
'turns' => '[9,10,11,12]',
'turns' => '[9,10,11,12,28]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => '2022-01-15 23:43:28',
'updated_at' => '2022-01-15 23:43:28',
'sort' => 3,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
11 =>
array (
@@ -193,13 +193,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 3,
'name' => '已取消',
'status' => 'end',
'turns' => '[9,10,11,12]',
'turns' => '[9,10,11,12,28]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 3,
'created_at' => '2022-01-15 23:43:28',
'updated_at' => '2022-01-15 23:43:28',
'sort' => 4,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
12 =>
array (
@@ -208,13 +208,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 4,
'name' => '待处理',
'status' => 'start',
'turns' => '[13,14,15,16]',
'turns' => '[13,14,15,16,29]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 0,
'created_at' => '2022-01-15 23:43:34',
'updated_at' => '2022-01-15 23:43:34',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
13 =>
array (
@@ -223,13 +223,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 4,
'name' => '进行中',
'status' => 'progress',
'turns' => '[13,14,15,16]',
'turns' => '[13,14,15,16,29]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 1,
'created_at' => '2022-01-15 23:43:34',
'updated_at' => '2022-01-15 23:43:34',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
14 =>
array (
@@ -238,13 +238,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 4,
'name' => '已完成',
'status' => 'end',
'turns' => '[13,14,15,16]',
'turns' => '[13,14,15,16,29]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => '2022-01-15 23:43:34',
'updated_at' => '2022-01-15 23:43:34',
'sort' => 3,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
15 =>
array (
@@ -253,13 +253,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 4,
'name' => '已取消',
'status' => 'end',
'turns' => '[13,14,15,16]',
'turns' => '[13,14,15,16,29]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 3,
'created_at' => '2022-01-15 23:43:34',
'updated_at' => '2022-01-15 23:43:34',
'sort' => 4,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
16 =>
array (
@@ -268,13 +268,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 5,
'name' => '待处理',
'status' => 'start',
'turns' => '[17,18,19,20]',
'turns' => '[17,18,19,20,30]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 0,
'created_at' => '2022-01-15 23:43:40',
'updated_at' => '2022-01-15 23:43:40',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
17 =>
array (
@@ -283,13 +283,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 5,
'name' => '进行中',
'status' => 'progress',
'turns' => '[17,18,19,20]',
'turns' => '[17,18,19,20,30]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 1,
'created_at' => '2022-01-15 23:43:40',
'updated_at' => '2022-01-15 23:43:40',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
18 =>
array (
@@ -298,13 +298,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 5,
'name' => '已完成',
'status' => 'end',
'turns' => '[17,18,19,20]',
'turns' => '[17,18,19,20,30]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => '2022-01-15 23:43:40',
'updated_at' => '2022-01-15 23:43:40',
'sort' => 3,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
19 =>
array (
@@ -313,13 +313,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 5,
'name' => '已取消',
'status' => 'end',
'turns' => '[17,18,19,20]',
'turns' => '[17,18,19,20,30]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 3,
'created_at' => '2022-01-15 23:43:40',
'updated_at' => '2022-01-15 23:43:40',
'sort' => 4,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
20 =>
array (
@@ -328,13 +328,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 6,
'name' => '待处理',
'status' => 'start',
'turns' => '[21,22,23,24]',
'turns' => '[21,22,23,24,25]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 0,
'created_at' => '2022-01-15 23:43:45',
'updated_at' => '2022-01-15 23:43:45',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
21 =>
array (
@@ -343,13 +343,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 6,
'name' => '进行中',
'status' => 'progress',
'turns' => '[21,22,23,24]',
'turns' => '[21,22,23,24,25]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 1,
'created_at' => '2022-01-15 23:43:45',
'updated_at' => '2022-01-15 23:43:45',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
22 =>
array (
@@ -358,13 +358,13 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 6,
'name' => '已完成',
'status' => 'end',
'turns' => '[21,22,23,24]',
'turns' => '[21,22,23,24,25]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => '2022-01-15 23:43:45',
'updated_at' => '2022-01-15 23:43:45',
'sort' => 3,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
23 =>
array (
@@ -373,13 +373,103 @@ class ProjectFlowItemsTableSeeder extends Seeder
'flow_id' => 6,
'name' => '已取消',
'status' => 'end',
'turns' => '[21,22,23,24]',
'turns' => '[21,22,23,24,25]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 3,
'created_at' => '2022-01-15 23:43:45',
'updated_at' => '2022-01-15 23:43:45',
'sort' => 4,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
24 =>
array (
'id' => 25,
'project_id' => 7,
'flow_id' => 6,
'name' => '待测试',
'status' => 'test',
'turns' => '[21,22,23,24,25]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
25 =>
array (
'id' => 26,
'project_id' => 2,
'flow_id' => 1,
'name' => '待测试',
'status' => 'test',
'turns' => '[1,2,3,4,26]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
26 =>
array (
'id' => 27,
'project_id' => 3,
'flow_id' => 2,
'name' => '待测试',
'status' => 'test',
'turns' => '[5,6,7,8,27]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
27 =>
array (
'id' => 28,
'project_id' => 4,
'flow_id' => 3,
'name' => '待测试',
'status' => 'test',
'turns' => '[9,10,11,12,28]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
28 =>
array (
'id' => 29,
'project_id' => 5,
'flow_id' => 4,
'name' => '待测试',
'status' => 'test',
'turns' => '[13,14,15,16,29]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
29 =>
array (
'id' => 30,
'project_id' => 6,
'flow_id' => 5,
'name' => '待测试',
'status' => 'test',
'turns' => '[17,18,19,20,30]',
'userids' => '[]',
'usertype' => 'add',
'userlimit' => 0,
'sort' => 2,
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
));

View File

@@ -26,48 +26,48 @@ class ProjectFlowsTableSeeder extends Seeder
'id' => 1,
'project_id' => 2,
'name' => 'Default',
'created_at' => '2022-01-15 23:43:15',
'updated_at' => '2022-01-15 23:43:15',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
1 =>
array (
'id' => 2,
'project_id' => 3,
'name' => 'Default',
'created_at' => '2022-01-15 23:43:23',
'updated_at' => '2022-01-15 23:43:23',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
2 =>
array (
'id' => 3,
'project_id' => 4,
'name' => 'Default',
'created_at' => '2022-01-15 23:43:28',
'updated_at' => '2022-01-15 23:43:28',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
3 =>
array (
'id' => 4,
'project_id' => 5,
'name' => 'Default',
'created_at' => '2022-01-15 23:43:34',
'updated_at' => '2022-01-15 23:43:34',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
4 =>
array (
'id' => 5,
'project_id' => 6,
'name' => 'Default',
'created_at' => '2022-01-15 23:43:40',
'updated_at' => '2022-01-15 23:43:40',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
5 =>
array (
'id' => 6,
'project_id' => 7,
'name' => 'Default',
'created_at' => '2022-01-15 23:43:45',
'updated_at' => '2022-01-15 23:43:45',
'created_at' => seeders_at('2021-07-01 12:27:12'),
'updated_at' => seeders_at('2021-07-01 12:27:12'),
),
));

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