committed by
Gitee
77 changed files with 2465 additions and 1860 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> |
|||
<div v-show="show"> |
|||
<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=""> |
|||
<el-tabs type="border-card" :stretch="true"> |
|||
<el-tab-pane :label="`已读(${readedMembers.length})`"> |
|||
<el-scrollbar class="scroll-box"> |
|||
<div v-for="(member) in readedMembers" :key="member.id"> |
|||
<chat-group-member :member="member"></chat-group-member> |
|||
</div> |
|||
</el-scrollbar> |
|||
</el-tab-pane> |
|||
<el-tab-pane :label="`未读(${unreadMembers.length})`"> |
|||
<el-scrollbar class="scroll-box"> |
|||
<div v-for="(member) in unreadMembers" :key="member.id"> |
|||
<chat-group-member :member="member"></chat-group-member> |
|||
</div> |
|||
</el-scrollbar> |
|||
</el-tab-pane> |
|||
</el-tabs> |
|||
<div v-show="msgInfo.selfSend" class="arrow-right" :style="{ 'top': pos.arrowY + 'px' }"> |
|||
<div class="arrow-right-inner"> |
|||
</div> |
|||
</div> |
|||
<div v-show="!msgInfo.selfSend" class="arrow-left" :style="{ 'top': pos.arrowY + 'px' }"> |
|||
<div class="arrow-left-inner"> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div v-show="show"> |
|||
<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=""> |
|||
<el-tabs type="border-card" :stretch="true"> |
|||
<el-tab-pane :label="`已读(${readedMembers.length})`"> |
|||
<virtual-scroller class="scroll-box" :items="readedMembers"> |
|||
<template v-slot="{ item }"> |
|||
<chat-group-member :member="item"></chat-group-member> |
|||
</template> |
|||
</virtual-scroller> |
|||
</el-tab-pane> |
|||
<el-tab-pane :label="`未读(${unreadMembers.length})`"> |
|||
<virtual-scroller class="scroll-box" :items="unreadMembers"> |
|||
<template v-slot="{ item }"> |
|||
<chat-group-member :member="item"></chat-group-member> |
|||
</template> |
|||
</virtual-scroller> |
|||
</el-tab-pane> |
|||
</el-tabs> |
|||
<div v-show="msgInfo.selfSend" class="arrow-right" :style="{ 'top': pos.arrowY + 'px' }"> |
|||
<div class="arrow-right-inner"> |
|||
</div> |
|||
</div> |
|||
<div v-show="!msgInfo.selfSend" class="arrow-left" :style="{ 'top': pos.arrowY + 'px' }"> |
|||
<div class="arrow-left-inner"> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
|
|||
<script> |
|||
import VirtualScroller from '../common/VirtualScroller.vue'; |
|||
import ChatGroupMember from "./ChatGroupMember.vue"; |
|||
|
|||
export default { |
|||
name: "chatGroupReaded", |
|||
components: { |
|||
ChatGroupMember |
|||
}, |
|||
data() { |
|||
return { |
|||
show: false, |
|||
pos: { |
|||
x: 0, |
|||
y: 0, |
|||
arrowY: 0 |
|||
}, |
|||
readedMembers: [], |
|||
unreadMembers: [] |
|||
} |
|||
}, |
|||
props: { |
|||
groupMembers: { |
|||
type: Array |
|||
}, |
|||
msgInfo: { |
|||
type: Object |
|||
} |
|||
}, |
|||
methods: { |
|||
close() { |
|||
this.show = false; |
|||
}, |
|||
open(rect) { |
|||
this.show = true; |
|||
this.pos.arrowY = 200; |
|||
// 计算窗口位置 |
|||
if (this.msgInfo.selfSend) { |
|||
// 自己发的消息弹出在消息的左边 |
|||
this.pos.x = rect.left - 310; |
|||
} else { |
|||
// 别人发的消息弹窗在消息右边 |
|||
this.pos.x = rect.right + 20; |
|||
} |
|||
this.pos.y = rect.top + rect.height / 2 - 215; |
|||
// 防止窗口溢出 |
|||
if (this.pos.y < 0) { |
|||
this.pos.arrowY += this.pos.y |
|||
this.pos.y = 0; |
|||
} |
|||
this.loadReadedUser() |
|||
}, |
|||
loadReadedUser() { |
|||
this.readedMembers = []; |
|||
this.unreadMembers = []; |
|||
this.$http({ |
|||
url: "/message/group/findReadedUsers", |
|||
method: 'get', |
|||
params: { groupId: this.msgInfo.groupId, messageId: this.msgInfo.id } |
|||
}).then(userIds => { |
|||
this.groupMembers.forEach(member => { |
|||
// 发送者和已退群的不显示 |
|||
if (member.userId == this.msgInfo.sendId || member.quit) { |
|||
return; |
|||
} |
|||
// 区分已读还是未读 |
|||
if (userIds.find(userId => member.userId == userId)) { |
|||
this.readedMembers.push(member); |
|||
} else { |
|||
this.unreadMembers.push(member); |
|||
} |
|||
}) |
|||
name: "chatGroupReaded", |
|||
components: { |
|||
ChatGroupMember, VirtualScroller |
|||
}, |
|||
data() { |
|||
return { |
|||
show: false, |
|||
pos: { |
|||
x: 0, |
|||
y: 0, |
|||
arrowY: 0 |
|||
}, |
|||
readedMembers: [], |
|||
unreadMembers: [] |
|||
} |
|||
}, |
|||
props: { |
|||
groupMembers: { |
|||
type: Array |
|||
}, |
|||
msgInfo: { |
|||
type: Object |
|||
} |
|||
}, |
|||
methods: { |
|||
close() { |
|||
this.show = false; |
|||
}, |
|||
open(rect) { |
|||
this.show = true; |
|||
this.pos.arrowY = 200; |
|||
// 计算窗口位置 |
|||
if (this.msgInfo.selfSend) { |
|||
// 自己发的消息弹出在消息的左边 |
|||
this.pos.x = rect.left - 310; |
|||
} else { |
|||
// 别人发的消息弹窗在消息右边 |
|||
this.pos.x = rect.right + 20; |
|||
} |
|||
this.pos.y = rect.top + rect.height / 2 - 215; |
|||
// 防止窗口溢出 |
|||
if (this.pos.y < 0) { |
|||
this.pos.arrowY += this.pos.y |
|||
this.pos.y = 0; |
|||
} |
|||
this.loadReadedUser() |
|||
}, |
|||
loadReadedUser() { |
|||
this.readedMembers = []; |
|||
this.unreadMembers = []; |
|||
this.$http({ |
|||
url: "/message/group/findReadedUsers", |
|||
method: 'get', |
|||
params: { groupId: this.msgInfo.groupId, messageId: this.msgInfo.id } |
|||
}).then(userIds => { |
|||
this.groupMembers.forEach(member => { |
|||
// 发送者和已退群的不显示 |
|||
if (member.userId == this.msgInfo.sendId || member.quit) { |
|||
return; |
|||
} |
|||
// 区分已读还是未读 |
|||
if (userIds.find(userId => member.userId == userId)) { |
|||
this.readedMembers.push(member); |
|||
} else { |
|||
this.unreadMembers.push(member); |
|||
} |
|||
}) |
|||
// 更新已读人数 |
|||
this.$store.commit("updateMessage", { |
|||
let msgInfo = { |
|||
id: this.msgInfo.id, |
|||
groupId: this.msgInfo.groupId, |
|||
readedCount: this.readedMembers.length |
|||
}) |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
let chatInfo = { |
|||
type: 'GROUP', |
|||
targetId: this.msgInfo.groupId |
|||
} |
|||
this.$store.commit("updateMessage", [msgInfo, chatInfo]) |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.chat-group-readed-mask { |
|||
position: fixed; |
|||
left: 0; |
|||
top: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
z-index: 9999; |
|||
position: fixed; |
|||
left: 0; |
|||
top: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
z-index: 9999; |
|||
} |
|||
|
|||
.chat-group-readed { |
|||
position: fixed; |
|||
width: 300px; |
|||
position: fixed; |
|||
width: 300px; |
|||
|
|||
.scroll-box { |
|||
height: 400px; |
|||
} |
|||
.scroll-box { |
|||
height: 400px; |
|||
} |
|||
|
|||
.arrow-left { |
|||
position: absolute; |
|||
left: -15px; |
|||
width: 0; |
|||
height: 0; |
|||
border-top: 15px solid transparent; |
|||
border-bottom: 15px solid transparent; |
|||
border-right: 15px solid #ccc; |
|||
.arrow-left { |
|||
position: absolute; |
|||
left: -15px; |
|||
width: 0; |
|||
height: 0; |
|||
border-top: 15px solid transparent; |
|||
border-bottom: 15px solid transparent; |
|||
border-right: 15px solid #ccc; |
|||
|
|||
.arrow-left-inner { |
|||
position: absolute; |
|||
top: -12px; |
|||
left: 3px; |
|||
width: 0; |
|||
height: 0; |
|||
overflow: hidden; |
|||
border-top: 12px solid transparent; |
|||
border-bottom: 12px solid transparent; |
|||
border-right: 12px solid white; |
|||
} |
|||
} |
|||
.arrow-left-inner { |
|||
position: absolute; |
|||
top: -12px; |
|||
left: 3px; |
|||
width: 0; |
|||
height: 0; |
|||
overflow: hidden; |
|||
border-top: 12px solid transparent; |
|||
border-bottom: 12px solid transparent; |
|||
border-right: 12px solid white; |
|||
} |
|||
} |
|||
|
|||
.arrow-right { |
|||
position: absolute; |
|||
right: -15px; |
|||
width: 0; |
|||
height: 0; |
|||
border-top: 15px solid transparent; |
|||
border-bottom: 15px solid transparent; |
|||
border-left: 15px solid #ccc; |
|||
.arrow-right { |
|||
position: absolute; |
|||
right: -15px; |
|||
width: 0; |
|||
height: 0; |
|||
border-top: 15px solid transparent; |
|||
border-bottom: 15px solid transparent; |
|||
border-left: 15px solid #ccc; |
|||
|
|||
.arrow-right-inner { |
|||
position: absolute; |
|||
top: -12px; |
|||
right: 3px; |
|||
width: 0; |
|||
height: 0; |
|||
overflow: hidden; |
|||
border-top: 12px solid transparent; |
|||
border-bottom: 12px solid transparent; |
|||
border-left: 12px solid white; |
|||
} |
|||
} |
|||
.arrow-right-inner { |
|||
position: absolute; |
|||
top: -12px; |
|||
right: 3px; |
|||
width: 0; |
|||
height: 0; |
|||
overflow: hidden; |
|||
border-top: 12px solid transparent; |
|||
border-bottom: 12px solid transparent; |
|||
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> |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue