337 lines
9.8 KiB
Vue
337 lines
9.8 KiB
Vue
![]() |
<!-- 聊天记录模式+虚拟列表演示(vue),加载更多聊天记录无闪动&支持渲染大量数据 -->
|
|||
|
<!-- 注意:虚拟列表缓存高度默认仅在cell初始化时获取一次,若您的聊天列表中带有图片,强烈建议给图片一个固定的高度,如果高度根据内容动态撑高,可能导致虚拟列表抖动 -->
|
|||
|
<!-- 如果必须实现高度根据内容动态撑高+虚拟列表功能,可监听图片加载完毕事件,并在其中调用z-paging的didUpdateVirtualListCell刷新缓存高度 -->
|
|||
|
<template>
|
|||
|
<view class="content">
|
|||
|
<!-- use-chat-record-mode:开启聊天记录模式 -->
|
|||
|
<!-- use-virtual-list:开启虚拟列表模式 -->
|
|||
|
<!-- cell-height-mode:设置虚拟列表模式高度不固定 -->
|
|||
|
<!-- safe-area-inset-bottom:开启底部安全区域适配 -->
|
|||
|
<!-- bottom-bg-color:设置slot="bottom"容器的背景色,这里设置为和chat-input-bar的背景色一致 -->
|
|||
|
<z-paging ref="paging" v-model="dataList" :paging-style="{ paddingTop: paddingTop + 'px' }" default-page-size="20" :innerListStyle="innerListStyle" use-chat-record-mode use-virtual-list cell-height-mode="dynamic" :auto-clean-list-when-reload="false" safe-area-inset-bottom bottom-bg-color="#fafafa"
|
|||
|
|
|||
|
@query="queryList" :back-to-top-style="backToTopStyle" :auto-show-back-to-top="showNewMessageTip" @scrolltoupper="onScrollToUpper" @backToTopClick="onBackToTopClick" @keyboardHeightChange="keyboardHeightChange" @hidedKeyboard="hidedKeyboard">
|
|||
|
<!-- 顶部提示文字 -->
|
|||
|
<template #top>
|
|||
|
<navigation-Bar :isReconnecting="isReconnecting" :user="user" @initNav="initNav" @more="navMore">
|
|||
|
<template #card-swiper>
|
|||
|
<card-swiper v-if="navSwiper"></card-swiper>
|
|||
|
</template>
|
|||
|
<template #card-notice>
|
|||
|
<card-notice v-if="navNotice"></card-notice>
|
|||
|
</template>
|
|||
|
</navigation-Bar>
|
|||
|
</template>
|
|||
|
|
|||
|
<!-- style="transform: scaleY(-1)"必须写,否则会导致列表倒置!!! -->
|
|||
|
<!-- 注意不要直接在chat-item组件标签上设置style,因为在微信小程序中是无效的,请包一层view -->
|
|||
|
<template #cell="{item,index}">
|
|||
|
<view style="transform: scaleY(-1)">
|
|||
|
<view-item @playAudio="playAudio" :playId="playId" :messageList="dataList" :messageIndex="index" :item="item"></view-item>
|
|||
|
</view>
|
|||
|
</template>
|
|||
|
|
|||
|
<!-- 底部聊天输入框 -->
|
|||
|
<template #bottom>
|
|||
|
<slot name="chat-bar" :user="user" :keyboardHeight="keyboardHeight"></slot>
|
|||
|
</template>
|
|||
|
|
|||
|
<!-- 查看最新消息 -->
|
|||
|
<template #backToTop>
|
|||
|
<text>有新消息</text>
|
|||
|
</template>
|
|||
|
|
|||
|
</z-paging>
|
|||
|
</view>
|
|||
|
</template>
|
|||
|
|
|||
|
<script>
|
|||
|
import dayjs from 'dayjs';
|
|||
|
import relativeTime from "dayjs/plugin/relativeTime";
|
|||
|
dayjs.extend(relativeTime);
|
|||
|
import NavigationBar from '@/pages/im/components/navigationBar.vue';
|
|||
|
import cardSwiper from '@/pages/im/components/cardSwiper.vue';
|
|||
|
import cardNotice from '@/pages/im/components/cardNotice.vue';
|
|||
|
import ViewItem from '@/pages/im/components/viewItem.vue';
|
|||
|
import ImMessageApi from '@/sheep/api/im/memberMessage';
|
|||
|
import UserApi from '@/sheep/api/member/user';
|
|||
|
export default {
|
|||
|
components: {
|
|||
|
NavigationBar,
|
|||
|
cardSwiper,
|
|||
|
cardNotice,
|
|||
|
ViewItem,
|
|||
|
},
|
|||
|
props: {
|
|||
|
groupId: 0,
|
|||
|
userId: 0,
|
|||
|
isReconnecting: false,
|
|||
|
},
|
|||
|
data() {
|
|||
|
return {
|
|||
|
dataList: [],
|
|||
|
keyboardHeight: -1,
|
|||
|
paddingTop: 0,
|
|||
|
navSwiper: false,
|
|||
|
navSwiperH: 80,
|
|||
|
navNotice: false,
|
|||
|
navNoticeH: 90,
|
|||
|
height: 0,
|
|||
|
|
|||
|
queryParams: {
|
|||
|
pageNo: 1,
|
|||
|
pageSize: 10,
|
|||
|
groupId: 0,
|
|||
|
},
|
|||
|
|
|||
|
showNewMessageTip: false,
|
|||
|
backToTopStyle: {
|
|||
|
'width': '100px',
|
|||
|
'background-color': '#fff',
|
|||
|
'border-radius': '30px',
|
|||
|
'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.1)',
|
|||
|
'display': 'flex',
|
|||
|
'justifyContent': 'center',
|
|||
|
'alignItems': 'center',
|
|||
|
},
|
|||
|
|
|||
|
innerListStyle: {
|
|||
|
borderBottom: '20px solid #fafafa',
|
|||
|
},
|
|||
|
|
|||
|
audioList: [],
|
|||
|
voiceIndex: -1,
|
|||
|
playId: null,
|
|||
|
|
|||
|
user: {},
|
|||
|
}
|
|||
|
},
|
|||
|
created() {
|
|||
|
this.getUserInfo();
|
|||
|
},
|
|||
|
methods: {
|
|||
|
getUserInfo() {
|
|||
|
UserApi.getUserInfoById(this.userId).then(res => {
|
|||
|
this.user = res.data;
|
|||
|
this.user.time = this.showDayTime(this.user.updateTime);
|
|||
|
});
|
|||
|
},
|
|||
|
showDayTime(datetime) {
|
|||
|
if (!datetime) return "";
|
|||
|
return dayjs(datetime).fromNow();
|
|||
|
},
|
|||
|
initNav(e) {
|
|||
|
this.height = e.height;
|
|||
|
this.paddingTop = this.height;
|
|||
|
|
|||
|
if(this.navSwiper){
|
|||
|
this.paddingTop = this.paddingTop + this.navSwiperH;
|
|||
|
}
|
|||
|
|
|||
|
if(this.navNotice){
|
|||
|
this.paddingTop = this.paddingTop + this.navNoticeH;
|
|||
|
}
|
|||
|
},
|
|||
|
navMore() {
|
|||
|
this.navNotice = !this.navNotice;
|
|||
|
|
|||
|
if(this.navNotice){
|
|||
|
this.paddingTop = this.paddingTop + this.navNoticeH;
|
|||
|
}else{
|
|||
|
this.paddingTop = this.paddingTop - this.navNoticeH;
|
|||
|
}
|
|||
|
},
|
|||
|
queryList(pageNo, pageSize) {
|
|||
|
// 组件加载时会自动触发此方法,因此默认页面加载时会自动触发,无需手动调用
|
|||
|
// 这里的pageNo和pageSize会自动计算好,直接传给服务器即可
|
|||
|
// 模拟请求服务器获取分页数据,请替换成自己的网络请求
|
|||
|
this.queryParams.pageNo = pageNo;
|
|||
|
this.queryParams.pageSize = pageSize;
|
|||
|
this.queryParams.groupId = this.groupId;
|
|||
|
|
|||
|
/* this.$refs.paging.complete([
|
|||
|
{
|
|||
|
dd:'2',
|
|||
|
msgType: 'message',
|
|||
|
message: {
|
|||
|
time: false,
|
|||
|
src: 'https://rbtnet.oss-cn-hangzhou.aliyuncs.com/laohu.png',
|
|||
|
position: 'left',
|
|||
|
isRead: true,
|
|||
|
src: '',
|
|||
|
sec: '30',
|
|||
|
msgType: 'voice',
|
|||
|
},
|
|||
|
},
|
|||
|
{
|
|||
|
dd:'2',
|
|||
|
msgType: 'info',
|
|||
|
message: {
|
|||
|
title: '温馨提示',
|
|||
|
content: '请礼貌用语友好沟通,如遇骚扰等不文明行为,可以将对方屏蔽并投诉。',
|
|||
|
msgType: 'card',
|
|||
|
},
|
|||
|
},
|
|||
|
{
|
|||
|
dd:'2',
|
|||
|
msgType: 'message',
|
|||
|
message: {
|
|||
|
time: true,
|
|||
|
timeDesc: '14:52',
|
|||
|
src: 'https://rbtnet.oss-cn-hangzhou.aliyuncs.com/laohu.png',
|
|||
|
position: 'right',
|
|||
|
isRead: true,
|
|||
|
msgType: 'emoji',
|
|||
|
},
|
|||
|
},
|
|||
|
{
|
|||
|
dd:'2',
|
|||
|
msgType: 'message',
|
|||
|
message: {
|
|||
|
time: true,
|
|||
|
timeDesc: '14:52',
|
|||
|
src: 'http://rbtnet.oss-cn-hangzhou.aliyuncs.com/test/20240726/98a24075202b429d9c9722df541621c6.jpg',
|
|||
|
position: 'right',
|
|||
|
isRead: true,
|
|||
|
msgType: 'img',
|
|||
|
},
|
|||
|
},
|
|||
|
{
|
|||
|
dd:'2',
|
|||
|
msgType: 'message',
|
|||
|
message: {
|
|||
|
time: false,
|
|||
|
src: 'https://rbtnet.oss-cn-hangzhou.aliyuncs.com/laohu.png',
|
|||
|
position: 'right',
|
|||
|
isRead: true,
|
|||
|
src: '',
|
|||
|
sec: '130',
|
|||
|
msgType: 'voice',
|
|||
|
},
|
|||
|
},
|
|||
|
{
|
|||
|
dd:'2',
|
|||
|
msgType: 'message',
|
|||
|
message: {
|
|||
|
time: false,
|
|||
|
position: 'right',
|
|||
|
content: '地址:广东省广州市番禺区祈福新村129号',
|
|||
|
msgType: 'map',
|
|||
|
},
|
|||
|
},
|
|||
|
{
|
|||
|
dd:'2',
|
|||
|
msgType: 'message',
|
|||
|
message: {
|
|||
|
time: false,
|
|||
|
position: 'right',
|
|||
|
content: '吃饭了吗',
|
|||
|
msgType: 'text',
|
|||
|
},
|
|||
|
},
|
|||
|
{
|
|||
|
dd:'2',
|
|||
|
msgType: 'info',
|
|||
|
message: {
|
|||
|
content: '长按图片、文字或视频,支持撤回消息哦~',
|
|||
|
msgType: 'text',
|
|||
|
},
|
|||
|
},
|
|||
|
{
|
|||
|
dd:'2',
|
|||
|
msgType: 'message',
|
|||
|
message: {
|
|||
|
time: true,
|
|||
|
timeDesc: '14:52',
|
|||
|
src: 'https://rbtnet.oss-cn-hangzhou.aliyuncs.com/laohu.png',
|
|||
|
position: 'left',
|
|||
|
isRead: true,
|
|||
|
msgType: 'emoji',
|
|||
|
},
|
|||
|
},
|
|||
|
]);
|
|||
|
*/
|
|||
|
|
|||
|
ImMessageApi.getMemberMessagePage(this.queryParams).then(res => {
|
|||
|
// 将请求的结果数组传递给z-paging
|
|||
|
this.$refs.paging.complete(res.data.list);
|
|||
|
}).catch(res => {
|
|||
|
// 如果请求失败写this.$refs.paging.complete(false);
|
|||
|
// 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
|
|||
|
// 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
|
|||
|
this.$refs.paging.complete(false);
|
|||
|
})
|
|||
|
},
|
|||
|
/** 监听滚动到底部事件(因为 scroll 翻转了顶就是底) */
|
|||
|
onScrollToUpper() {
|
|||
|
// 若已是第一页则不做处理
|
|||
|
if (this.queryParams.pageNo === 1) {
|
|||
|
return;
|
|||
|
}
|
|||
|
this.showNewMessageTip = false;
|
|||
|
// 到底重置消息列表
|
|||
|
this.reload();
|
|||
|
},
|
|||
|
// 监听键盘高度改变,请不要直接通过uni.onKeyboardHeightChange监听,否则可能导致z-paging内置的键盘高度改变监听失效(如果不需要切换表情面板则不用写)
|
|||
|
keyboardHeightChange(res) {
|
|||
|
this.keyboardHeight = res.height;
|
|||
|
},
|
|||
|
// 用户尝试隐藏键盘,此时如果表情面板在展示中,应当通知chatInputBar隐藏表情面板(如果不需要切换表情面板则不用写)
|
|||
|
hidedKeyboard() {
|
|||
|
this.$emit('hidedKeyboard');
|
|||
|
},
|
|||
|
reload(message) {
|
|||
|
if(message){
|
|||
|
if(this.queryParams.pageNo > 1){
|
|||
|
this.showNewMessageTip = true;
|
|||
|
}
|
|||
|
if(message.readStatus){
|
|||
|
this.updateReadStatus(message);
|
|||
|
}else{
|
|||
|
this.$refs.paging.addChatRecordData([message]);
|
|||
|
}
|
|||
|
}else{
|
|||
|
this.$refs.paging.reload();
|
|||
|
}
|
|||
|
},
|
|||
|
updateReadStatus(message) {
|
|||
|
this.dataList.forEach(item => {
|
|||
|
if (item.id == message.id) {
|
|||
|
item.readStatus = true;
|
|||
|
}
|
|||
|
});
|
|||
|
},
|
|||
|
/** 滚动到最新消息 */
|
|||
|
onBackToTopClick(event) {
|
|||
|
event(false); // 禁用默认操作
|
|||
|
this.$refs.paging.scrollToBottom();
|
|||
|
},
|
|||
|
playAudio(e) {
|
|||
|
this.playId = e.id;
|
|||
|
this.clearAllAudio();
|
|||
|
var audio = uni.createInnerAudioContext();
|
|||
|
this.audioList.push(audio);
|
|||
|
//语音自然播放结束
|
|||
|
audio.onEnded((res) => {
|
|||
|
this.playId = null;
|
|||
|
});
|
|||
|
|
|||
|
audio.src = e.content;
|
|||
|
audio.play();
|
|||
|
},
|
|||
|
clearAllAudio() {
|
|||
|
this.audioList.forEach(function(e){
|
|||
|
e.destroy();
|
|||
|
})
|
|||
|
},
|
|||
|
}
|
|||
|
}
|
|||
|
</script>
|
|||
|
|
|||
|
<style scoped>
|
|||
|
.content {
|
|||
|
background-color: #fafafa;
|
|||
|
height: calc(100vh);
|
|||
|
}
|
|||
|
</style>
|