mirror of
https://github.com/kuaifan/dootask.git
synced 2026-03-04 16:37:06 +00:00
perf: 优化消息列表
This commit is contained in:
parent
4fcdeb0313
commit
d9cd7a93cc
@ -54,7 +54,7 @@
|
||||
"vue-resize-observer": "^2.0.16",
|
||||
"vue-router": "^3.5.3",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"vue-virtual-scroller-hi": "^1.0.10-3",
|
||||
"vue-virtual-scroll-list": "^2.3.3",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.6.2",
|
||||
"webpack": "^5.69.1",
|
||||
|
||||
108
resources/assets/js/pages/manage/components/DialogItem.vue
Normal file
108
resources/assets/js/pages/manage/components/DialogItem.vue
Normal file
@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div :class="classArray">
|
||||
<div class="dialog-avatar">
|
||||
<UserAvatar :userid="source.userid" :tooltipDisabled="source.userid == userId" :size="30"/>
|
||||
</div>
|
||||
<DialogView
|
||||
ref="view"
|
||||
:msg-data="source"
|
||||
:dialog-type="dialogData.type"
|
||||
:hide-percentage="isMyDialog"
|
||||
:operate-visible="operateVisible"
|
||||
:operate-action="operateVisible && source.id === operateItem.id"
|
||||
@on-longpress="onLongpress"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {mapState} from "vuex";
|
||||
import DialogView from "./DialogView";
|
||||
import {Store} from "le5le-store";
|
||||
|
||||
export default {
|
||||
name: "DialogItem",
|
||||
components: {DialogView},
|
||||
props: {
|
||||
source: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
dialogData: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
isMyDialog: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
operateVisible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
operateItem: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
subscribe: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.subscribe = Store.subscribe('dialogOperate', this.onOperate);
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.subscribe.unsubscribe();
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['userId']),
|
||||
|
||||
classArray() {
|
||||
return {
|
||||
'dialog-item': true,
|
||||
'self': this.source.userid == this.userId,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onLongpress(e) {
|
||||
Store.set('dialogLongpress', e);
|
||||
},
|
||||
|
||||
onOperate({id, action, value}) {
|
||||
if (id === this.source.id) {
|
||||
switch (action) {
|
||||
case "withdraw":
|
||||
this.$refs.view.withdraw()
|
||||
break;
|
||||
|
||||
case "view":
|
||||
this.$refs.view.viewFile()
|
||||
break;
|
||||
|
||||
case "down":
|
||||
this.$refs.view.downFile()
|
||||
break;
|
||||
|
||||
case "emoji":
|
||||
this.$refs.view.setEmoji(value)
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -70,49 +70,19 @@
|
||||
</div>
|
||||
|
||||
<!--消息列表-->
|
||||
<DynamicScroller
|
||||
<VirtualList
|
||||
ref="scroller"
|
||||
class="dialog-scroller scrollbar-overlay"
|
||||
:style="{opacity: scrollOpacity ? 1 : 0}"
|
||||
:disabled="touchBackInProgress"
|
||||
:items="allMsgs"
|
||||
:min-item-size="58"
|
||||
@onScroll="onScroll">
|
||||
<template #before>
|
||||
<template v-if="allMsgs.length === 0">
|
||||
<div v-if="dialogData.loading > 0" class="dialog-item loading"><Loading/></div>
|
||||
<div v-else class="dialog-item nothing">{{$L('暂无消息')}}</div>
|
||||
</template>
|
||||
<div v-else-if="dialogData.hasMorePages" class="dialog-item history" @click="loadNextPage">{{$L('加载历史消息')}}</div>
|
||||
</template>
|
||||
<template v-slot="{ item, index, active }">
|
||||
<DynamicScrollerItem
|
||||
:item="item"
|
||||
:active="active"
|
||||
:size-dependencies="[item.msg]"
|
||||
:data-index="index"
|
||||
:data-active="active"
|
||||
:class="{
|
||||
'dialog-item': true,
|
||||
'self': item.userid == userId,
|
||||
'history-tip': topId == item.id
|
||||
}"
|
||||
@click.native="">
|
||||
<em v-if="topId == item.id" class="history-text">{{$L('历史消息')}}</em>
|
||||
<div class="dialog-avatar">
|
||||
<UserAvatar :userid="item.userid" :tooltipDisabled="item.userid == userId" :size="30"/>
|
||||
</div>
|
||||
<DialogView
|
||||
:ref="`msg_${item.id}`"
|
||||
:msg-data="item"
|
||||
:dialog-type="dialogData.type"
|
||||
:hide-percentage="isMyDialog"
|
||||
:operate-visible="operateVisible"
|
||||
:operate-action="operateVisible && item.id === operateItem.id"
|
||||
@on-longpress="onLongpress"/>
|
||||
</DynamicScrollerItem>
|
||||
</template>
|
||||
</DynamicScroller>
|
||||
:data-key="'id'"
|
||||
:data-sources="allMsgs"
|
||||
:data-component="msgItem"
|
||||
:extra-props="{dialogData, isMyDialog, operateVisible, operateItem}"
|
||||
:estimate-size="78"
|
||||
:keeps="80"
|
||||
@scroll="onScroll"
|
||||
@totop="loadNextPage">
|
||||
<div slot="header" v-if="!dialogData.loading && allMsgs.length === 0" class="dialog-item nothing">{{$L('暂无消息')}}</div>
|
||||
</VirtualList>
|
||||
|
||||
<!--底部输入-->
|
||||
<div class="dialog-footer" :class="{newmsg: msgNew > 0 && allMsgs.length > 0}" @click="onActive">
|
||||
@ -268,28 +238,25 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import DialogView from "./DialogView";
|
||||
import DialogItem from "./DialogItem";
|
||||
import DialogUpload from "./DialogUpload";
|
||||
import UserInput from "../../../components/UserInput";
|
||||
import DrawerOverlay from "../../../components/DrawerOverlay";
|
||||
import DialogGroupInfo from "./DialogGroupInfo";
|
||||
import ChatInput from "./ChatInput";
|
||||
|
||||
import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller-hi'
|
||||
import 'vue-virtual-scroller-hi/dist/vue-virtual-scroller.css'
|
||||
import VirtualList from 'vue-virtual-scroll-list'
|
||||
import {Store} from "le5le-store";
|
||||
|
||||
export default {
|
||||
name: "DialogWrapper",
|
||||
components: {
|
||||
DynamicScroller,
|
||||
DynamicScrollerItem,
|
||||
VirtualList,
|
||||
ChatInput,
|
||||
DialogGroupInfo,
|
||||
DrawerOverlay,
|
||||
UserInput,
|
||||
DialogUpload,
|
||||
DialogView
|
||||
DialogUpload
|
||||
},
|
||||
|
||||
props: {
|
||||
@ -306,9 +273,9 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
msgItem: DialogItem,
|
||||
msgText: '',
|
||||
msgNew: 0,
|
||||
topId: 0,
|
||||
|
||||
allMsgs: [],
|
||||
tempMsgs: [],
|
||||
@ -329,7 +296,7 @@ export default {
|
||||
|
||||
dialogDrag: false,
|
||||
groupInfoShow: false,
|
||||
scrollOpacity: true,
|
||||
dialogSubscribe: null,
|
||||
|
||||
navStyle: {},
|
||||
|
||||
@ -344,8 +311,13 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.dialogSubscribe = Store.subscribe('dialogLongpress', this.onLongpress)
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.$store.dispatch('forgetInDialog', this._uid)
|
||||
this.dialogSubscribe.unsubscribe();
|
||||
},
|
||||
|
||||
computed: {
|
||||
@ -453,7 +425,6 @@ export default {
|
||||
handler(id) {
|
||||
if (id) {
|
||||
this.msgNew = 0;
|
||||
this.topId = -1;
|
||||
//
|
||||
if (this.allMsgList.length > 0) {
|
||||
this.allMsgs = this.allMsgList;
|
||||
@ -500,35 +471,25 @@ export default {
|
||||
},
|
||||
|
||||
allMsgList(newList, oldList) {
|
||||
const {scrollE} = this.scrollInfo();
|
||||
if (oldList.length === 0) {
|
||||
this.scrollOpacity = false;
|
||||
}
|
||||
const {balance} = this.scrollInfo();
|
||||
this.allMsgs = newList;
|
||||
//
|
||||
if (!this.windowActive || (scrollE > 10 && oldList.length > 0)) {
|
||||
if (!this.windowActive || (balance > 10 && oldList.length > 0)) {
|
||||
const lastId = oldList[oldList.length - 1].id
|
||||
const tmpList = newList.filter(item => item.id && item.id > lastId)
|
||||
this.msgNew += tmpList.length
|
||||
} else {
|
||||
requestAnimationFrame(this.onToBottom)
|
||||
}
|
||||
//
|
||||
this.allMsgTimer && clearTimeout(this.allMsgTimer);
|
||||
if (!this.scrollOpacity) {
|
||||
this.allMsgTimer = setTimeout(_=>{
|
||||
this.scrollOpacity = true;
|
||||
},100)
|
||||
}
|
||||
},
|
||||
|
||||
windowScrollY(val) {
|
||||
if ($A.isIos()) {
|
||||
const {scrollE} = this.scrollInfo();
|
||||
const {balance} = this.scrollInfo();
|
||||
this.navStyle = {
|
||||
marginTop: val + 'px'
|
||||
}
|
||||
if (scrollE <= 10) {
|
||||
if (balance <= 10) {
|
||||
requestAnimationFrame(this.onToBottom)
|
||||
}
|
||||
}
|
||||
@ -698,12 +659,12 @@ export default {
|
||||
}
|
||||
if (this.wrapperStart.clientY > e.touches[0].clientY) {
|
||||
// 向上滑动
|
||||
if (this.wrapperStart.scrollE === 0) {
|
||||
if (this.wrapperStart.balance === 0) {
|
||||
e.preventDefault();
|
||||
}
|
||||
} else {
|
||||
// 向下滑动
|
||||
if (this.wrapperStart.scrollY === 0) {
|
||||
if (this.wrapperStart.offset === 0) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
@ -808,14 +769,16 @@ export default {
|
||||
},
|
||||
|
||||
loadNextPage() {
|
||||
let tmpId = this.allMsgs[0].id;
|
||||
this.$store.dispatch('getDialogMoreMsgs', this.dialogId).then(() => {
|
||||
this.$store.dispatch('getDialogMoreMsgs', this.dialogId).then(result => {
|
||||
const resData = result.data;
|
||||
const ids = resData.data.map(item => item.id)
|
||||
this.$nextTick(() => {
|
||||
this.topId = tmpId;
|
||||
const index = this.allMsgs.findIndex(({id}) => id == tmpId);
|
||||
if (index > -1) {
|
||||
this.$refs.scroller.scrollToItem(index);
|
||||
}
|
||||
const scroller = this.$refs.scroller
|
||||
const offset = ids.reduce((previousValue, currentId) => {
|
||||
const previousSize = typeof previousValue === "object" ? previousValue.size : scroller.getSize(previousValue)
|
||||
return {size: previousSize + scroller.getSize(currentId)}
|
||||
})
|
||||
scroller.scrollToOffset(offset.size);
|
||||
});
|
||||
}).catch(() => {})
|
||||
},
|
||||
@ -876,22 +839,21 @@ export default {
|
||||
},
|
||||
|
||||
scrollInfo() {
|
||||
if (!this.isReady) {
|
||||
const scroller = this.$refs.scroller
|
||||
if (!scroller) {
|
||||
return {
|
||||
scale: 0, //已滚动比例
|
||||
scrollY: 0, //滚动的距离
|
||||
scrollE: 0, //与底部距离
|
||||
offset: 0, //滚动的距离
|
||||
balance: 0, //与底部距离
|
||||
}
|
||||
}
|
||||
const scrollerView = this.$refs.scroller.$el;
|
||||
let wInnerH = scrollerView.clientHeight;
|
||||
let wScrollY = scrollerView.scrollTop;
|
||||
let bScrollH = scrollerView.scrollHeight;
|
||||
this.scrollY = wScrollY;
|
||||
let clientSize = scroller.getClientSize();
|
||||
let offset = scroller.getOffset();
|
||||
let scrollSize = scroller.getScrollSize();
|
||||
return {
|
||||
scale: wScrollY / (bScrollH - wInnerH),
|
||||
scrollY: wScrollY,
|
||||
scrollE: bScrollH - wInnerH - wScrollY,
|
||||
scale: offset / (scrollSize - clientSize),
|
||||
offset: offset,
|
||||
balance: scrollSize - clientSize - offset,
|
||||
}
|
||||
},
|
||||
|
||||
@ -899,8 +861,8 @@ export default {
|
||||
this.operateVisible = false;
|
||||
this.__onScroll && clearTimeout(this.__onScroll);
|
||||
this.__onScroll = setTimeout(_ => {
|
||||
const {scrollE} = this.scrollInfo();
|
||||
if (scrollE <= 10) {
|
||||
const {balance} = this.scrollInfo();
|
||||
if (balance <= 10) {
|
||||
this.msgNew = 0;
|
||||
}
|
||||
}, 100)
|
||||
@ -945,10 +907,10 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
onOperate(name, value = null) {
|
||||
onOperate(action, value = null) {
|
||||
this.operateVisible = false;
|
||||
this.$nextTick(_ => {
|
||||
switch (name) {
|
||||
switch (action) {
|
||||
case "copy":
|
||||
if (this.operateHasText) {
|
||||
this.$copyText(this.operateItem.msg.text.replace(/<[^>]+>/g, "")).then(_ => {
|
||||
@ -975,19 +937,10 @@ export default {
|
||||
break;
|
||||
|
||||
case "withdraw":
|
||||
this.$refs[`msg_${this.operateItem.id}`].withdraw()
|
||||
break;
|
||||
|
||||
case "view":
|
||||
this.$refs[`msg_${this.operateItem.id}`].viewFile()
|
||||
break;
|
||||
|
||||
case "down":
|
||||
this.$refs[`msg_${this.operateItem.id}`].downFile()
|
||||
break;
|
||||
|
||||
case "emoji":
|
||||
this.$refs[`msg_${this.operateItem.id}`].setEmoji(value)
|
||||
Store.set('dialogOperate', {id: this.operateItem.id, action, value});
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
@ -646,62 +646,12 @@
|
||||
|
||||
}
|
||||
|
||||
&.history {
|
||||
cursor: pointer;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
padding: 3px 0;
|
||||
margin: 0 0 16px;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.history-tip {
|
||||
position: relative;
|
||||
padding-top: 60px;
|
||||
|
||||
.history-text {
|
||||
font-style: normal;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
padding: 0 48px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
border-radius: 2px;
|
||||
background: #f5f5f5;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
&.loading {
|
||||
padding: 0 0 16px;
|
||||
justify-content: center;
|
||||
|
||||
.common-loading {
|
||||
margin: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
&.nothing {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: $primary-desc-color;
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user