uniapp微信小程序仿聊天 输入框在软键盘隐藏显示时与底部距离,以及保证聊天消息滚动到底部 的问题以及解决办法

 (本文最后边有完整代码,想先看效果的,可以直接粘贴不会报错)

一、先说输入框

1、输入框和键盘紧贴着,也就是计算 输入框在键盘隐藏显示时 距离底部的距离,我让输入框外边的盒子固定定位,给bottom一个变量,给盒子绑定行间  :style="{ bottom: bottom }",简易代码如下,主要是 css 代码和 js 代码

<view class="intelligent_input" :style="{ bottom: bottom }">
		// <input placeholder="请输入…" :adjust-position="false" v-model="value"></input>  输入框下边有详细介绍的代码,这里输入框的内容先省略,主要看外边盒子的代码
		// <view> <text>发送</text> </view>  简易模拟,在本篇文章下边有输入框以及template的详细代码,主要看外边盒子代码以及布局格式
</view>


js代码

onLoad() {
	uni.onKeyboardHeightChange((res) => { //监听键盘高度变化
	  const res_keyboard = uni.getSystemInfoSync();
	  let key_height = res.height - (res_keyboard.screenHeight - res_keyboard.windowHeight + res_keyboard.safeAreaInsets.bottom)
	  this.bottom = `${ key_height ? key_height : 0}px`;
	})
},
onHide() {
	uni.offKeyboardHeightChange(); //取消监听键盘高度变化事件,避免内存消耗
},



css代码

.intelligent_input {
	width: 100%;
	height: 100rpx;
	min-height: 98rpx;
	background: #F7F7F7;
	position: fixed;
	bottom: 0;        // 这里可写可不写,上边盒子上的变量给的初始值是0px,因为等会计算的高度是px,所以就给的px单位
	padding-bottom: constant(safe-area-inset-bottom);  // 苹果底部的安全距离
	padding-bottom: env(safe-area-inset-bottom);       // 苹果底部的安全距离
	display: flex;
    box-sizing: content-box;
}

 总结 上边表达的 核心代码

1、// 这个加在输入框外边包的元素上   :style="{ bottom: bottom }",bottom的初始值给 ‘0px’

:style="{ bottom: bottom }"

2、 js 的核心代码

onLoad() {
    uni.onKeyboardHeightChange((res) => { //监听键盘高度变化
            const res_keyboard = uni.getSystemInfoSync();

            // res_keyboard.safeAreaInsets.bottom 底部的安全距离,是个负数

            // 这个key_height 是我计算出的底部高度,你自己的可以看着计算,只要凑上就可以,我这也是凑上的,具体每一项我也不是很懂
            let key_height = res.height - (res_keyboard.screenHeight - res_keyboard.windowHeight + res_keyboard.safeAreaInsets.bottom)

             // px是因为每个数字本来就是px单位,不用担心跟rpx相违背之类的
             this.bottom = `${ key_height ? key_height : 0}px`; 
    })

},
onHide() {
    uni.offKeyboardHeightChange();  // 取消监听键盘高度变化事件,避免内存消耗
},

3、 css的核心代码

 position: fixed;
bottom: 0;

// 下边这两行:设置苹果手机底部安全距离

padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);

这里写完输入框和键盘已经保证距离差不多了

2、如果你想像微信一样,保证点击发送之后,软键盘不收起,这三个有用

// 现在输入框写一个聚焦的变量fousefouse初始值给false,在聚焦事件里赋值 true

:focus="fouse" 

// 绑定聚焦事件 @focus="i_focus",

 @focus="i_focus" 

// 这个加在输入框上边,hold_keyboard 初始值给false,在聚焦事件里赋值 true

:hold-keyboard="hold_keyboard" 

// 这个把外边发送按钮按钮的@click事件换@touchend.prevent="input_send"

 @touchend.prevent="input_send"     

3、如果你的软键盘右下角和我的一样是发送功能,保证点击右下角键盘之后,软键盘不收起,这两个有用(跟上边类似,所以我绑定的都是同一个变量)

// 这个加在输入框上边,hold_keyboard 初始值给false,在聚焦事件里赋值 true

:confirm-hold="hold_keyboard" 

// 同理,把input上的这个事件这样写 @confirm.prevent="input_send"

 @confirm.prevent="input_send"     

