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

View File

@ -75,7 +75,7 @@
<!-- 发送按钮 -->
<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 class="chat-load">
<Loading/>

View File

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

View File

@ -10,7 +10,7 @@
:class="headClass"
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"/>
<div class="reply-desc">{{formatMsgDesc(msgData.reply_data)}}</div>
</div>
@ -83,7 +83,7 @@
<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>
{{msgData.reply_num}}条回复
</div>
@ -154,6 +154,10 @@ export default {
type: Boolean,
default: false
},
hideReply: {
type: Boolean,
default: false
},
operateVisible: {
type: Boolean,
default: false
@ -393,6 +397,12 @@ export default {
this.$emit("on-view-file", this.msgData)
},
replyList() {
this.$emit("on-reply-list", {
msg_id: this.msgData.id,
})
},
onEmoji(symbol) {
this.$emit("on-emoji", {
msg_id: this.msgData.id,

View File

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

View File

@ -2182,7 +2182,7 @@ export default {
* @param state
* @param dispatch
* @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>}
*/
getDialogMsgs({state, dispatch, getters}, data) {
@ -2192,11 +2192,8 @@ export default {
reject({msg: 'Parameter error'});
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)) {
reject({msg: 'Loading'});
return

View File

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

View File

@ -13,6 +13,29 @@
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) {
overflow-y: overlay;
}