perf: 优化消息分页加载

This commit is contained in:
kuaifan 2022-06-27 20:17:59 +08:00
parent 17ce620abf
commit 58856c6620
4 changed files with 121 additions and 110 deletions

View File

@ -214,10 +214,11 @@ class DialogController extends AbstractController
* @apiName msg__lists
*
* @apiParam {Number} dialog_id 对话ID
* @apiParam {String} [position_id] 定位消息ID填写时page无效
* @apiParam {String} [position_id] 此消息ID前后的数据优先级1
* @apiParam {Number} [prev_id] 此消息ID之前的数据优先级2
* @apiParam {Number} [next_id] 此消息ID之后的数据优先级3
*
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:50,最大:100
* @apiParam {Number} [take] 获取条数,默认:50,最大:100
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@ -229,6 +230,9 @@ class DialogController extends AbstractController
//
$dialog_id = intval(Request::input('dialog_id'));
$position_id = intval(Request::input('position_id'));
$prev_id = intval(Request::input('prev_id'));
$next_id = intval(Request::input('next_id'));
$take = Base::getPaginate(100, 50, 'take');
//
$dialog = WebSocketDialog::checkDialog($dialog_id);
//
@ -240,16 +244,40 @@ class DialogController extends AbstractController
$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');
})->where('web_socket_dialog_msgs.dialog_id', $dialog_id);
//
$perPage = Base::getPaginate(100, 50);
if ($position_id > 0) {
$position_count = $builder->clone()->where('web_socket_dialog_msgs.id', '>=', $position_id)->count();
$list = $builder->paginate($perPage, [], 'page', ceil($position_count / $perPage));
} else {
$list = $builder->paginate($perPage);
$array = $builder->clone()
->where('web_socket_dialog_msgs.id', '>=', $position_id)
->orderBy('web_socket_dialog_msgs.id')
->take(intval($take / 2))
->get();
$prev_id = intval($array->last()?->id);
}
//
$cloner = $builder->clone();
if ($prev_id > 0) {
$cloner->where('web_socket_dialog_msgs.id', '<=', $prev_id)->orderByDesc('web_socket_dialog_msgs.id');
} elseif ($next_id > 0) {
$cloner->where('web_socket_dialog_msgs.id', '>=', $next_id)->orderBy('web_socket_dialog_msgs.id');
} else {
$cloner->orderByDesc('web_socket_dialog_msgs.id');
}
$list = $cloner->take($take)->get()->sortByDesc('id', SORT_NUMERIC)->values();
//
if ($list->isNotEmpty()) {
$first = $list->first();
$first->next_id = intval($builder->clone()
->where('web_socket_dialog_msgs.id', '>', $first->id)
->orderBy('web_socket_dialog_msgs.id')
->value('id'));
$last = $list->last();
$last->prev_id = intval($builder->clone()
->where('web_socket_dialog_msgs.id', '<', $last->id)
->orderByDesc('web_socket_dialog_msgs.id')
->value('id'));
}
// 记录当前打开的任务对话
if ($dialog->type == 'group' && $dialog->group_type == 'task') {
$user->task_dialog_id = $dialog->id;
$user->save();
@ -261,8 +289,9 @@ class DialogController extends AbstractController
$isMarkDialogUser->save();
}
//
$data = $list->toArray();
if ($list->currentPage() === 1) {
$data = [];
$data['list'] = $list;
if ($prev_id === 0 && $next_id === 0) {
$data['dialog'] = $dialog->formatData($user->userid);
}
return Base::retSuccess('success', $data);

View File

@ -81,7 +81,7 @@
:keeps="70"
@scroll="onScroll"
@range="onRange"
@totop="onNextPage"
@totop="onPrevPage"
@on-longpress="onLongpress"
@on-view-reply="onViewReply"
@ -89,7 +89,7 @@
@on-view-file="onViewFile"
@on-emoji="onEmoji">
<template slot="header">
<div v-if="(allMsgs.length === 0 && dialogData.loading > 0) || nextPage > 0" class="dialog-item loading"><Loading/></div>
<div v-if="loadMsg || prevId > 0" class="dialog-item loading"><Loading/></div>
<div v-else-if="allMsgs.length === 0" class="dialog-item nothing">{{$L('暂无消息')}}</div>
</template>
</VirtualList>
@ -256,7 +256,7 @@
</template>
<script>
import {mapState} from "vuex";
import {mapGetters, mapState} from "vuex";
import DialogItem from "./DialogItem";
import DialogUpload from "./DialogUpload";
import UserInput from "../../../components/UserInput";
@ -335,6 +335,10 @@ export default {
replyId: 0,
replyActiveIndex: -1,
scrollDirection: null,
scrollAction: 0,
scrollTmp: 0,
}
},
@ -354,6 +358,8 @@ export default {
'windowActive',
]),
...mapGetters(['isLoad']),
isReady() {
return this.dialogId > 0 && this.dialogData.id > 0
},
@ -393,13 +399,13 @@ export default {
return dialogMsgList;
},
nextPage() {
loadMsg() {
return this.isLoad(`msg::${this.dialogId}-0`)
},
prevId() {
if (this.allMsgs.length > 0) {
let topMsgPage = $A.runNum(this.allMsgs[0]._page);
let {lastPage} = this.dialogData;
if (topMsgPage < lastPage && lastPage > 1) {
return topMsgPage + 1
}
return $A.runNum(this.allMsgs[0].prev_id)
}
return 0
},
@ -466,22 +472,22 @@ export default {
watch: {
dialogId: {
handler(id) {
if (id) {
handler(dialog_id) {
if (dialog_id) {
this.msgNew = 0;
//
if (this.allMsgList.length > 0) {
this.allMsgs = this.allMsgList;
requestAnimationFrame(this.onToBottom);
}
this.$store.dispatch("getDialogMsgs", id).then(_ => {
this.openId = id;
this.$store.dispatch("getDialogMsgs", {dialog_id}).then(_ => {
this.openId = dialog_id;
setTimeout(this.onSearchMsgId, 100)
}).catch(_ => {});
//
this.$store.dispatch('saveInDialog', {
uid: this._uid,
dialog_id: id,
dialog_id,
})
//
if (this.autoFocus) {
@ -516,7 +522,9 @@ export default {
wsOpenNum(num) {
if (num <= 1) return
this.$store.dispatch("getDialogMsgs", this.dialogId).catch(_ => {});
this.$store.dispatch("getDialogMsgs", {
dialog_id: this.dialogId
}).catch(_ => {});
},
allMsgList(newList, oldList) {
@ -708,7 +716,7 @@ export default {
})
}
this.preventToBottom = true;
this.$store.dispatch("getDialogMoreMsgs", {
this.$store.dispatch("getDialogMsgs", {
dialog_id: this.dialogId,
position_id
}).finally(_ => {
@ -935,16 +943,15 @@ export default {
this.$store.dispatch("openTask", this.dialogData.group_info.id);
},
onNextPage() {
if (this.nextPage === 0) {
onPrevPage() {
if (this.prevId === 0) {
return
}
this.$store.dispatch('getDialogMoreMsgs', {
this.$store.dispatch('getDialogMsgs', {
dialog_id: this.dialogId,
page: this.nextPage
}).then(result => {
const resData = result.data;
const ids = resData.data.map(item => item.id)
prev_id: this.prevId
}).then(({data}) => {
const ids = data.list.map(item => item.id)
this.$nextTick(() => {
const scroller = this.$refs.scroller
const offset = ids.reduce((previousValue, currentId) => {
@ -952,7 +959,7 @@ export default {
return {size: previousSize + scroller.getSize(currentId)}
})
let size = scroller.getOffset() + offset.size;
if (this.nextPage === 0) {
if (this.prevId === 0) {
size -= 36
}
this.onToOffset(size);
@ -1015,7 +1022,7 @@ export default {
}
},
onScroll() {
onScroll(event) {
this.operateVisible = false;
//
const {tail} = this.scrollInfo();
@ -1023,25 +1030,31 @@ export default {
if (this.scrollTail <= 10) {
this.msgNew = 0;
}
//
this.scrollAction = event.target.scrollTop;
this.scrollDirection = this.scrollTmp <= this.scrollAction ? 'down' : 'up';
setTimeout(_ => this.scrollTmp = this.scrollAction, 0);
},
onRange(range) {
if (this.preventMoreLoad) {
return
}
let tmpPage = 0;
const key = this.scrollDirection === 'down' ? 'next_id' : 'prev_id';
for (let i = range.start; i <= range.end; i++) {
if (tmpPage - parseInt(this.allMsgs[i]._page) > 1) {
this.preventMoreLoad = true
this.$store.dispatch("getDialogMoreMsgs", {
dialog_id: this.dialogId,
page: tmpPage - 1
}).finally(_ => {
this.preventMoreLoad = false
})
break;
const rangeValue = this.allMsgs[i][key]
if (rangeValue) {
const nearMsg = this.allMsgs[i + (key === 'next_id' ? 1 : -1)]
if (nearMsg && nearMsg.id != rangeValue) {
this.preventMoreLoad = true
this.$store.dispatch("getDialogMsgs", {
[key]: rangeValue,
dialog_id: this.dialogId,
}).finally(_ => {
this.preventMoreLoad = false
})
}
}
tmpPage = parseInt(this.allMsgs[i]._page);
}
},

View File

@ -2181,88 +2181,45 @@ export default {
* 获取会话消息
* @param state
* @param dispatch
* @param dialog_id
* @param getters
* @param data {dialog_id, ?reply_id, ?position_id, ?prev_id, ?next_id}
* @returns {Promise<unknown>}
*/
getDialogMsgs({state, dispatch}, dialog_id) {
return new Promise(resolve => {
if (!dialog_id) {
resolve()
return;
}
let dialog = state.cacheDialogs.find(({id}) => id == dialog_id);
if (!dialog) {
dialog = {
id: dialog_id,
};
state.cacheDialogs.push(dialog);
}
if (dialog.loading) {
resolve()
return;
}
dialog.loading = true;
//
dispatch("call", {
url: 'dialog/msg/lists',
data: {
dialog_id: dialog_id,
page: 1
},
complete: _ => dialog.loading = false
}).then(result => {
const resData = result.data;
dialog.lastPage = resData.last_page;
dialog = Object.assign(dialog, resData.dialog)
//
const ids = resData.data.map(({id}) => id)
state.dialogMsgs = state.dialogMsgs.filter((item) => item.dialog_id != dialog_id || ids.includes(item.id));
//
dispatch("saveDialogMsg", resData.data.map(item => Object.assign(item, {_page: resData.current_page})));
resolve()
}).catch(e => {
console.warn(e);
resolve()
}).finally(_ => {
dispatch("saveDialog", dialog);
});
});
},
/**
* 获取更多会话消息指定页|定位页
* @param state
* @param dispatch
* @param data {dialog_id, ?page, ?position_id}
*/
getDialogMoreMsgs({state, dispatch}, data) {
return new Promise(function (resolve, reject) {
getDialogMsgs({state, dispatch, getters}, data) {
return new Promise((resolve, reject) => {
const dialog = state.cacheDialogs.find(({id}) => id == data.dialog_id);
if (!dialog) {
reject({msg: 'Parameter error'});
return;
}
if (dialog.loading) {
reject({msg: 'Loading'});
return;
if (!/^d+$/.test(data.reply_id)) {
data.reply_id = 0;
}
dialog.loading = true;
const loadKey = `msg::${data.dialog_id}-${data.reply_id}`
if (getters.isLoad(loadKey)) {
reject({msg: 'Loading'});
return
}
dispatch("setLoad", loadKey)
//
dispatch("call", {
url: 'dialog/msg/lists',
data,
complete: _ => dialog.loading = false
complete: _ => dispatch("cancelLoad", loadKey)
}).then(result => {
const resData = result.data;
dialog.lastPage = resData.last_page;
if ($A.isJson(resData.dialog)) {
dispatch("saveDialog", resData.dialog);
//
const ids = resData.list.map(({id}) => id)
state.dialogMsgs = state.dialogMsgs.filter(item => item.dialog_id != data.dialog_id || ids.includes(item.id));
}
//
dispatch("saveDialogMsg", resData.data.map(item => Object.assign(item, {_page: resData.current_page})));
dispatch("saveDialogMsg", resData.list);
resolve(result)
}).catch(e => {
console.warn(e);
reject(e)
}).finally(_ => {
dispatch("saveDialog", dialog);
});
});
},

View File

@ -1,4 +1,16 @@
export default {
/**
* 是否加载中
* @param state
* @returns {function(*)}
*/
isLoad(state) {
return function (key) {
const load = state.loads.find(item => item.key === key);
return load && load.num > 0
}
},
/**
* 当前打开的项目
* @param state