4、这样写完之后你会发现,点击软键盘以外的地方,键盘也不收起了,所以我给scroll-view标签加了两个事件,点击事件和滑动事件,绑定同一个方法,在方法里隐藏键盘这行码 

 uni.hideKeyboard()

// 点击事件 @click

@click="other_click"

// 滑动事件   @dragging

 @dragging="other_click"

5、如果你想像微信一样,保证点击发送之后,软键盘不收起,这两个有用

// 这个加在输入框上边

:hold-keyboard="hold_keyboard" 

// 这个把外边发送按钮按钮的@click事件换@touchend.prevent="input_send"

 @touchend.prevent="input_send"     

下边是上边的完整代码

template部分:

<view class="intelligent_input" :style="{ bottom: bottom }">//bottom输入框距底部的距离,计算出的
	<input 
        type="text" 
        confirm-type="发送" // 软键盘右下角改文字,配合 type="text" 才有效
        placeholder-style="font-size: 30rpx;color: #C8C8C8;"  // 输入框placeholder样式
        placeholder="请输入…"   
        :adjust-position="true " // true 禁止键盘显示时上推内容,默认是true
        v-model="value"  
        @confirm.prevent="input_send"  // 软键盘右下角事件,这里我绑定的和发送一样的功能
        :focus="fouse"  // 控制输入框聚焦,布尔
        :hold-keyboard="hold_keyboard"  // 保证点击发送之后,键盘不收起,布尔
        :confirm-hold="hold_keyboard"   // 保证点击软键盘右下角发送之后,键盘不收起,布尔
        @focus="i_focus" // 输入框聚焦事件
        @blur="blur"  // 输入框失焦事件
></input>
	<view class="input_send" 
        @touchend.prevent="input_send" //这里说明一下,一定要用@touchend.prevent  这个事件,要不键盘会收起,上面尽管写了也不起效
    >
		<image src="https://yiben01.oss-cn- hangzhou.aliyuncs.com/img/921c55e21e95bf68a5d32f4aeab37bc64d2317d0_100.png" mode=""> </image>
	 </view>
 </view>

script部分

data里输入框用到的变量:{
    value: '',  // 输入框value
	bottom: '0px',  // 输入框距底部高度(键盘显示隐藏高度变化)
	fouse: false,  // 输入框聚焦
	hold_keyboard: true,  // 键盘不收起,
    msg_list: [
        {
			left: true,
			msg: '嗨~我是小伊,您身边的小助手~',
			data: null
		},
        {
			left: true,
			msg: '请问有什么可以帮助您的?',
			data: null
		}
    ],
}

onShow,onHide的键盘监听和取消监听我就不重复写了,上边就有,css最主要代码也写了,这里就不写了

method:{
    // 点击键盘以外的地方收起键盘 在 scroll-view 标签绑定的事件
	other_click() {
		uni.hideKeyboard()
	},
    // 输入框聚焦事件
	i_focus() {
		this.fouse = true;
		this.hold_keyboard = true;
	},
    // 输入框失焦事件
    blur() {
		// 换id
		// this.controlId = false   //等会scroll-view要用到  这里先注释掉
	},
    // input发送事件
	async input_send() {
        if (!this.value.toString()) {
			return
		}
        // 增加数组,将自己的话放进去,再将对方的回答放进去,这里模拟两个人对话,等会在scroll-view 里  v-for 出来  left代表左右对话,msg代表话,data是我的项目有网络强求,返回的数个数组,所以,数组没有的时候就置空,有就赋值
		this.msg_list = [
            ...this.msg_list, 
            {
				left: false,
				msg: this.value,
				data: null
		    },
            {
				left: true,
				msg: '没有找到该问题答案',
				data: null
		    }
        ]

    }
}

二、接下里说容器scroll-view的一些问题,以及保证聊天输入时一直滚动到底部的问题

如果你用的view标签,就把view标签换成scroll-view标签,如果你样式已经写好了,换了之后也不影响(原因:scroll-view标签有它自己的好处哈哈,其实是用到scroll-into-view这个属性很关键)

  先说一下保证滚动到底部的简单代码

<scroll-view :scroll-y="true" style="height: 600rpx;" 
    :scroll-into-view="bottomId" :scroll-with-animation="true">
    <view v-for="(item,index) in msg_list">
        <view>{{item.msg}}</view>
    </view>
    <view :id="'dade'+num">底部</view>
</scroll-view>

