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