perf: 支持查看回复列表

This commit is contained in:
kuaifan 2022-06-28 10:01:09 +08:00
parent 13ca1b18d6
commit 66b7a00b5c
8 changed files with 156 additions and 31 deletions

View File

@ -279,9 +279,11 @@ class DialogController extends AbstractController
* @apiName msg__list * @apiName msg__list
* *
* @apiParam {Number} dialog_id 对话ID * @apiParam {Number} dialog_id 对话ID
* @apiParam {Number} [position_id] 此消息ID前后的数据优先级1 * @apiParam {Number} msg_id 消息ID
* @apiParam {Number} [prev_id] 此消息ID之前的数据优先级2 * @apiParam {Number} [position_id] 此消息ID前后的数据
* @apiParam {Number} [next_id] 此消息ID之后的数据优先级3 * @apiParam {Number} [prev_id] 此消息ID之前的数据
* @apiParam {Number} [next_id] 此消息ID之后的数据
* - position_id、prev_id、next_id 只有一个有效优先循序为position_id > prev_id > next_id
* *
* @apiParam {Number} [take] 获取条数,默认:50,最大:100 * @apiParam {Number} [take] 获取条数,默认:50,最大:100
* *
@ -295,6 +297,7 @@ class DialogController extends AbstractController
$user = User::auth(); $user = User::auth();
// //
$dialog_id = intval(Request::input('dialog_id')); $dialog_id = intval(Request::input('dialog_id'));
$msg_id = intval(Request::input('msg_id'));
$position_id = intval(Request::input('position_id')); $position_id = intval(Request::input('position_id'));
$prev_id = intval(Request::input('prev_id')); $prev_id = intval(Request::input('prev_id'));
$next_id = intval(Request::input('next_id')); $next_id = intval(Request::input('next_id'));
@ -302,6 +305,7 @@ class DialogController extends AbstractController
$data = []; $data = [];
// //
$dialog = WebSocketDialog::checkDialog($dialog_id); $dialog = WebSocketDialog::checkDialog($dialog_id);
$reDialog = true;
// //
$builder = WebSocketDialogMsg::select([ $builder = WebSocketDialogMsg::select([
'web_socket_dialog_msgs.*', 'web_socket_dialog_msgs.*',
@ -313,6 +317,11 @@ class DialogController extends AbstractController
->on('read.msg_id', '=', 'web_socket_dialog_msgs.id'); ->on('read.msg_id', '=', 'web_socket_dialog_msgs.id');
})->where('web_socket_dialog_msgs.dialog_id', $dialog_id); })->where('web_socket_dialog_msgs.dialog_id', $dialog_id);
// //
if ($msg_id > 0) {
$builder->whereReplyId($msg_id);
$reDialog = false;
}
//
if ($position_id > 0) { if ($position_id > 0) {
$array = $builder->clone() $array = $builder->clone()
->where('web_socket_dialog_msgs.id', '>=', $position_id) ->where('web_socket_dialog_msgs.id', '>=', $position_id)
@ -325,11 +334,12 @@ class DialogController extends AbstractController
$cloner = $builder->clone(); $cloner = $builder->clone();
if ($prev_id > 0) { if ($prev_id > 0) {
$cloner->where('web_socket_dialog_msgs.id', '<=', $prev_id)->orderByDesc('web_socket_dialog_msgs.id'); $cloner->where('web_socket_dialog_msgs.id', '<=', $prev_id)->orderByDesc('web_socket_dialog_msgs.id');
$reDialog = false;
} elseif ($next_id > 0) { } elseif ($next_id > 0) {
$cloner->where('web_socket_dialog_msgs.id', '>=', $next_id)->orderBy('web_socket_dialog_msgs.id'); $cloner->where('web_socket_dialog_msgs.id', '>=', $next_id)->orderBy('web_socket_dialog_msgs.id');
$reDialog = false;
} else { } else {
$cloner->orderByDesc('web_socket_dialog_msgs.id'); $cloner->orderByDesc('web_socket_dialog_msgs.id');
$data['dialog'] = $dialog->formatData($user->userid);
} }
$list = $cloner->take($take)->get()->sortByDesc('id', SORT_NUMERIC)->values(); $list = $cloner->take($take)->get()->sortByDesc('id', SORT_NUMERIC)->values();
// //
@ -358,6 +368,9 @@ class DialogController extends AbstractController
$isMarkDialogUser->save(); $isMarkDialogUser->save();
} }
// //
if ($reDialog) {
$data['dialog'] = $dialog->formatData($user->userid);
}
return Base::retSuccess('success', $data); return Base::retSuccess('success', $data);
} }

