18 changed files with 513 additions and 284 deletions
@ -0,0 +1,56 @@ |
|||||
|
<template> |
||||
|
<scroll-view scroll-y="true" upper-threshold="200" @scrolltolower="onScrollToBottom" scroll-with-animation="true"> |
||||
|
<view v-for="(item, idx) in showItems" :key="idx"> |
||||
|
<slot :item="item"> |
||||
|
</slot> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: "virtual-scroller", |
||||
|
data() { |
||||
|
return { |
||||
|
page: 1, |
||||
|
isInitEvent: false, |
||||
|
lockTip: false |
||||
|
} |
||||
|
}, |
||||
|
props: { |
||||
|
items: { |
||||
|
type: Array |
||||
|
}, |
||||
|
size: { |
||||
|
type: Number, |
||||
|
default: 30 |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
onScrollToBottom(e) { |
||||
|
console.log("onScrollToBottom") |
||||
|
if (this.showMaxIdx >= this.items.length) { |
||||
|
this.showTip(); |
||||
|
} else { |
||||
|
this.page++; |
||||
|
} |
||||
|
}, |
||||
|
showTip() { |
||||
|
uni.showToast({ |
||||
|
title: "已滚动至底部", |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
showMaxIdx() { |
||||
|
return Math.min(this.page * this.size, this.items.length); |
||||
|
}, |
||||
|
showItems() { |
||||
|
return this.items.slice(0, this.showMaxIdx); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped></style> |
||||
@ -0,0 +1,20 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<web-view :src="linkUrl"></web-view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
linkUrl: '' |
||||
|
}; |
||||
|
}, |
||||
|
onLoad(options) { |
||||
|
this.linkUrl = decodeURIComponent(options.url); |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style></style> |
||||
@ -1,183 +1,189 @@ |
|||||
<template> |
<template> |
||||
<div v-show="show"> |
<div v-show="show"> |
||||
<div class="chat-group-readed-mask" @click.self="close()"> |
<div class="chat-group-readed-mask" @click.self="close()"> |
||||
<div class="chat-group-readed" :style="{ 'left': pos.x + 'px', 'top': pos.y + 'px' }" @click.prevent=""> |
<div class="chat-group-readed" :style="{ 'left': pos.x + 'px', 'top': pos.y + 'px' }" @click.prevent=""> |
||||
<el-tabs type="border-card" :stretch="true"> |
<el-tabs type="border-card" :stretch="true"> |
||||
<el-tab-pane :label="`已读(${readedMembers.length})`"> |
<el-tab-pane :label="`已读(${readedMembers.length})`"> |
||||
<el-scrollbar class="scroll-box"> |
<virtual-scroller class="scroll-box" :items="readedMembers"> |
||||
<div v-for="(member) in readedMembers" :key="member.id"> |
<template v-slot="{ item }"> |
||||
<chat-group-member :member="member"></chat-group-member> |
<chat-group-member :member="item"></chat-group-member> |
||||
</div> |
</template> |
||||
</el-scrollbar> |
</virtual-scroller> |
||||
</el-tab-pane> |
</el-tab-pane> |
||||
<el-tab-pane :label="`未读(${unreadMembers.length})`"> |
<el-tab-pane :label="`未读(${unreadMembers.length})`"> |
||||
<el-scrollbar class="scroll-box"> |
<virtual-scroller class="scroll-box" :items="unreadMembers"> |
||||
<div v-for="(member) in unreadMembers" :key="member.id"> |
<template v-slot="{ item }"> |
||||
<chat-group-member :member="member"></chat-group-member> |
<chat-group-member :member="item"></chat-group-member> |
||||
</div> |
</template> |
||||
</el-scrollbar> |
</virtual-scroller> |
||||
</el-tab-pane> |
</el-tab-pane> |
||||
</el-tabs> |
</el-tabs> |
||||
<div v-show="msgInfo.selfSend" class="arrow-right" :style="{ 'top': pos.arrowY + 'px' }"> |
<div v-show="msgInfo.selfSend" class="arrow-right" :style="{ 'top': pos.arrowY + 'px' }"> |
||||
<div class="arrow-right-inner"> |
<div class="arrow-right-inner"> |
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
<div v-show="!msgInfo.selfSend" class="arrow-left" :style="{ 'top': pos.arrowY + 'px' }"> |
<div v-show="!msgInfo.selfSend" class="arrow-left" :style="{ 'top': pos.arrowY + 'px' }"> |
||||
<div class="arrow-left-inner"> |
<div class="arrow-left-inner"> |
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
|
|
||||
</div> |
</div> |
||||
</template> |
</template> |
||||
|
|
||||
|
|
||||
<script> |
<script> |
||||
|
import VirtualScroller from '../common/VirtualScroller.vue'; |
||||
import ChatGroupMember from "./ChatGroupMember.vue"; |
import ChatGroupMember from "./ChatGroupMember.vue"; |
||||
|
|
||||
export default { |
export default { |
||||
name: "chatGroupReaded", |
name: "chatGroupReaded", |
||||
components: { |
components: { |
||||
ChatGroupMember |
ChatGroupMember, VirtualScroller |
||||
}, |
}, |
||||
data() { |
data() { |
||||
return { |
return { |
||||
show: false, |
show: false, |
||||
pos: { |
pos: { |
||||
x: 0, |
x: 0, |
||||
y: 0, |
y: 0, |
||||
arrowY: 0 |
arrowY: 0 |
||||
}, |
}, |
||||
readedMembers: [], |
readedMembers: [], |
||||
unreadMembers: [] |
unreadMembers: [] |
||||
} |
} |
||||
}, |
}, |
||||
props: { |
props: { |
||||
groupMembers: { |
groupMembers: { |
||||
type: Array |
type: Array |
||||
}, |
}, |
||||
msgInfo: { |
msgInfo: { |
||||
type: Object |
type: Object |
||||
} |
} |
||||
}, |
}, |
||||
methods: { |
methods: { |
||||
close() { |
close() { |
||||
this.show = false; |
this.show = false; |
||||
}, |
}, |
||||
open(rect) { |
open(rect) { |
||||
this.show = true; |
this.show = true; |
||||
this.pos.arrowY = 200; |
this.pos.arrowY = 200; |
||||
// 计算窗口位置 |
// 计算窗口位置 |
||||
if (this.msgInfo.selfSend) { |
if (this.msgInfo.selfSend) { |
||||
// 自己发的消息弹出在消息的左边 |
// 自己发的消息弹出在消息的左边 |
||||
this.pos.x = rect.left - 310; |
this.pos.x = rect.left - 310; |
||||
} else { |
} else { |
||||
// 别人发的消息弹窗在消息右边 |
// 别人发的消息弹窗在消息右边 |
||||
this.pos.x = rect.right + 20; |
this.pos.x = rect.right + 20; |
||||
} |
} |
||||
this.pos.y = rect.top + rect.height / 2 - 215; |
this.pos.y = rect.top + rect.height / 2 - 215; |
||||
// 防止窗口溢出 |
// 防止窗口溢出 |
||||
if (this.pos.y < 0) { |
if (this.pos.y < 0) { |
||||
this.pos.arrowY += this.pos.y |
this.pos.arrowY += this.pos.y |
||||
this.pos.y = 0; |
this.pos.y = 0; |
||||
} |
} |
||||
this.loadReadedUser() |
this.loadReadedUser() |
||||
}, |
}, |
||||
loadReadedUser() { |
loadReadedUser() { |
||||
this.readedMembers = []; |
this.readedMembers = []; |
||||
this.unreadMembers = []; |
this.unreadMembers = []; |
||||
this.$http({ |
this.$http({ |
||||
url: "/message/group/findReadedUsers", |
url: "/message/group/findReadedUsers", |
||||
method: 'get', |
method: 'get', |
||||
params: { groupId: this.msgInfo.groupId, messageId: this.msgInfo.id } |
params: { groupId: this.msgInfo.groupId, messageId: this.msgInfo.id } |
||||
}).then(userIds => { |
}).then(userIds => { |
||||
this.groupMembers.forEach(member => { |
this.groupMembers.forEach(member => { |
||||
// 发送者和已退群的不显示 |
// 发送者和已退群的不显示 |
||||
if (member.userId == this.msgInfo.sendId || member.quit) { |
if (member.userId == this.msgInfo.sendId || member.quit) { |
||||
return; |
return; |
||||
} |
} |
||||
// 区分已读还是未读 |
// 区分已读还是未读 |
||||
if (userIds.find(userId => member.userId == userId)) { |
if (userIds.find(userId => member.userId == userId)) { |
||||
this.readedMembers.push(member); |
this.readedMembers.push(member); |
||||
} else { |
} else { |
||||
this.unreadMembers.push(member); |
this.unreadMembers.push(member); |
||||
} |
} |
||||
}) |
}) |
||||
// 更新已读人数 |
// 更新已读人数 |
||||
this.$store.commit("updateMessage", { |
let msgInfo = { |
||||
id: this.msgInfo.id, |
id: this.msgInfo.id, |
||||
groupId: this.msgInfo.groupId, |
groupId: this.msgInfo.groupId, |
||||
readedCount: this.readedMembers.length |
readedCount: this.readedMembers.length |
||||
}) |
} |
||||
}) |
let chatInfo = { |
||||
} |
type: 'GROUP', |
||||
} |
targetId: this.msgInfo.groupId |
||||
|
} |
||||
|
this.$store.commit("updateMessage", [msgInfo, chatInfo]) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
</script> |
</script> |
||||
|
|
||||
<style lang="scss"> |
<style lang="scss"> |
||||
.chat-group-readed-mask { |
.chat-group-readed-mask { |
||||
position: fixed; |
position: fixed; |
||||
left: 0; |
left: 0; |
||||
top: 0; |
top: 0; |
||||
right: 0; |
right: 0; |
||||
bottom: 0; |
bottom: 0; |
||||
width: 100%; |
width: 100%; |
||||
height: 100%; |
height: 100%; |
||||
z-index: 9999; |
z-index: 9999; |
||||
} |
} |
||||
|
|
||||
.chat-group-readed { |
.chat-group-readed { |
||||
position: fixed; |
position: fixed; |
||||
width: 300px; |
width: 300px; |
||||
|
|
||||
.scroll-box { |
.scroll-box { |
||||
height: 400px; |
height: 400px; |
||||
} |
} |
||||
|
|
||||
.arrow-left { |
.arrow-left { |
||||
position: absolute; |
position: absolute; |
||||
left: -15px; |
left: -15px; |
||||
width: 0; |
width: 0; |
||||
height: 0; |
height: 0; |
||||
border-top: 15px solid transparent; |
border-top: 15px solid transparent; |
||||
border-bottom: 15px solid transparent; |
border-bottom: 15px solid transparent; |
||||
border-right: 15px solid #ccc; |
border-right: 15px solid #ccc; |
||||
|
|
||||
.arrow-left-inner { |
.arrow-left-inner { |
||||
position: absolute; |
position: absolute; |
||||
top: -12px; |
top: -12px; |
||||
left: 3px; |
left: 3px; |
||||
width: 0; |
width: 0; |
||||
height: 0; |
height: 0; |
||||
overflow: hidden; |
overflow: hidden; |
||||
border-top: 12px solid transparent; |
border-top: 12px solid transparent; |
||||
border-bottom: 12px solid transparent; |
border-bottom: 12px solid transparent; |
||||
border-right: 12px solid white; |
border-right: 12px solid white; |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
.arrow-right { |
.arrow-right { |
||||
position: absolute; |
position: absolute; |
||||
right: -15px; |
right: -15px; |
||||
width: 0; |
width: 0; |
||||
height: 0; |
height: 0; |
||||
border-top: 15px solid transparent; |
border-top: 15px solid transparent; |
||||
border-bottom: 15px solid transparent; |
border-bottom: 15px solid transparent; |
||||
border-left: 15px solid #ccc; |
border-left: 15px solid #ccc; |
||||
|
|
||||
.arrow-right-inner { |
.arrow-right-inner { |
||||
position: absolute; |
position: absolute; |
||||
top: -12px; |
top: -12px; |
||||
right: 3px; |
right: 3px; |
||||
width: 0; |
width: 0; |
||||
height: 0; |
height: 0; |
||||
overflow: hidden; |
overflow: hidden; |
||||
border-top: 12px solid transparent; |
border-top: 12px solid transparent; |
||||
border-bottom: 12px solid transparent; |
border-bottom: 12px solid transparent; |
||||
border-left: 12px solid white; |
border-left: 12px solid white; |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
</style> |
</style> |
||||
@ -0,0 +1,74 @@ |
|||||
|
<template> |
||||
|
<el-scrollbar ref="scrollbar"> |
||||
|
<div v-for="(item, idx) in items" :key="idx"> |
||||
|
<slot :item="item" v-if=" idx < showMaxIdx"> |
||||
|
</slot> |
||||
|
</div> |
||||
|
</el-scrollbar> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: "virtualScroller", |
||||
|
data() { |
||||
|
return { |
||||
|
page: 1, |
||||
|
isInitEvent: false, |
||||
|
lockTip: false |
||||
|
} |
||||
|
}, |
||||
|
props: { |
||||
|
items: { |
||||
|
type: Array |
||||
|
}, |
||||
|
size: { |
||||
|
type: Number, |
||||
|
default: 30 |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
init() { |
||||
|
this.page = 1; |
||||
|
this.initEvent(); |
||||
|
}, |
||||
|
initEvent() { |
||||
|
if (!this.isInitEvent) { |
||||
|
let scrollWrap = this.$refs.scrollbar.$el.querySelector('.el-scrollbar__wrap'); |
||||
|
scrollWrap.addEventListener('scroll', this.onScroll); |
||||
|
this.isInitEvent = true; |
||||
|
} |
||||
|
}, |
||||
|
onScroll(e) { |
||||
|
const scrollbar = e.target; |
||||
|
// 滚到底部 |
||||
|
if (scrollbar.scrollTop + scrollbar.clientHeight >= scrollbar.scrollHeight - 30) { |
||||
|
if(this.showMaxIdx >= this.items.length ){ |
||||
|
this.showTip(); |
||||
|
}else{ |
||||
|
this.page++; |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
showTip(){ |
||||
|
// 提示限制最多3秒显示一次 |
||||
|
if(!this.lockTip){ |
||||
|
this.$message.success("已到滚动到底部") |
||||
|
this.lockTip = true; |
||||
|
setTimeout(()=>{ |
||||
|
this.lockTip = false; |
||||
|
},3000) |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
showMaxIdx() { |
||||
|
return Math.min(this.page * this.size, this.items.length); |
||||
|
} |
||||
|
}, |
||||
|
mounted(){ |
||||
|
this.initEvent(); |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped></style> |
||||
Loading…
Reference in new issue