setTimeout(()=>{
    this.num = this.msg_list.length
    this.bottomId = 'dade'+ (this.num)
},500)

光写上边的代码,会出现一些问题,所以我在下图中才有了 controlId 来判断,具体可以往下看

下面可以先看着图片


说说上边图片里的变量

// 容器高度,设置成变量是因为这个是后期计算出来的
key_height: '0px', // 初始值

// 容器滚动底部定位id,有了这个id就可以保证聊天滚动到底部
bottomId: '',

// 定位id,设置这个是因为,我发现在键盘弹起隐藏的时候聊天可以保证滚动到最后面,但是只要我在页面上滑动,再在输入框输入的时候就不能保证聊天滚动到最后了,所以我替换了id,等输入框聚焦的时候就换成上边的bottomId,等输入框失去焦点的时候就换成这个别的id,所以在输入框的聚焦和失焦时间里控制一下这个变量的布尔,聚焦为true,失焦为false
controlId: false, // 用来控制切换id
 // 数组长度,用到定位id
 num: 2, 

 // 防连点
boolen: true,

在created或者onLoad生命周期里,先给容器一个高度,也就是变量 key_height 给一个值

const res_s = uni.getSystemInfoSync();
this.key_height = `${res_s.windowHeight - 52}px`  // 52 是我输入框的高度,单位记着是px,如果你输入框时104rpx,那就是52px。(注:输入框的高度是可以获取元素高度来计算的,我这里简写了,具体可以看本篇文章最后边的完整代码里)

res_s.windowHeight是屏幕的可用总高度,就是除了NavigationBar的高度,所以拿上屏幕的可用总高度,减去输入框的高度,就是盒子的高度,因为刚开始输入框的软按键盘还没弹起,所以不用减键盘的高度

然后在监听键盘高度变化的函数里写上这三行代码

this.key_height = `${res_keyboard.windowHeight - 52 - key_height}px`;   // key_height是上边输入框距离底部的高度(也就是软键盘的高度),在文章开头的时候中有
this.num = this.msg_list.length; //数组的length,实时更新length,保证scroll-into-view找到最后一个id
this.bottomId = 'dade' + (this.num);  //最后的id   scroll-into-view就是找对应的id,定位到找到的当前id,就保证了聊天滚动到最后

下边是整个页面的完整代码

直接粘贴不会报错

<template>
	<view class="pages_height">
		<view class="intelligent_main">
			<scroll-view :scroll-y="true" :style="{height:key_height}"
				:scroll-into-view="controlId ? bottomId : 'msg'+controlId" :scroll-with-animation="true"
				class="intelligent_chat" @click="other_click" @dragging="other_click" :show-scrollbar="false">
				<view v-for="(item, index) in msg_list" :key="index" :class="'intelligent_chat_view'+index">
					<view :class="item.left ? 'robot_word' : 'patient_word'">
						<view v-if="item.msg">
							<view>
								{{item.msg}}
							</view>
						</view>
						<view v-else>
							<view>
								{{item.title}}
							</view>
							<view v-if="item.data">
								<view v-for="(data, data_index) in item.data" :key="data.id">
									<view class="keyHighlight" v-html="data.keyHighlight" v-if="data.keyHighlight"
										@click="keyHighlight(data,data_index)"></view>
								</view>
							</view>
 
						</view>
					</view>
				</view>
				<view :id="'dade'+num" v-if="controlId"></view>
				<view :id="'msg'+controlId" v-else></view>
			</scroll-view>
 
 
			<view class="intelligent_input" :style="{ bottom: bottom }">
				<input type="text" placeholder-style="font-size: 30rpx;color: #C8C8C8;" placeholder="请输入…"
					:adjust-position="false" v-model="value" @confirm.prevent="input_send" :focus="fouse"
					:hold-keyboard="hold_keyboard" :confirm-hold="hold_keyboard" @focus="i_focus" @blur="blur"
					confirm-type="发送"></input>
				<view class="input_send" @touchend.prevent="input_send">
					<image
						src="https://yiben01.oss-cn-hangzhou.aliyuncs.com/img/921c55e21e95bf68a5d32f4aeab37bc64d2317d0_100.png"
						mode=""></image>
				</view>
			</view>
		</view>
	</view>
</template>
 
