diff --git a/app/Http/Controllers/Api/DialogController.php b/app/Http/Controllers/Api/DialogController.php index 8942d0048..981484237 100755 --- a/app/Http/Controllers/Api/DialogController.php +++ b/app/Http/Controllers/Api/DialogController.php @@ -10,6 +10,7 @@ use App\Models\User; use App\Models\WebSocketDialog; use App\Models\WebSocketDialogMsg; use App\Models\WebSocketDialogMsgRead; +use App\Models\WebSocketDialogMsgTodo; use App\Models\WebSocketDialogUser; use App\Module\Base; use Carbon\Carbon; @@ -190,6 +191,62 @@ class DialogController extends AbstractController return Base::retSuccess('success', $array); } + /** + * @api {get} api/dialog/todo 20. 获取会话待办 + * + * @apiDescription 需要token身份 + * @apiVersion 1.0.0 + * @apiGroup dialog + * @apiName todo + * + * @apiParam {Number} dialog_id 会话ID + * + * @apiSuccess {Number} ret 返回状态码(1正确、0错误) + * @apiSuccess {String} msg 返回信息(错误描述) + * @apiSuccess {Object} data 返回数据 + */ + public function todo() + { + $user = User::auth(); + // + $dialog_id = intval(Request::input('dialog_id')); + // + WebSocketDialog::checkDialog($dialog_id); + // + $list = WebSocketDialogMsgTodo::whereDialogId($dialog_id)->whereUserid($user->userid)->whereDoneAt(null)->orderByDesc('id')->take(50)->get(); + return Base::retSuccess("success", $list); + } + + /** + * @api {get} api/dialog/top 20. 会话置顶 + * + * @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/open/user 05. 打开会话 * @@ -412,6 +469,7 @@ class DialogController extends AbstractController // if ($reDialog) { $data['dialog'] = $dialog->formatData($user->userid, true); + $data['todo'] = $data['dialog']->has_todo ? WebSocketDialogMsgTodo::whereDialogId($dialog->id)->whereUserid($user->userid)->whereDoneAt(null)->orderByDesc('id')->take(50)->get() : []; } return Base::retSuccess('success', $data); } @@ -956,33 +1014,32 @@ class DialogController extends AbstractController } /** - * @api {get} api/dialog/top 20. 会话置顶 + * @api {get} api/dialog/msg/todo 19. 设待办/取消待办 * * @apiDescription 需要token身份 * @apiVersion 1.0.0 * @apiGroup dialog - * @apiName top + * @apiName msg__todo * - * @apiParam {Number} dialog_id 会话ID + * @apiParam {Number} msg_id 消息ID * * @apiSuccess {Number} ret 返回状态码(1正确、0错误) * @apiSuccess {String} msg 返回信息(错误描述) * @apiSuccess {Object} data 返回数据 */ - public function top() + public function msg__todo() { $user = User::auth(); - $dialogId = intval(Request::input('dialog_id')); - $dialogUser = WebSocketDialogUser::whereUserid($user->userid)->whereDialogId($dialogId)->first(); - if (!$dialogUser) { - return Base::retError("会话不存在"); + // + $msg_id = intval(Request::input("msg_id")); + // + $msg = WebSocketDialogMsg::whereId($msg_id)->first(); + if (empty($msg)) { + 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(), - ]); + WebSocketDialog::checkDialog($msg->dialog_id); + // + return $msg->toggleTodoMsg($user->userid); } /** diff --git a/app/Models/WebSocketDialog.php b/app/Models/WebSocketDialog.php index 839c3b23a..571eacb2e 100644 --- a/app/Models/WebSocketDialog.php +++ b/app/Models/WebSocketDialog.php @@ -80,6 +80,8 @@ class WebSocketDialog extends AbstractModel // 对话人数 $builder = WebSocketDialogUser::whereDialogId($this->id); $this->people = $builder->count(); + // 有待办 + $this->has_todo = WebSocketDialogMsgTodo::whereDialogId($this->id)->whereDoneAt(null)->exists(); } // 对方信息 $this->dialog_user = null; @@ -101,25 +103,29 @@ class WebSocketDialog extends AbstractModel $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(); - if ($this->group_info) { - $this->name = $this->group_info->name; - } else { - $this->name = '[Delete]'; - $this->dialog_delete = 1; - } - } 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(); - if ($this->group_info) { - $this->name = $this->group_info->name; - } else { - $this->name = '[Delete]'; - $this->dialog_delete = 1; - } - } elseif ($this->group_type === 'all') { - $this->name = Base::Lang('全体成员'); - $this->all_group_mute = Base::settingFind('system', 'all_group_mute'); + switch ($this->group_type) { + case 'project': + $this->group_info = Project::withTrashed()->select(['id', 'name', 'archived_at', 'deleted_at'])->whereDialogId($this->id)->first()?->cancelAppend()->cancelHidden(); + if ($this->group_info) { + $this->name = $this->group_info->name; + } else { + $this->name = '[Delete]'; + $this->dialog_delete = 1; + } + break; + case 'task': + $this->group_info = ProjectTask::withTrashed()->select(['id', 'name', 'complete_at', 'archived_at', 'deleted_at'])->whereDialogId($this->id)->first()?->cancelAppend()->cancelHidden(); + if ($this->group_info) { + $this->name = $this->group_info->name; + } else { + $this->name = '[Delete]'; + $this->dialog_delete = 1; + } + break; + case 'all': + $this->name = Base::Lang('全体成员'); + $this->all_group_mute = Base::settingFind('system', 'all_group_mute'); + break; } break; } diff --git a/app/Models/WebSocketDialogMsg.php b/app/Models/WebSocketDialogMsg.php index 39779c110..83c3a982e 100644 --- a/app/Models/WebSocketDialogMsg.php +++ b/app/Models/WebSocketDialogMsg.php @@ -25,6 +25,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property int|null $read 已阅数量 * @property int|null $send 发送数量 * @property int|null $tag 标注会员ID + * @property int|null $todo 设为待办会员ID * @property int|null $link 是否存在链接 * @property int|null $modify 是否编辑 * @property int|null $reply_num 有多少条回复 @@ -55,6 +56,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereReplyNum($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereSend($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereTag($value) + * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereTodo($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereType($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUserid($value) @@ -265,16 +267,14 @@ class WebSocketDialogMsg extends AbstractModel if ($this->type === 'tag') { return Base::retError('此消息不支持标注'); } - $this->tag = $this->tag ? 0 : $sender; + $before = $this->tag; + $this->tag = $before ? 0 : $sender; $this->save(); $resData = [ 'id' => $this->id, 'tag' => $this->tag, ]; // - $dialog = WebSocketDialog::find($this->dialog_id); - $dialog?->pushMsg('update', $resData); - // $data = [ 'update' => $resData ]; @@ -288,6 +288,65 @@ class WebSocketDialogMsg extends AbstractModel ], $sender); if (Base::isSuccess($res)) { $data['add'] = $res['data']; + $dialog = WebSocketDialog::find($this->dialog_id); + $dialog->pushMsg('update', $data['update']); + } else { + $this->tag = $before; + $this->save(); + } + // + return Base::retSuccess('sucess', $data); + } + + /** + * 设待办、取消待办 + * @param int $sender 设待办的会员ID + * @return mixed + */ + public function toggleTodoMsg($sender) + { + if ($this->type === 'todo') { + return Base::retError('此消息不支持社待办'); + } + $before = $this->todo; + $this->todo = $before ? 0 : $sender; + $this->save(); + $resData = [ + 'id' => $this->id, + 'todo' => $this->todo, + ]; + // + $data = [ + 'update' => $resData + ]; + $res = self::sendMsg(null, $this->dialog_id, 'todo', [ + 'action' => $this->todo ? 'add' : 'remove', + 'data' => [ + 'id' => $this->id, + 'type' => $this->type, + 'msg' => $this->msg, + ] + ], $sender); + if (Base::isSuccess($res)) { + $data['add'] = $res['data']; + $dialog = WebSocketDialog::find($this->dialog_id); + $dialog->pushMsg('update', array_merge($data['update'], ['dialog_id' => $this->dialog_id])); + // + if ($this->todo) { + $userids = $dialog->dialogUser->pluck('userid')->toArray(); + foreach ($userids as $userid) { + WebSocketDialogMsgTodo::createInstance([ + 'dialog_id' => $this->dialog_id, + 'msg_id' => $this->id, + 'userid' => $userid, + ])->saveOrIgnore(); + } + } else { + WebSocketDialogMsgTodo::whereMsgId($this->id)->delete(); + } + } else { + $this->todo = $before; + $this->save(); } // return Base::retSuccess('sucess', $data); @@ -395,6 +454,9 @@ class WebSocketDialogMsg extends AbstractModel case 'tag': $action = $data['msg']['action'] === 'remove' ? '取消标注' : '标注'; return "[{$action}] {$this->previewMsg(false, $data['msg']['data'])}"; + case 'todo': + $action = $data['msg']['action'] === 'remove' ? '取消待办' : '设待办'; + return "[{$action}] {$this->previewMsg(false, $data['msg']['data'])}"; case 'notice': return $data['msg']['notice']; default: diff --git a/app/Models/WebSocketDialogMsgTodo.php b/app/Models/WebSocketDialogMsgTodo.php new file mode 100644 index 000000000..5f0be1396 --- /dev/null +++ b/app/Models/WebSocketDialogMsgTodo.php @@ -0,0 +1,46 @@ +timestamps = false; + } + + /** + * 消息详情 + * @return array|mixed + */ + public function getMsgDataAttribute() + { + if (!isset($this->appendattrs['msgData'])) { + $this->appendattrs['msgData'] = WebSocketDialogMsg::select(['id', 'type', 'msg'])->whereId($this->msg_id)->first()?->cancelAppend(); + } + return $this->appendattrs['msgData']; + } +} diff --git a/database/migrations/2022_07_07_064824_add_web_socket_dialog_msgs_todo.php b/database/migrations/2022_07_07_064824_add_web_socket_dialog_msgs_todo.php new file mode 100644 index 000000000..e25954be2 --- /dev/null +++ b/database/migrations/2022_07_07_064824_add_web_socket_dialog_msgs_todo.php @@ -0,0 +1,34 @@ +bigInteger('todo')->nullable()->default(0)->after('tag')->comment('设为待办会员ID'); + } + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('web_socket_dialog_msgs', function (Blueprint $table) { + $table->dropColumn("todo"); + }); + } +} diff --git a/database/migrations/2022_07_07_065607_create_web_socket_dialog_msg_todos_table.php b/database/migrations/2022_07_07_065607_create_web_socket_dialog_msg_todos_table.php new file mode 100644 index 000000000..5e5d82ac8 --- /dev/null +++ b/database/migrations/2022_07_07_065607_create_web_socket_dialog_msg_todos_table.php @@ -0,0 +1,35 @@ +bigIncrements('id'); + $table->bigInteger('dialog_id')->nullable()->default(0)->comment('对话ID'); + $table->bigInteger('msg_id')->nullable()->default(0)->comment('消息ID'); + $table->bigInteger('userid')->nullable()->default(0)->comment('发送会员ID'); + $table->timestamp('done_at')->nullable()->comment('完成时间'); + $table->index(['dialog_id', 'userid', 'done_at']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('web_socket_dialog_msg_todos'); + } +} diff --git a/resources/assets/js/functions/web.js b/resources/assets/js/functions/web.js index 89840f147..b47a452de 100755 --- a/resources/assets/js/functions/web.js +++ b/resources/assets/js/functions/web.js @@ -492,6 +492,8 @@ return `[${$A.L('文件')}] ${data.msg.name}` case 'tag': return `[${$A.L(data.msg.action === 'remove' ? '取消标注' : '标注')}] ${$A.getMsgSimpleDesc(data.msg.data)}` + case 'todo': + return `[${$A.L(data.msg.action === 'remove' ? '取消待办' : '设待办')}] ${$A.getMsgSimpleDesc(data.msg.data)}` case 'notice': return data.msg.notice default: diff --git a/resources/assets/js/pages/manage/components/DialogItem.vue b/resources/assets/js/pages/manage/components/DialogItem.vue index 72223b6b9..b0c6e12dc 100644 --- a/resources/assets/js/pages/manage/components/DialogItem.vue +++ b/resources/assets/js/pages/manage/components/DialogItem.vue @@ -5,6 +5,11 @@ {{$L(source.msg.action === 'remove' ? '取消标注' : '标注了')}} "{{$A.getMsgSimpleDesc(source.msg.data)}}" +
+
+ {{$L(source.msg.action === 'remove' ? '取消待办' : '设待办')}} + "{{$A.getMsgSimpleDesc(source.msg.data)}}" +
{{source.msg.notice}}
@@ -120,6 +125,13 @@ export default { }) }, + onViewTodo() { + this.onViewReply({ + msg_id: this.source.id, + reply_id: this.source.msg.data.id + }) + }, + onLongpress(e) { this.dispatch("on-longpress", e) }, diff --git a/resources/assets/js/pages/manage/components/DialogView.vue b/resources/assets/js/pages/manage/components/DialogView.vue index 318c8d30b..f7e31da07 100644 --- a/resources/assets/js/pages/manage/components/DialogView.vue +++ b/resources/assets/js/pages/manage/components/DialogView.vue @@ -91,6 +91,10 @@
+ +
+ +
diff --git a/resources/assets/js/pages/manage/components/DialogWrapper.vue b/resources/assets/js/pages/manage/components/DialogWrapper.vue index 4965524bb..ac709849d 100644 --- a/resources/assets/js/pages/manage/components/DialogWrapper.vue +++ b/resources/assets/js/pages/manage/components/DialogWrapper.vue @@ -121,6 +121,14 @@ @on-progress="chatFile('progress', $event)" @on-success="chatFile('success', $event)" @on-error="chatFile('error', $event)"/> +
+
{{$L('待办')}}:
+ +
{{$L('禁言发言')}}
@@ -196,6 +204,10 @@ {{ $L('新任务') }} +
  • + + {{ $L(operateItem.todo ? '取消待办' : '设待办') }} +
  • @@ -409,6 +421,7 @@ export default { 'taskId', 'dialogSearchMsgId', 'dialogMsgs', + 'dialogTodos', 'dialogMsgTransfer', 'cacheDialogs', 'wsOpenNum', @@ -510,6 +523,15 @@ export default { return array }, + todoList() { + if (!this.dialogData.has_todo) { + return [] + } + return this.dialogTodos.filter(item => !item.done_at && item.dialog_id == this.dialogId).sort((a, b) => { + return b.id - a.id; + }); + }, + wrapperClass() { if (['ready', 'ing'].includes(this.recordState)) { return ['record-ready'] @@ -1368,6 +1390,10 @@ export default { }); break; + case "todo": + this.onTodo() + break; + case "emoji": this.onEmoji(value) break; @@ -1598,17 +1624,57 @@ export default { url: 'dialog/msg/tag', data, }).then(({data}) => { - this.$store.dispatch("saveDialogMsg", data.update); - if (data.add) { - this.$store.dispatch("saveDialogMsg", data.add); - this.$store.dispatch("updateDialogLastMsg", data.add); - } + this.tagOrTodoSuccess(data) }).catch(({msg}) => { $A.messageError(msg); }).finally(_ => { this.$store.dispatch("cancelLoad", `msg-${data.msg_id}`) }); - } + }, + + onTodo() { + if (this.operateVisible) { + return + } + const data = { + msg_id: this.operateItem.id, + } + // + $A.modalConfirm({ + title: this.operateItem.todo ? '取消待办' : '设置待办', + content: this.operateItem.todo ? "撤回待办后,会话其他成员的待办也将消失。" : "设置为待办后,将通知会话所有成员。", + cancelText: '取消', + okText: '确定', + loading: true, + onOk: () => { + return new Promise((resolve, reject) => { + this.$store.dispatch("setLoad", { + key: `msg-${data.msg_id}`, + delay: 600 + }) + this.$store.dispatch("call", { + url: 'dialog/msg/todo', + data, + }).then(({data, msg}) => { + resolve(msg) + this.tagOrTodoSuccess(data) + }).catch(({msg}) => { + reject(msg); + }).finally(_ => { + this.$store.dispatch("cancelLoad", `msg-${data.msg_id}`) + }); + }) + } + }); + }, + + tagOrTodoSuccess(data) { + this.$store.dispatch("saveDialogMsg", data.update); + if (data.add) { + this.$store.dispatch("saveDialogMsg", data.add); + this.$store.dispatch("updateDialogLastMsg", data.add); + } + }, } } diff --git a/resources/assets/js/store/actions.js b/resources/assets/js/store/actions.js index 0b6c1bdbb..fc64362f9 100644 --- a/resources/assets/js/store/actions.js +++ b/resources/assets/js/store/actions.js @@ -2026,6 +2026,35 @@ export default { }); }, + /** + * 获取会话待办 + * @param state + * @param dispatch + * @param dialog_id + */ + getDialogTodo({state, dispatch}, dialog_id) { + dispatch("call", { + url: 'dialog/todo', + data: { + dialog_id, + }, + }).then(({data}) => { + if ($A.arrayLength(data) > 0) { + dispatch("saveDialog", { + id: dialog_id, + has_todo: true + }); + state.dialogTodos = state.dialogTodos.filter(item => item.dialog_id != dialog_id) + state.dialogTodos.push(...data) + } else { + dispatch("saveDialog", { + id: dialog_id, + has_todo: false + }); + } + }).catch(console.warn); + }, + /** * 打开会话 * @param state @@ -2214,6 +2243,10 @@ export default { const ids = resData.list.map(({id}) => id) state.dialogMsgs = state.dialogMsgs.filter(item => item.dialog_id != data.dialog_id || ids.includes(item.id)); } + if ($A.isArray(resData.todo)) { + state.dialogTodos = state.dialogTodos.filter(item => item.dialog_id != data.dialog_id) + state.dialogTodos.push(...resData.todo) + } // dispatch("saveDialogMsg", resData.list) } @@ -2488,6 +2521,10 @@ export default { // 更新、已读回执 if (state.dialogMsgs.find(({id}) => id == data.id)) { dispatch("saveDialogMsg", data) + // 更新待办 + if (typeof data.todo !== "undefined") { + dispatch("getDialogTodo", dialog_id) + } } break; case 'groupAdd': diff --git a/resources/assets/js/store/state.js b/resources/assets/js/store/state.js index 042c53514..76e59b8f8 100644 --- a/resources/assets/js/store/state.js +++ b/resources/assets/js/store/state.js @@ -74,6 +74,7 @@ const stateData = { dialogSearchMsgId: 0, dialogIns: [], dialogMsgs: [], + dialogTodos: [], dialogInputCache: $A.getStorageArray("cacheDialogInput"), dialogMsgTransfer: {time: 0}, diff --git a/resources/assets/sass/pages/components/dialog-wrapper.scss b/resources/assets/sass/pages/components/dialog-wrapper.scss index f7b9dcc11..a4becfacd 100644 --- a/resources/assets/sass/pages/components/dialog-wrapper.scss +++ b/resources/assets/sass/pages/components/dialog-wrapper.scss @@ -296,6 +296,7 @@ padding-bottom: 16px; .dialog-tag, + .dialog-todo, .dialog-notice { font-size: 12px; max-width: 80%; @@ -315,6 +316,14 @@ } } + .dialog-todo { + cursor: pointer; + + .todo-user { + display: inline-block; + } + } + .dialog-avatar { position: relative; margin-bottom: 20px; @@ -756,6 +765,7 @@ } .tag, + .todo, .reply, .modify { display: flex; @@ -982,6 +992,39 @@ overflow: hidden; } + .chat-todo { + display: flex; + align-items: center; + padding: 8px 0; + + .todo-label { + flex-shrink: 0; + padding-right: 8px; + } + + > ul { + flex: 1; + display: flex; + align-items: center; + overflow-x: auto; + + > li { + flex-shrink: 0; + list-style: none; + margin-right: 8px; + background-color: #f0f1f3; + padding: 0 8px; + border-radius: 6px; + line-height: 24px; + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: pointer; + } + } + } + .chat-mute { color: $primary-desc-color; background-color: #F4F5F7;