View File

@ -75,7 +75,7 @@
<!-- 发送按钮 --> <!-- 发送按钮 -->
<li class="chat-send" :class="sendClass" v-touchmouse="clickSend"> <li class="chat-send" :class="sendClass" v-touchmouse="clickSend">
<ETooltip placement="top" :disabled="windowSmall" :content="$L('发送')"> <ETooltip placement="top" :disabled="windowSmall" :content="$L(sendClass === 'recorder' ? '长按录音' : '发送')">
<div v-if="loading"> <div v-if="loading">
<div class="chat-load"> <div class="chat-load">
<Loading/> <Loading/>

View File

@ -7,12 +7,14 @@
:msg-data="source" :msg-data="source"
:dialog-type="dialogData.type" :dialog-type="dialogData.type"
:hide-percentage="hidePercentage" :hide-percentage="hidePercentage"
:hide-reply="hideReply"
:operate-visible="operateVisible" :operate-visible="operateVisible"
:operate-action="operateVisible && source.id === operateItem.id" :operate-action="operateVisible && source.id === operateItem.id"
@on-longpress="onLongpress" @on-longpress="onLongpress"
@on-view-reply="onViewReply" @on-view-reply="onViewReply"
@on-view-text="onViewText" @on-view-text="onViewText"
@on-view-file="onViewFile" @on-view-file="onViewFile"
@on-reply-list="onReplyList"
@on-emoji="onEmoji"/> @on-emoji="onEmoji"/>
</div> </div>
</template> </template>
@ -41,6 +43,14 @@ export default {
type: Boolean, type: Boolean,
default: false default: false
}, },
hideReply: {
type: Boolean,
default: false
},
isReply: {
type: Boolean, //
default: false
},
operateVisible: { operateVisible: {
type: Boolean, type: Boolean,
default: false default: false
@ -65,7 +75,7 @@ export default {
classArray() { classArray() {
return { return {
'dialog-item': true, 'dialog-item': true,
'self': this.source.userid == this.userId, 'self': !this.isReply && this.source.userid == this.userId,
} }
} }
}, },
@ -87,11 +97,20 @@ export default {
this.dispatch("on-view-file", data) this.dispatch("on-view-file", data)
}, },
onReplyList(data) {
this.dispatch("on-reply-list", data)
},
onEmoji(data) { onEmoji(data) {
this.dispatch("on-emoji", data) this.dispatch("on-emoji", data)
}, },
dispatch(event, arg) { dispatch(event, arg) {
if (this.isReply) {
this.$emit(event, arg)
return
}
let parent = this.$parent let parent = this.$parent
let name = parent.$options.name let name = parent.$options.name

View File

@ -10,7 +10,7 @@
:class="headClass" :class="headClass"
v-longpress="{callback: handleLongpress, delay: 300}"> v-longpress="{callback: handleLongpress, delay: 300}">
<!--回复--> <!--回复-->
<div v-if="msgData.reply_data" class="dialog-reply no-dark-content" @click="viewReply"> <div v-if="!hideReply && msgData.reply_data" class="dialog-reply no-dark-content" @click="viewReply">
<UserAvatar :userid="msgData.reply_data.userid" :show-icon="false" :show-name="true" :tooltip-disabled="true"/> <UserAvatar :userid="msgData.reply_data.userid" :show-icon="false" :show-name="true" :tooltip-disabled="true"/>
<div class="reply-desc">{{formatMsgDesc(msgData.reply_data)}}</div> <div class="reply-desc">{{formatMsgDesc(msgData.reply_data)}}</div>
</div> </div>
@ -83,7 +83,7 @@
<div class="dialog-foot"> <div class="dialog-foot">
<!--回复数--> <!--回复数-->
<div v-if="msgData.reply_num > 0" class="reply"> <div v-if="!hideReply && msgData.reply_num > 0" class="reply" @click="replyList">
<i class="taskfont">&#xe6eb;</i> <i class="taskfont">&#xe6eb;</i>
{{msgData.reply_num}}条回复 {{msgData.reply_num}}条回复
</div> </div>
@ -154,6 +154,10 @@ export default {
type: Boolean, type: Boolean,
default: false default: false
}, },
hideReply: {
type: Boolean,
default: false
},
operateVisible: { operateVisible: {
type: Boolean, type: Boolean,
default: false default: false
@ -393,6 +397,12 @@ export default {
this.$emit("on-view-file", this.msgData) this.$emit("on-view-file", this.msgData)
}, },
replyList() {
this.$emit("on-reply-list", {
msg_id: this.msgData.id,
})
},
onEmoji(symbol) { onEmoji(symbol) {
this.$emit("on-emoji", { this.$emit("on-emoji", {
msg_id: this.msgData.id, msg_id: this.msgData.id,

View File

@ -76,7 +76,7 @@
:data-component="msgItem" :data-component="msgItem"
:item-class-add="itemClassAdd" :item-class-add="itemClassAdd"
:extra-props="{dialogData, operateVisible, operateItem, hidePercentage: isMyDialog}" :extra-props="{dialogData, operateVisible, operateItem, hidePercentage: isMyDialog, hideReply: msgId > 0}"
:estimate-size="78" :estimate-size="78"
:keeps="70" :keeps="70"
@scroll="onScroll" @scroll="onScroll"
@ -87,6 +87,7 @@
@on-view-reply="onViewReply" @on-view-reply="onViewReply"
@on-view-text="onViewText" @on-view-text="onViewText"
@on-view-file="onViewFile" @on-view-file="onViewFile"
@on-reply-list="onReplyList"
@on-emoji="onEmoji"> @on-emoji="onEmoji">
<template slot="header"> <template slot="header">
<div v-if="loadMsg || prevId > 0" class="dialog-item loading"><Loading/></div> <div v-if="loadMsg || prevId > 0" class="dialog-item loading"><Loading/></div>
@ -110,7 +111,7 @@
ref="input" ref="input"
v-model="msgText" v-model="msgText"
:dialog-id="dialogId" :dialog-id="dialogId"
:reply-id="replyId" :reply-id="replyActiveId"
:emoji-bottom="windowSmall" :emoji-bottom="windowSmall"
:maxlength="200000" :maxlength="200000"
@on-focus="onEventFocus" @on-focus="onEventFocus"
@ -139,7 +140,7 @@
<DropdownMenu slot="list"> <DropdownMenu slot="list">
<DropdownItem name="action"> <DropdownItem name="action">
<ul class="operate-action"> <ul class="operate-action">
<li @click="onOperate('reply')"> <li v-if="msgId === 0" @click="onOperate('reply')">
<i class="taskfont">&#xe6eb;</i> <i class="taskfont">&#xe6eb;</i>
<span>{{ $L('回复') }}</span> <span>{{ $L('回复') }}</span>
</li> </li>
@ -252,6 +253,29 @@
:size="400"> :size="400">
<DialogGroupInfo v-if="groupInfoShow" :dialogId="dialogId"/> <DialogGroupInfo v-if="groupInfoShow" :dialogId="dialogId"/>
</DrawerOverlay> </DrawerOverlay>
<!--回复列表-->
<DrawerOverlay
v-model="replyListShow"
placement="right"
:size="500">
<DialogWrapper
v-if="replyListShow && replyListItem"
:dialogId="dialogId"
:msgId="replyListId"
class="reply-list">
<div slot="head" class="dialog-scroller">
<DialogItem
:source="replyListItem"
@on-view-text="onViewText"
@on-view-file="onViewFile"
@on-emoji="onEmoji"
hidePercentage
hideReply
isReply/>
</div>
</DialogWrapper>
</DrawerOverlay>
</div> </div>
</template> </template>
@ -271,6 +295,7 @@ import {textImagesInfo} from "../../../functions/utils";
export default { export default {
name: "DialogWrapper", name: "DialogWrapper",
components: { components: {
DialogItem,
VirtualList, VirtualList,
ChatInput, ChatInput,
DialogGroupInfo, DialogGroupInfo,
@ -284,6 +309,10 @@ export default {
type: Number, type: Number,
default: 0 default: 0
}, },
msgId: {
type: Number,
default: 0
},
autoFocus: { autoFocus: {
type: Boolean, type: Boolean,
default: false default: false
@ -333,9 +362,12 @@ export default {
preventMoreLoad: false, preventMoreLoad: false,
preventToBottom: false, preventToBottom: false,
replyId: 0, replyActiveId: 0,
replyActiveIndex: -1, replyActiveIndex: -1,
replyListShow: false,
replyListId: 0,
scrollDirection: null, scrollDirection: null,
scrollAction: 0, scrollAction: 0,
scrollTmp: 0, scrollTmp: 0,
@ -372,8 +404,11 @@ export default {
if (!this.isReady) { if (!this.isReady) {
return []; return [];
} }
return this.dialogMsgs.filter(({dialog_id}) => { return this.dialogMsgs.filter(item => {
return dialog_id == this.dialogId; if (this.msgId) {
return item.reply_id == this.msgId;
}
return item.dialog_id == this.dialogId;
}).sort((a, b) => { }).sort((a, b) => {
return a.id - b.id; return a.id - b.id;
}); });
@ -383,8 +418,11 @@ export default {
if (!this.isReady) { if (!this.isReady) {
return []; return [];
} }
return this.tempMsgs.filter(({dialog_id}) => { return this.tempMsgs.filter(item => {
return dialog_id == this.dialogId; if (this.msgId) {
return item.reply_id == this.msgId;
}
return item.dialog_id == this.dialogId;
}); });
}, },
@ -400,7 +438,7 @@ export default {
}, },
loadMsg() { loadMsg() {
return this.isLoad(`msg::${this.dialogId}-0`) return this.isLoad(`msg::${this.dialogId}-${this.msgId}`)
}, },
prevId() { prevId() {
@ -467,6 +505,18 @@ export default {
isMyDialog() { isMyDialog() {
const {dialogData, userId} = this; const {dialogData, userId} = this;
return dialogData.dialog_user && dialogData.dialog_user.userid == userId return dialogData.dialog_user && dialogData.dialog_user.userid == userId
},
replyId() {
return parseInt(this.msgId > 0 ? this.msgId : this.replyActiveId)
},
replyItem() {
return this.replyId ? this.dialogMsgs.find(({id}) => id === this.replyId) : null
},
replyListItem() {
return this.replyListId ? this.dialogMsgs.find(item => item.id == this.replyListId) : null
} }
}, },
@ -480,7 +530,10 @@ export default {
this.allMsgs = this.allMsgList; this.allMsgs = this.allMsgList;
requestAnimationFrame(this.onToBottom); requestAnimationFrame(this.onToBottom);
} }
this.$store.dispatch("getDialogMsgs", {dialog_id}).then(_ => { this.$store.dispatch("getDialogMsgs", {
dialog_id,
msg_id: this.msgId
}).then(_ => {
this.openId = dialog_id; this.openId = dialog_id;
setTimeout(this.onSearchMsgId, 100) setTimeout(this.onSearchMsgId, 100)
}).catch(_ => {}); }).catch(_ => {});
@ -523,7 +576,8 @@ export default {
wsOpenNum(num) { wsOpenNum(num) {
if (num <= 1) return if (num <= 1) return
this.$store.dispatch("getDialogMsgs", { this.$store.dispatch("getDialogMsgs", {
dialog_id: this.dialogId dialog_id: this.dialogId,
msg_id: this.msgId,
}).catch(_ => {}); }).catch(_ => {});
}, },
@ -594,7 +648,7 @@ export default {
id: tempId, id: tempId,
dialog_id: this.dialogData.id, dialog_id: this.dialogData.id,
reply_id: this.replyId, reply_id: this.replyId,
reply_data: this.replyId ? this.dialogMsgs.find(({id}) => id === this.replyId) : null, reply_data: this.replyItem,
type: 'text', type: 'text',
userid: this.userId, userid: this.userId,
msg: { msg: {
@ -612,7 +666,6 @@ export default {
data: { data: {
dialog_id: this.dialogId, dialog_id: this.dialogId,
reply_id: this.replyId, reply_id: this.replyId,
reply_data: this.replyId ? this.dialogMsgs.find(({id}) => id === this.replyId) : null,
text: msgText, text: msgText,
}, },
method: 'post' method: 'post'
@ -638,7 +691,7 @@ export default {
id: tempId, id: tempId,
dialog_id: this.dialogData.id, dialog_id: this.dialogData.id,
reply_id: this.replyId, reply_id: this.replyId,
reply_data: this.replyId ? this.dialogMsgs.find(({id}) => id === this.replyId) : null, reply_data: this.replyItem,
type: 'loading', type: 'loading',
userid: this.userId, userid: this.userId,
msg, msg,
@ -718,6 +771,7 @@ export default {
this.preventToBottom = true; this.preventToBottom = true;
this.$store.dispatch("getDialogMsgs", { this.$store.dispatch("getDialogMsgs", {
dialog_id: this.dialogId, dialog_id: this.dialogId,
msg_id: this.msgId,
position_id position_id
}).finally(_ => { }).finally(_ => {
const index = this.allMsgs.findIndex(item => item.id === position_id) const index = this.allMsgs.findIndex(item => item.id === position_id)
@ -949,6 +1003,7 @@ export default {
} }
this.$store.dispatch('getDialogMsgs', { this.$store.dispatch('getDialogMsgs', {
dialog_id: this.dialogId, dialog_id: this.dialogId,
msg_id: this.msgId,
prev_id: this.prevId prev_id: this.prevId
}).then(({data}) => { }).then(({data}) => {
const ids = data.list.map(item => item.id) const ids = data.list.map(item => item.id)
@ -1048,8 +1103,9 @@ export default {
if (nearMsg && nearMsg.id != rangeValue) { if (nearMsg && nearMsg.id != rangeValue) {
this.preventMoreLoad = true this.preventMoreLoad = true
this.$store.dispatch("getDialogMsgs", { this.$store.dispatch("getDialogMsgs", {
[key]: rangeValue,
dialog_id: this.dialogId, dialog_id: this.dialogId,
msg_id: this.msgId,
[key]: rangeValue,
}).finally(_ => { }).finally(_ => {
this.preventMoreLoad = false this.preventMoreLoad = false
}) })
@ -1151,7 +1207,7 @@ export default {
onReply() { onReply() {
const {tail} = this.scrollInfo() const {tail} = this.scrollInfo()
this.replyId = this.operateItem.id this.replyActiveId = this.operateItem.id
this.inputFocus() this.inputFocus()
if (tail <= 10) { if (tail <= 10) {
requestAnimationFrame(this.onToBottom) requestAnimationFrame(this.onToBottom)
@ -1159,7 +1215,7 @@ export default {
}, },
onCancelReply() { onCancelReply() {
this.replyId = 0; this.replyActiveId = 0;
}, },
onWithdraw() { onWithdraw() {
@ -1307,6 +1363,14 @@ export default {
}); });
}, },
onReplyList(data) {
if (this.operateVisible) {
return
}
this.replyListId = data.msg_id
this.replyListShow = true
},
onEmoji(data) { onEmoji(data) {
if (!$A.isJson(data)) { if (!$A.isJson(data)) {
data = { data = {

View File

@ -2182,7 +2182,7 @@ export default {
* @param state * @param state
* @param dispatch * @param dispatch
* @param getters * @param getters
* @param data {dialog_id, ?reply_id, ?position_id, ?prev_id, ?next_id} * @param data {dialog_id, msg_id, ?position_id, ?prev_id, ?next_id}
* @returns {Promise<unknown>} * @returns {Promise<unknown>}
*/ */
getDialogMsgs({state, dispatch, getters}, data) { getDialogMsgs({state, dispatch, getters}, data) {
@ -2192,11 +2192,8 @@ export default {
reject({msg: 'Parameter error'}); reject({msg: 'Parameter error'});
return; return;
} }
if (!/^d+$/.test(data.reply_id)) {
data.reply_id = 0;
}
// //
const loadKey = `msg::${data.dialog_id}-${data.reply_id}` const loadKey = `msg::${data.dialog_id}-${data.msg_id}`
if (getters.isLoad(loadKey)) { if (getters.isLoad(loadKey)) {
reject({msg: 'Loading'}); reject({msg: 'Loading'});
return return

View File

@ -607,7 +607,6 @@
} }
.mention-item-name { .mention-item-name {
flex-shrink: 0;
padding: 0 8px; padding: 0 8px;
font-size: 14px; font-size: 14px;
overflow: hidden; overflow: hidden;

View File

@ -13,6 +13,29 @@
overflow: hidden; overflow: hidden;
} }
&.reply-list {
border-radius: 18px 0 0 18px;
.dialog-nav {
position: relative;
&:before {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
background-color: #f4f5f5;
}
.dialog-scroller {
padding: 16px 16px 0;
}
}
}
.vue-recycle-scroller.direction-vertical:not(.page-mode) { .vue-recycle-scroller.direction-vertical:not(.page-mode) {
overflow-y: overlay; overflow-y: overlay;
} }