<script>
	export default {
		data() {
			return {
				// 输入框value
				value: '',
				// 输入框距底部高度(键盘显示隐藏高度变化)
				bottom: '0px',
				// 对话框数组
				msg_list: [{
					left: true,
					msg: '嗨~我是小助,您身边的智能助手~',
					data: null
				}, {
					left: true,
					msg: '请问有什么可以帮助您的?',
					data: null
				}],
				// 容器高度
				key_height: '0px',
				// 容器滚动底部定位id
				bottomId: '',
				// 数组长度,用到定位id
				num: 2,
				// 输入框聚焦
				fouse: false,
				// 键盘不收起
				hold_keyboard: true,
				// 防抖
				boolen: true,
				// 定位id
				controlId: false,
				inputHeight: 0,
			}
		},
		onLoad() {
		},
		watch: {
			inputHeight(n, o) {
				this.inputHeight = n
				const res_s = uni.getSystemInfoSync();
				this.key_height =
					`${res_s.windowHeight - n}px`;
			},
		},
		onShow() {
			this.keyboardHeightChange()
			this.compute_input_height()
			const res_s = uni.getSystemInfoSync();
			this.key_height =
				`${res_s.windowHeight - this.inputHeight}px`;
		},
		onHide() {
		},
		onUnload() {
			uni.offKeyboardHeightChange(); //取消监听键盘高度变化事件,避免内存消耗
		},
		methods: {
			compute_input_height() {
				const query = uni.createSelectorQuery().in(this); //这样写就只会选择本页面组件的类名box的
				query.selectAll('.intelligent_input').boundingClientRect(
					data => {
						console.log(data[0].height);
						this.inputHeight = data[0].height
					}).exec();
			},
			// 点击键盘以外的地方收起键盘
			other_click() {
				uni.hideKeyboard()
			},
			// 监听键盘高度事件
			keyboardHeightChange() {
				uni.onKeyboardHeightChange((res) => { //监听键盘高度变化
					const res_keyboard = uni.getSystemInfoSync();
					let key_height = res.height - (res_keyboard.screenHeight - res_keyboard.windowHeight +
						res_keyboard.safeAreaInsets.bottom)
					this.bottom = `${res.height ?  key_height : 0 }px`;
					
					this.compute_input_height()
					
					if (res.height) {
						this.key_height =
							`${res_keyboard.windowHeight - this.inputHeight - key_height }px`;
					} else {
						this.key_height =
							`${res_keyboard.windowHeight - this.inputHeight}px`;
					}
					
					console.log(this.key_height);
					this.num = this.msg_list.length;
					this.bottomId = 'dade' + (this.num);
				})
			},
			// 点击回答的高亮,跳转页面
			keyHighlight(data, data_index) {
				// uni.navigateTo({
				// 	url: `./problems?data=${encodeURIComponent(JSON.stringify(data))}`
				// })
			},
			// 输入框聚焦事件
			i_focus() {
				this.fouse = true;
				this.hold_keyboard = true;
				// 换id
				this.controlId = true
			},
			blur() {
				// 换id
				this.controlId = false
			},
			// input发送事件
			async input_send() {
				if (!this.value.toString()) {
					return
				}
				let timer
				if (timer) clearTimeout(timer)
				if (this.boolen) {
					this.boolen = false
 
					// 增加回答,自己的话
					this.msg_list = [...this.msg_list, {
						left: false,
						msg: this.value,
						data: null
					}]
					// 如果你们没有网络请求,----------------
					if (this.value.match(/你好|您好/)) {
						this.msg_list = [...this.msg_list, {
							left: true,
							msg: '您好,我是小助,您有什么问题可以直接问我哦~',
							data: null
						}]
					} else {
						this.msg_list = [...this.msg_list, {
							left: true,
							msg: '没有找到该问题答案,请换种问法试一试。',
							data: null
						}]
					}
					this.value = '';
					this.num = this.msg_list.length;
					this.bottomId = 'dade' + (this.num);
 
					timer = setTimeout(() => {
						this.boolen = true
					}, 300)
					// 如果你们有网络请求,把下边的注开----------------------------------------------
 
 
					// // 注释掉的是网络请求,如果直接粘贴会报错,所以我就注掉了这就不会报错了
					// let res = await this.$api.disease_keyword({
					// 	keyword: this.value
					// })
					// console.log(res);
					// if (res.code == 200) {
					// 	if (Array.isArray(res.data) && res.data.length > 0) {
					// 		// 替换高亮,增加样式
					// 		res.data.forEach((v, i) => {
					// 			if (v.keyHighlight && v.keyHighlight.match(/em/)) {
					// 				v.keyHighlight = v.keyHighlight.replace(/<em>/,
					// 					'<strong style="color: #F07828;">')
					// 				v.keyHighlight = v.keyHighlight.replace(/<\/em>/, '</strong>')
					// 			}
					// 		})
					// 		// 增加回答,机器人回答的话
					// 		this.msg_list = [...this.msg_list, {
					// 			left: true,
					// 			title: `以下是关于“${this.value}”的解答:`,
					// 			data: res.data
					// 		}]
					// 	} else {
					// 		// 提示,没有找到该问题答案
					// 		if (this.value.match(/你好|您好/)) {
					// 			this.msg_list = [...this.msg_list, {
					// 				left: true,
					// 				msg: '您好,我是小助,您有什么问题可以直接问我哦~',
					// 				data: null
					// 			}]
					// 		} else {
					// 			this.msg_list = [...this.msg_list, {
					// 				left: true,
					// 				msg: '没有找到该问题答案,请换种问法试一试。',
					// 				data: null
					// 			}]
					// 		}
					// 	}
					// 	this.value = '';
					// 	this.num = this.msg_list.length;
					// 	this.bottomId = 'dade' + (this.num);
					// } else {
					// 	// 出什么问题,toast出来
					// }
					// timer = setTimeout(() => {
					// 	this.boolen = true
					// }, 300)
 
				}
			}
		}
	}
