这里提到两个方法
方法一:用整个scroll-view的高度减去所有聊天内容的高度,得到的就是滚动条需要下拉的距离,即可使聊天页下拉至最底部。
参考文章:《uni-app 之 聊天室滚到最底部》
方法二:利用uniapp中的scroll-view组件的scroll-into-view属性 具体使用方法请参考官网文档
第一种方法:
- 将聊天框封装成组件,并判断是谁发送的信息
首先要给聊天内容部分封装成一个组件(不封装也可以),将组件中的对方和自己用v-if做一个判断,当接收的用户名(或者id,只要是唯一值就可以)等于自己的用户名就显示自己的聊天框,不是自己的用户名就显示对方的。这里接受过来的用户名是服务器传过来的,信息一般包含:(发信人,接受的人(如果是给本人发的,那么这里的接受人就等于本人的用户名),信息,类型等等)
//聊天组件
<!-- 本人 -->
<view class=" position-relative" style="margin-top: 30rpx;" v-if="userId === item.payload.owner" >
<u-row>
<u-col span="2"></u-col>
<u-col span="10">
<view class="d-flex a-center j-end">
<view class="text-muted font-sm">{{dateFormat2(item.meta.timestamp)}}</view>
</view>
</u-col>
</u-row>
<u-row>
<u-col span="10.5">
<view class="d-flex j-end a-start position-relative top-0" v-if="item.payload.type === 'text' || item.payload.type === 'image'">
<view class="customer px-2 py-1 d-flex j-end a-center position-relative top-0 baseStyle"
style=" right: -10rpx; z-index: 5;" >
<text style="word-break:break-all;" v-if="item.payload.type === 'text'" :selectable="true">{{item.payload.data}}</text>
<u-image v-if="item.payload.type === 'image'" :src="item.payload.data" width="300rpx" mode="widthFix" class="pt-1" @click="previewImage(item)"></u-image>
<!-- <u-image :src="avatar" width="100%" mode="aspectFit"></u-image> -->
</view>
<u-icon name="play-right-fill" color="#98E165" class="position-relative mt-2 top-0"></u-icon>
</view>
<audio v-if="item.payload.type === 'music'" :src="item.payload.data" controls></audio>
<video v-if="item.payload.type === 'video'" :src="item.payload.data" controls></video>
</u-col>
<u-col span="1.5">
<view class="d-flex j-center a-center position-absolute" style="top: 40rpx;">
<u-avatar :src="avatar" size="mini" mode="circle"></u-avatar>
</view>
</u-col>
</u-row>
</view>
<!-- 对方 -->
<view class=" position-relative" style="margin-top: 30rpx;" v-else>
<u-row>
<u-col span="10">
<view class="d-flex a-center">
<!-- <view class="pr-1">{{item.meta.client}}</view> -->
<view class="font-sm text-muted">{{dateFormat2(item.meta.timestamp)}}</view>
</view>
</u-col>
<u-col span="2"></u-col>
</u-row>
<u-row>
<u-col span="1.5">
<view class="d-flex j-center position-absolute " style="top: 40rpx;" >
<u-avatar :src="avatar" size="mini" mode="circle"></u-avatar>
</view>
</u-col>
<u-col span="10">
<view class="d-flex j-start a-start position-relative top-0" v-if="item.payload.type === 'text' || item.payload.type === 'image'">
<u-icon name="play-left-fill" color="#fff" class="mt-2 top-0"></u-icon>
<view class="kefu px-2 py-1 d-flex j-start a-center position-relative top-0 baseStyle"
style="left: -10rpx;">
<text style="word-break:break-all;" v-if="item.payload.type === 'text'" :selectable="true">{{item.payload.data}}</text>
<u-image v-if="item.payload.type === 'image'" :src="item.payload.data" width="300rpx" mode="widthFix" class="pt-1" @click="previewImage(item)"></u-image>
</view>
</view>
<audio v-if="item.payload.type === 'music'" :src="item.payload.data" controls></audio>
<video v-if="item.payload.type === 'video'" :src="item.payload.data" controls></video>
</u-col>
</u-row>
</view>
- 在script的methods中添加一个滚动到底部的函数
在scroll-view上添加一个id或者是类名,再在包裹聊天组件的标签上添加一个类或者id,在script中的methods中添加一个滚动到底部的方法,
//标题导航栏
<view class="navBar-bg top-0" :style="'height:' + navBarHeight + 'px;'">
<view :style="'height:' + statusBarHeight + 'px;'"></view>
<u-row>
<u-col span="2"><u-icon name="arrow-left" class="pl-2" @click="back"></u-icon></u-col>
<u-col span="8">
<view class="d-flex j-center a-center flex-column">
<view class="u-font-19">{{ title }}</view>
<view class="u-font-12">{{ subTitle }}</view>
</view>
</u-col>
<u-col span="2"><view @click="manpower">转人工</view></u-col>
</u-row>
</view>
//内容框
<scroll-view id="scrollview" style=" background-color: #71D5A1;" scroll-y :style="'height:' + contentH + 'px;'" :scroll-top="scrollTop" :scroll-into-view="scrollIntoIndex">
<view id="msglistview" class="pb-3">
<block v-for="(item, index) in simulation" :key="index" :id="'chatItem_'+index"><chat-item :clientId="clientId" :item="item"></chat-item></block>
</view>
</scroll-view>
//底部输入框
<view>
//这里调用的是一个自定义组件,组件中有一个获取底部输入栏的高度,详情见下。
<chat-footer
:KeyboardHeight="KeyboardHeight"
:tagList="tagList"
:isExpression="isExpression"
:isMoreTool="isMoreTool"
:moreList="moreList"
@focusInput="focusInput"
@expressionBtn="expressionBtn"
@moreTool="moreTool"
@sendMsgs="sendMsgs"
@clickCard="clickCard"
@fillInput="fillInput"
></chat-footer>
</view>
这里是底部输入栏组件的全部代码
<template>
<view>
<view class="position-fixed left-0 right-0 py-2 footerview" style="width: 100%; background-color: #A0CFFF;" :style="'bottom:' + KeyboardHeight + 'px;'">
<block v-for="(item, index) in tagList" :key="index">
<u-tag :text="item.text" mode="light" shape="circle" type="info" class="mx-1" @click="fillInput(item)"></u-tag>
</block>
<view class="my-1"><u-divider></u-divider></view>
<view class="d-flex j-center a-center">
<view style="width: 80%;" class="px-1">
<input
type="text"
v-model="inputContext"
placeholder="请输入..."
class="py-1"
@focus="focusInput('text')"
style="background-color: #FFFFFF; border-radius: 10rpx;"
/>
</view>
<view class="d-flex j-center mr-1" @click="expressionBtn"><u-image :src="expression" width="60rpx" height="60rpx"></u-image></view>
<view class="d-flex j-center mr-1" v-if="!inputContext" @click="moreTool"><u-image :src="addition" width="60rpx" height="60rpx"></u-image></view>
<u-button type="success" size="mini" class="mr-1" v-if="inputContext" @click="sendMsgs">发送</u-button>
</view>
<view v-if="isExpression" class="">
<!-- <swiper class="slider" :current="emojiData.length - 1">
<swiper-item v-for="(item, key) in emojiData" :key="key" class="slider-emoji" :class="[key == emojiData.length - 1 ? 'lastbox' : '']">
<text v-for="(emoji, index) in item" :key="index" @click="selemoji(emoji)" class="slider-emoji-icon font-lg">{{ emoji }}</text>
</swiper-item>
</swiper> -->
</view>
<view v-if="isMoreTool">
<u-grid :col="4" :border="false">
<u-grid-item v-for="(item, index) in moreList" :key="index" bg-color="rgba(255,255,255,0)" @click="clickCard(item)">
<view style="background-color: #FFFFFF; border-radius: 40rpx;" class="p-2"><u-image :src="item.icon" width="60rpx" height="60rpx"></u-image></view>
<view>{{ item.text }}</view>
</u-grid-item>
</u-grid>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'chat-footer',
props: {
// classes: Array,
KeyboardHeight:Number,
tagList:Array,
// inputContext:String,
moreList:Array,
gridStyle:Object,
isMoreTool:Boolean,
isExpression:Boolean
},
data() {
return {
// isExpression:false,
// isMoreTool:false,
expression: require('../static/expression.png'),
addition: require('../static/addition.png'),
inputContext:''
};
},
updated() {
// this.getFooterHeight();
},
methods:{
focusInput(type,) {
this.$emit('focusInput',type,)
},
expressionBtn() {
this.$emit('expressionBtn')
},
moreTool() {
this.$emit('moreTool')
},
// 键盘enter回车发送
submit() {
this.sendMsgs()
},
sendMsgs() {
const text = this.inputContext
this.$emit('sendMsgs', text)
this.inputContext = ''
},
clickCard(item) {
// console.log(this.inde);
this.$emit('clickCard',item)
},
fillInput(item) {
this.inputContext = item.text
const type = 'text'
this.$emit('fillInput', type)
this.sendMsgs()
}
}
};
</script>
<style></style>
这个组件输入栏的样式是
//设置页面滚动到底部
scrollToBottom() {
let that = this
let query = uni.createSelectorQuery()
query.select('#scrollview').boundingClientRect()
query.select('#msglistview').boundingClientRect()
query.exec((res) => {
// console.log(res)
//获取所有内部子元素的高度
//判断子元素高度是否大于显示高度
if(res[1].height > res[0].height){
// 用子元素的高度减去显示的高度就获益获得滚动的高度
that.scrollTop = res[1].height - res[0].height
}
})
},
// 获取底部菜单栏的高度
getFooterHeight() {
let that = this;
let query = uni.createSelectorQuery();
query.select('.footerview').boundingClientRect();
query.exec(function(res) {
if (res[0]) {
that.footViewHeight = res[0].height;
}
console.log(that.footViewHeight);
});
this.scrollToBottom();
// console.log(this.footViewHeight);
}
- 在updated中调用scrollToBottom()方法,每次发生改变时都会调用滚动到底部的方法
updated() {
//调用滚动到底部的方法
this.scrollToBottom();
this.getFooterHeight();
},
完成!!!
第二种方法
将聊天框封装成组件,和方法一中一样。这里就不在赘述。
样式布局除了scroll-view聊天框部分。其他顶部和底部输入栏都和方法一 一样。这里主要写聊天框部分,如下
//在scroll-view上添加scroll-into-view的属性,在循环聊天组件的id设置为'chatItem_' + index 这里style绑定chatBodyBottom来定位内容框的位置
<scroll-view scroll-y="true" class="main-scroll" :style="chatBodyBottom" :scroll-into-view="scrollIntoIndex" :scroll-with-animation="true" @click="showChat">
<view v-for="(item, index) in chatData" :key="index" :id="'chatItem_' + index">
<chat-item :userId="clientInfoData.userId" :item="item" ref="chatItem" @previewImage="previewImage"></chat-item>
</view>
</scroll-view>
是聊天页滚动到最底部的方法是:
// 跳转页面底部
scrollToBottom() {
this.$nextTick(() => {
//这里将 最后一条聊天的下标'chatItem_' + index 赋值给 刚刚绑定的scrollIntoIndex。这样就可以实现滚动到底部
let index = this.chatData.length - 1;
this.scrollIntoIndex = 'chatItem_' + index;
});
},
其他和方法一中的一样
这里提到怎么适配 有顶部标题栏和底部输入栏
在computed计算属性中添加
computed: {
//设置聊天内容框距顶部和底部的距离。以此来了定位内容框的位置
chatBodyBottom() {
return `bottom:${this.footViewHeight + this.KeyboardHeight}px;top:${this.navBarHeight}px;`;
}
},
在data中都有定义KeyboardHeight和navBarHeight。分别是键盘高度和顶部标题栏高度
结束最后,我要说几点,以上布局样式中有很多方法以及数据,在文章的methods和data中没有体现。没用过uniapp的可能难理解,见谅。