From 59aa854470a0ad3bb3e5dfe38519a1f0b714b881 Mon Sep 17 00:00:00 2001 From: weifashi <605403358@qq.com> Date: Thu, 21 Dec 2023 17:20:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=20=E6=B6=88=E6=81=AF=E7=BD=AE=E9=A1=B6?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20-=2050%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Api/DialogController.php | 59 ++++++++++ app/Models/WebSocketDialogMsg.php | 46 ++++++++ ...2_update_owner_add_index_some_20231217.php | 2 +- ...5804_add_web_socket_dialog_msgs_top_at.php | 36 ++++++ resources/assets/js/functions/web.js | 2 + .../js/pages/manage/components/DialogItem.vue | 5 + .../js/pages/manage/components/DialogView.vue | 4 + .../pages/manage/components/DialogWrapper.vue | 108 +++++++++++++++++- resources/assets/js/store/actions.js | 68 +++++++++++ resources/assets/js/store/state.js | 1 + .../assets/sass/components/user-select.scss | 2 +- .../sass/pages/components/dialog-wrapper.scss | 59 +++++++++- 12 files changed, 385 insertions(+), 7 deletions(-) create mode 100644 database/migrations/2023_12_20_145804_add_web_socket_dialog_msgs_top_at.php diff --git a/app/Http/Controllers/Api/DialogController.php b/app/Http/Controllers/Api/DialogController.php index a707ed924..f10a4d364 100755 --- a/app/Http/Controllers/Api/DialogController.php +++ b/app/Http/Controllers/Api/DialogController.php @@ -438,6 +438,8 @@ class DialogController extends AbstractController $builder->where('tag', '>', 0); } elseif ($msg_type === 'todo') { $builder->where('todo', '>', 0); + } elseif ($msg_type === 'top') { + $builder->whereNotNull('top_at'); } elseif ($msg_type === 'link') { $builder->whereLink(1); } elseif (in_array($msg_type, ['text', 'image', 'file', 'record', 'meeting'])) { @@ -507,6 +509,7 @@ class DialogController extends AbstractController if ($reDialog) { $data['dialog'] = $dialog->formatData($user->userid, true); $data['todo'] = $data['dialog']->todo_num > 0 ? WebSocketDialogMsgTodo::whereDialogId($dialog->id)->whereUserid($user->userid)->whereDoneAt(null)->orderByDesc('id')->take(50)->get() : []; + $data['tops'] = WebSocketDialogMsg::whereDialogId($dialog->id)->whereNotNull('top_at')->orderByDesc('top_at')->take(50)->get(); } return Base::retSuccess('success', $data); } @@ -2169,4 +2172,60 @@ class DialogController extends AbstractController return Base::retSuccess('发送成功', $result); } + /** + * @api {get} api/dialog/msg/top 46. 置顶/取消置顶 + * + * @apiDescription 需要token身份 + * @apiVersion 1.0.0 + * @apiGroup dialog + * @apiName msg__top + * + * @apiParam {Number} msg_id 消息ID + * + * @apiSuccess {Number} ret 返回状态码(1正确、0错误) + * @apiSuccess {String} msg 返回信息(错误描述) + * @apiSuccess {Object} data 返回数据 + */ + public function msg__top() + { + $user = User::auth(); + // + $msg_id = intval(Request::input("msg_id")); + // + $msg = WebSocketDialogMsg::whereId($msg_id)->first(); + if (empty($msg)) { + return Base::retError("消息不存在或已被删除"); + } + WebSocketDialog::checkDialog($msg->dialog_id); + // + return $msg->toggleTopMsg($user->userid); + } + + /** + * @api {get} api/dialog/toplist 47. 获取置顶列表 + * + * @apiDescription 需要token身份 + * @apiVersion 1.0.0 + * @apiGroup dialog + * @apiName toplist + * + * @apiParam {Number} dialog_id 会话ID + * + * @apiSuccess {Number} ret 返回状态码(1正确、0错误) + * @apiSuccess {String} msg 返回信息(错误描述) + * @apiSuccess {Object} data 返回数据 + */ + public function toplist() + { + $user = User::auth(); + // + $dialog_id = intval(Request::input('dialog_id')); + // + WebSocketDialog::checkDialog($dialog_id); + // + $tops = WebSocketDialogMsg::whereDialogId($dialog_id)->whereNotNull('top_at')->orderByDesc('top_at')->take(50)->get(); + // + return Base::retSuccess('success', $tops ?: []); + } + } diff --git a/app/Models/WebSocketDialogMsg.php b/app/Models/WebSocketDialogMsg.php index 0d019cbc7..be4040c56 100644 --- a/app/Models/WebSocketDialogMsg.php +++ b/app/Models/WebSocketDialogMsg.php @@ -380,6 +380,49 @@ class WebSocketDialogMsg extends AbstractModel return Base::retSuccess($this->todo ? '设置成功' : '取消成功', $data); } + /** + * 置顶、取消置顶 + * @param int $sender 置顶的会员ID + * @return mixed + */ + public function toggleTopMsg($sender) + { + $before = $this->top; + $beforeTopAt = $this->top_at; + $this->top = $before ? 0 : $sender; + $this->top_at = $before ? null : Carbon::now(); + $this->save(); + $resData = [ + 'id' => $this->id, + 'top' => $this->top, + 'top_at' => $this->top_at, + ]; + // + $data = [ + 'update' => $resData + ]; + $res = self::sendMsg(null, $this->dialog_id, 'top', [ + 'action' => $this->top ? 'add' : 'remove', + 'data' => [ + 'id' => $this->id, + 'type' => $this->type, + 'msg' => $this->quoteTextMsg(), + ] + ], $sender); + if (Base::isSuccess($res)) { + $data['add'] = $res['data']; + $dialog = WebSocketDialog::find($this->dialog_id); + $resData['tops'] = WebSocketDialogMsg::whereDialogId($dialog->id)->whereNotNull('top_at')->orderByDesc('top_at')->take(50)->get(); + $dialog->pushMsg('update', $resData); + } else { + $this->top = $before; + $this->top_at = $beforeTopAt; + $this->save(); + } + // + return Base::retSuccess($this->top ? '置顶成功' : '取消成功', $data); + } + /** * 转发消息 * @param array|int $dialogids @@ -536,6 +579,9 @@ class WebSocketDialogMsg extends AbstractModel case 'tag': $action = $data['msg']['action'] === 'remove' ? '取消标注' : '标注'; return "[{$action}] {$this->previewMsg(false, $data['msg']['data'])}"; + case 'top': + $action = $data['msg']['action'] === 'remove' ? '取消置顶' : '置顶'; + return "[{$action}] {$this->previewMsg(false, $data['msg']['data'])}"; case 'todo': $action = $data['msg']['action'] === 'remove' ? '取消待办' : ($data['msg']['action'] === 'done' ? '完成' : '设待办'); return "[{$action}] {$this->previewMsg(false, $data['msg']['data'])}"; diff --git a/database/migrations/2023_12_07_160642_update_owner_add_index_some_20231217.php b/database/migrations/2023_12_07_160642_update_owner_add_index_some_20231217.php index cec15a86a..8c9ea337a 100644 --- a/database/migrations/2023_12_07_160642_update_owner_add_index_some_20231217.php +++ b/database/migrations/2023_12_07_160642_update_owner_add_index_some_20231217.php @@ -168,7 +168,7 @@ class UpdateOwnerAddIndexSome20231217 extends Migration $table->dropIndex(['msg_id']); $table->dropIndex(['userid']); }); - Schema::table('web_socket_dialog_msg_todos', function (Blueprint $table) { + Schema::table('web_socket_dialog_msg_reads', function (Blueprint $table) { $table->dropIndex(['dialog_id']); }); diff --git a/database/migrations/2023_12_20_145804_add_web_socket_dialog_msgs_top_at.php b/database/migrations/2023_12_20_145804_add_web_socket_dialog_msgs_top_at.php new file mode 100644 index 000000000..762f061a8 --- /dev/null +++ b/database/migrations/2023_12_20_145804_add_web_socket_dialog_msgs_top_at.php @@ -0,0 +1,36 @@ +bigInteger('top')->nullable()->default(0)->after('send')->comment('置顶的会员ID'); + $table->timestamp('top_at')->nullable()->after('top')->comment('置顶时间'); + } + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('web_socket_dialog_msgs', function (Blueprint $table) { + $table->dropColumn("top"); + $table->dropColumn("top_at"); + }); + } +} diff --git a/resources/assets/js/functions/web.js b/resources/assets/js/functions/web.js index 59d271296..c006438e9 100755 --- a/resources/assets/js/functions/web.js +++ b/resources/assets/js/functions/web.js @@ -839,6 +839,8 @@ return `[${$A.L('文件')}] ${data.msg.name}` case 'tag': return `[${$A.L(data.msg.action === 'remove' ? '取消标注' : '标注')}] ${$A.getMsgSimpleDesc(data.msg.data)}` + case 'top': + return `[${$A.L(data.msg.action === 'remove' ? '取消置顶' : '置顶*')}] ${$A.getMsgSimpleDesc(data.msg.data)}` case 'todo': return `[${$A.L(data.msg.action === 'remove' ? '取消待办' : (data.msg.action === 'done' ? '完成' : '设待办'))}] ${$A.getMsgSimpleDesc(data.msg.data)}` case 'notice': diff --git a/resources/assets/js/pages/manage/components/DialogItem.vue b/resources/assets/js/pages/manage/components/DialogItem.vue index 2f38789e6..e9bf9e614 100644 --- a/resources/assets/js/pages/manage/components/DialogItem.vue +++ b/resources/assets/js/pages/manage/components/DialogItem.vue @@ -8,6 +8,11 @@ {{$L(source.msg.action === 'remove' ? '取消标注' : '标注了')}} "{{$A.getMsgSimpleDesc(source.msg.data)}}" +
+
+ {{$L(source.msg.action === 'remove' ? '取消置顶' : '置顶了')}} + "{{$A.getMsgSimpleDesc(source.msg.data)}}" +
{{$L(source.msg.action === 'remove' ? '取消待办' : (source.msg.action === 'done' ? '完成' : '设待办'))}} diff --git a/resources/assets/js/pages/manage/components/DialogView.vue b/resources/assets/js/pages/manage/components/DialogView.vue index 7f0c7ae45..b36a6c205 100644 --- a/resources/assets/js/pages/manage/components/DialogView.vue +++ b/resources/assets/js/pages/manage/components/DialogView.vue @@ -169,6 +169,10 @@
+ +
+ +
+ +
+
+

{{$L('置顶消息')}}

+

{{$A.getMsgSimpleDesc(topMessage)}}

+
+
+ +
+
+
@@ -287,6 +298,10 @@ {{ $L(operateItem.todo ? '取消待办' : '设待办') }} +
  • + + {{ $L(operateItem.top_at ? '取消置顶' : '置顶') }} +
  • {{ $L('完整对话') }} @@ -538,6 +553,34 @@ + + +
    +
    +
    {{$L('置顶消息')}}
    +
    + +
    + +
    + + +
    +
    +
    +
    +
    +
  • @@ -701,6 +744,8 @@ export default { unreadMsgId: 0, // 最早未读消息id toBottomReGetMsg: false, // 滚动到底部重新获取消息 selectionRange: false, // 是否选择文本 + + topViewShow: false, } }, @@ -728,6 +773,7 @@ export default { 'dialogSearchMsgId', 'dialogMsgs', 'dialogTodos', + 'dialogTops', 'dialogMsgTransfer', 'cacheDialogs', 'wsOpenNum', @@ -1034,7 +1080,17 @@ export default { return this.systemConfig.file_upload_limit * 1024 } return 1024000 - } + }, + + topList() { + return this.dialogTops.filter(item => item.top_at && item.dialog_id == this.dialogId).sort((a, b) => { + return b.top_at - a.top_at; + }); + }, + + topMessage() { + return this.topList[0] + }, }, watch: { @@ -2545,6 +2601,10 @@ export default { this.onEmoji(value) } break; + + case "top": + this.onTop() + break; } }) }, @@ -2902,7 +2962,7 @@ export default { url: 'dialog/msg/tag', data, }).then(({data}) => { - this.tagOrTodoSuccess(data) + this.tagOrTodoOrTopSuccess(data) }).catch(({msg}) => { $A.messageError(msg); }).finally(_ => { @@ -3001,7 +3061,7 @@ export default { data, }).then(({data, msg}) => { resolve(msg) - this.tagOrTodoSuccess(data) + this.tagOrTodoOrTopSuccess(data) this.onActive() }).catch(({msg}) => { reject(msg); @@ -3011,7 +3071,7 @@ export default { }) }, - tagOrTodoSuccess(data) { + tagOrTodoOrTopSuccess(data) { this.$store.dispatch("saveDialogMsg", data.update); if (data.add) { this.$store.dispatch("saveDialogMsg", data.add); @@ -3176,6 +3236,46 @@ export default { } }, + onTop() { + if (this.operateVisible) { + return + } + if (this.operateItem?.top_at) { + $A.modalConfirm({ + content: "你确定取消置顶吗?", + cancelText: '取消', + okText: '确定', + loading: true, + onOk: () => this.onTopSubmit(this.operateItem) + }); + } else { + this.onTopSubmit(this.operateItem) + } + }, + + onTopSubmit(data) { + return new Promise((resolve, reject) => { + this.$store.dispatch("setLoad", { + key: `msg-${data.msg_id}`, + delay: 600 + }) + this.$store.dispatch("call", { + url: 'dialog/msg/top', + data: { + msg_id: data.id + }, + }).then(({ data, msg }) => { + resolve(msg) + this.tagOrTodoOrTopSuccess({ update: data }) + this.onActive() + }).catch(({ msg }) => { + reject(msg); + }).finally(_ => { + this.$store.dispatch("cancelLoad", `msg-${data.msg_id}`) + }); + }) + }, + getUserApproveStatus() { this.approvaUserStatus = '' if (this.dialogData.type !== 'user' || this.dialogData.bot) { diff --git a/resources/assets/js/store/actions.js b/resources/assets/js/store/actions.js index 0c574fa10..15d805286 100644 --- a/resources/assets/js/store/actions.js +++ b/resources/assets/js/store/actions.js @@ -2494,6 +2494,26 @@ export default { }).catch(console.warn); }, + /** + * 获取会话置顶 + * @param state + * @param dispatch + * @param dialog_id + */ + getDialogTop({state, dispatch}, dialog_id) { + dispatch("call", { + url: 'dialog/toplist', + data: { + dialog_id, + }, + }).then(({data}) => { + if ($A.arrayLength(data) > 0) { + state.dialogTops = state.dialogTops.filter(item => item.dialog_id != dialog_id) + dispatch("saveDialogTop", data) + } + }).catch(console.warn); + }, + /** * 打开会话 * @param state @@ -2703,6 +2723,44 @@ export default { } }, + /** + * 保存置顶数据 + * @param state + * @param dispatch + * @param data + */ + saveDialogTop({state, dispatch}, data) { + $A.execMainDispatch("saveDialogTop", data) + // + if ($A.isArray(data)) { + data.forEach(item => { + dispatch("saveDialogTop", item) + }); + } else if ($A.isJson(data)) { + const index = state.dialogTops.findIndex(item => item.id == data.id); + if (index > -1) { + state.dialogTops.splice(index, 1, Object.assign({}, state.dialogTops[index], data)); + } else { + state.dialogTops.push(data); + } + } + }, + + /** + * 忘记置顶数据 + * @param state + * @param dispatch + * @param msg_id + */ + forgetDialogTopForMsgId({state, dispatch}, msg_id) { + $A.execMainDispatch("forgetDialogTopForMsgId", msg_id) + // + const index = state.dialogTops.findIndex(item => item.msg_id == msg_id); + if (index > -1) { + state.dialogTops.splice(index, 1); + } + }, + /** * 保存聊天草稿 * @param state @@ -2777,6 +2835,7 @@ export default { } }) dispatch("forgetDialogTodoForMsgId", msg_id) + dispatch("forgetDialogTopForMsgId", msg_id) }, /** @@ -2845,6 +2904,10 @@ export default { state.dialogTodos = state.dialogTodos.filter(item => item.dialog_id != data.dialog_id) dispatch("saveDialogTodo", resData.todo) } + if ($A.isArray(resData.tops)) { + state.dialogTops = state.dialogTops.filter(item => item.dialog_id != data.dialog_id) + dispatch("saveDialogTop", resData.tops) + } // dispatch("saveDialogMsg", resData.list) resolve(result) @@ -3290,6 +3353,11 @@ export default { if (typeof data.todo !== "undefined") { dispatch("getDialogTodo", dialog_id) } + // 更新置顶 + if ($A.isArray(data.tops)) { + state.dialogTops = data.tops + dispatch("saveDialogTop", data.tops) + } return; } if (count <= 5) { diff --git a/resources/assets/js/store/state.js b/resources/assets/js/store/state.js index 123c40e18..6fa2ce618 100644 --- a/resources/assets/js/store/state.js +++ b/resources/assets/js/store/state.js @@ -110,6 +110,7 @@ export default { dialogIns: [], dialogMsgs: [], dialogTodos: [], + dialogTops: [], dialogHistory: [], dialogDraftTimer: {}, dialogMsgTransfer: {time: 0}, diff --git a/resources/assets/sass/components/user-select.scss b/resources/assets/sass/components/user-select.scss index c380cce02..4b52a384f 100755 --- a/resources/assets/sass/components/user-select.scss +++ b/resources/assets/sass/components/user-select.scss @@ -174,7 +174,7 @@ } &.twice-affirm{ - padding-bottom: 20px; + padding-bottom: 16px; .search-selected{ max-width: 100%; } diff --git a/resources/assets/sass/pages/components/dialog-wrapper.scss b/resources/assets/sass/pages/components/dialog-wrapper.scss index 7c3afb633..1c8904752 100644 --- a/resources/assets/sass/pages/components/dialog-wrapper.scss +++ b/resources/assets/sass/pages/components/dialog-wrapper.scss @@ -41,6 +41,19 @@ margin: 0 auto; box-shadow: none; } + + .dialog-scroller-item{ + border-bottom: 1px solid #eeeeee; + margin-bottom: 16px; + .reply-item { + border-bottom: none; + margin-bottom: 0; + } + .original-button-warp{ + display: flex; + margin-bottom: 16px; + } + } } .todo-button { @@ -421,6 +434,38 @@ } } + .dialog-top-message { + padding: 10px; + position: relative; + display: flex; + &:before { + content: ""; + position: absolute; + left: 0; + bottom: 0; + width: 100%; + height: 1px; + background-color: #f4f5f5; + } + .dialog-top-message-content { + flex: 1; + overflow: hidden; + p { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + .dialog-top-message-font { + width: 40px; + line-height: 42px; + text-align: center; + .taskfont{ + font-size: 22px; + } + } + } + .dialog-scroller { flex: 1; position: relative; @@ -447,6 +492,7 @@ .dialog-tag, .dialog-todo, + .dialog-top, .dialog-notice, .dialog-new { font-size: 12px; @@ -459,6 +505,7 @@ word-wrap: break-word; } + .dialog-top, .dialog-tag { cursor: pointer; @@ -1635,6 +1682,9 @@ display: none !important; } } + .dialog-avatar{ + display: none; + } &.self { .dialog-head{ background-color: #F4F5F7; @@ -1652,7 +1702,14 @@ color: #84C56A !important; } } - + .dialog-emoji{ + .avatar-name{ + color: #818181; + } + > li.hasme{ + background-color: #e1e1e1; + } + } } } }