</script>
 
<style scoped>
	.pages_height {
		width: 100%;
		color: #191919;
		background: #F0F0F0;
		font-family: PingFangSC-Regular, PingFang SC;
	}
 
	.intelligent_main {
		height: 100vh;
	}
 
	.intelligent_chat {
		height: 90vh;
		padding: 0 30rpx;
		box-sizing: border-box;
	}

     /* 取消滚动条 */
	.intelligent_chat ::-webkit-scrollbar {
		width: 0;
		height: 0;
		color: transparent;
		display: none;
	}
 
	/* 最上边两句话 */
	.intelligent_chat_view0 {
		padding-top: 30rpx;
	}
 
	.intelligent_chat>view {
		width: 100%;
		background-color: red;
	}
 
	.intelligent_chat>view>view {
		width: 100%;
	}
 
	.robot_word {
		color: #191919;
		display: flex;
		flex-direction: column;
		align-items: flex-start;
	}
 
	.patient_word {
		color: #fff;
		display: flex;
		flex-direction: column;
		align-items: flex-end;
	}
 
	.patient_word>view,
	.robot_word>view {
		max-width: 600rpx;
		margin-bottom: 20rpx;
		line-height: 42rpx;
		font-size: 30rpx;
		padding: 15rpx 20rpx;
		box-sizing: border-box;
		white-space: pre-wrap;
	}
 
	/* 患者问题 */
	.patient_word>view {
		background: #F07828;
		border-radius: 15rpx 0 15rpx 15rpx;
	}
 
	/* 机器人回答 */
	.robot_word>view {
		background: #FFFFFF;
		border-radius: 0 15rpx 15rpx 15rpx;
	}
 
	.keyHighlight {
		font-size: 30rpx;
		font-family: PingFangSC-Medium, PingFang SC;
		line-height: 42rpx;
		margin-top: 10rpx;
	}
 
 
	/* 输入框 */
	.intelligent_input {
		width: 100%;
		height: 100rpx;
		min-height: 98rpx;
		background: #F7F7F7;
		position: fixed;
		bottom: 0px;
		display: flex;
		box-sizing: content-box;
		padding-bottom: constant(safe-area-inset-bottom);
		padding-bottom: env(safe-area-inset-bottom);
	}
 
	.intelligent_input>input {
		width: 80%;
		background: #FFF;
		margin: 15rpx 30rpx;
		padding: 13rpx 20rpx;
		box-sizing: border-box;
		height: 70%;
		min-height: 68rpx;
		border-radius: 10rpx;
	}
 
	.intelligent_input>input::placeholder {
		font-size: 30rpx;
		color: #C8C8C8;
	}
 
	.input_send {
		margin-right: 20rpx;
		display: flex;
		justify-content: center;
		flex-direction: column;
		align-items: flex-end;
	}
 
	.input_send>image {
		width: 42rpx;
		height: 42rpx;
		padding: 10rpx;
	}
</style>

  • 14
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小坚果_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值