评论组件--uniapp textarea focus,string.replace

最近使用uniapp做了个h5的项目,嵌入App使用。中间有个评论组件,把人整个够呛,前后改了好几遍。第一次开发,照着文档找组件,好不容易写了个差不错,结果发现在手机App里表现非常不理想,表现为textarea无法自动focus,input组件也是如此。周六加班终于搞了个差不错。

问题1、textarea自动focus???

本来的方案是,在底部做个假的输入框,click时,弹出textarea所在popup,结果popup弹出来了,textarea没自动focus

分析:textarea自带的focus不好用

解决:舍弃假的输入框,直接使用textarea所在的view,开始在底部定位,textarea的 focus事件里,将view的位置进行修改。

问题2、评论取消后,textarea又无法聚焦了,即使focus使用了一个变量来控制

分析:或许focus绑定的值需要变化,才能引起textarea是否能focus

解决:取消评论后,及时将变量的值改为false,下次需要focus时,将变量值改为true

问题3、评论框内需显示@xxx:,给后端发送请求时还不能带着

分析: 评论时带着@xxx:比较好实现,发送时截取使用string.replace

解决:string.replace(/@[0-9a-zA-Z\u4e00-\u9fa5]+:/, '')

/@[0-9a-zA-Z\u4e00-\u9fa5]+:/,这个正则就是包括了 英文字母a-zA-Z数字0-9以及中文

最坑的地方是 string.replace方法不在原来的string修改,而是返回了一个新的字符串,需要接收!!!!

问题4、嵌入的App也没有做统一的处理,结果安卓手机键盘会盖在页面最上面,苹果手机则会将页面进行上推,我上面定义的view位置是bottom:50%,苹果手机给我推没了,5555

解决:根据/iphone|ipad|ipod|ios/.test(window.navigator.userAgent.toLowerCase()),判断出是苹果手机后,定位bottom:0。

附上代码,方便以后查阅。

vue3版的uniapp

<template>
	<view class="comment-wrap">
		<view class="title">评论</view>
		<view class="comment-list" v-if="list.length">
			<view v-for="item in list" :key="`comment-${item.id}`">
				<Item :item="item" @reply="onReply" />
				<view class="second-list" v-if="item.comment.length>0">
					<Item avatar-size="22" v-for="jtem in item.comment.slice(0, 2)" :item="jtem"
						:key="`reply-${jtem.id}`" @reply="onReply" />
					<view class="check-more" v-if="item.comment.length>2" @click="openMoreComment(item)">
						查看全部{{item.comment.length}}条精彩评论&nbsp;&nbsp;></view>
				</view>
			</view>
			<view class="more-comment-tip" @click="getMoreComment">{{more?'查看更多':'已经到底了'}}</view>
		</view>
		<view class="nodata" v-show="showCommentResult&&list.length===0">
			<image src="../../static/noContent.png" mode=""></image>
			<text>暂无评论</text>
		</view>
	</view>
	<!-- 显示一级评论的全部回复 -->
	<u-popup :show="showMoreCommentPop" :closeable="true" mode="bottom" round="32" @close="closeMoreCommentPop"
		class="comment-input-popup">
		<view class="popup-title-custom">显示全部({{moreComment?.comment?.length}})</view>
		<view class="main-comment">
			<Item :item="moreComment" @reply="e=>onReply(e, 'first')" />
		</view>
		<view class="popup-subtitle">全部回复</view>
		<view class="comment-comment" v-show="moreComment?.comment?.length">
			<Item :item="jtem" v-for="jtem in moreComment.comment" @reply="onReply" />
			<view class="more-comment-tip">已经到底了</view>
		</view>
	</u-popup>

	<view v-show="showInputPop" class="c-mask" @click="closeInputPop" @touchmove.stop.prevent="disableScroll"></view>
	<view class="iinput-view" :style="{bottom: showInputPop?isIphone?'0%':'50%':'0%'}" @touchmove.stop.prevent="disableScroll">
		<view class="c-popup-content">
			<view class="comment-input-wrap">
				<textarea v-model="comment" class="comment-textarea" :maxlength="250" :focus="replyFocus"
					placeholder="欢迎发表你的观点" confirm-type="send" auto-height @confirm="commentCommit"
					@focus="bindFocus" />
				<uni-icons v-if="hasComment" type="paperplane" size="30" color="#286CFB"
					@click="commentCommit"></uni-icons>

				<view class="cancel-text" v-if="showInputPop&&!hasComment" @click="closeInputPop">取消
				</view>
			</view>
			<!-- <view class="reply-block" v-if="replyContent">{{replyContent}}</view> -->
		</view>
	</view>
</template>

<script setup>
	import {
		ref,
		getCurrentInstance,
		computed
	} from 'vue'
	import Item from './Item.vue'
	import {
		getCommentList,
		commentCreate
	} from '../../api/index.js'
	// 当前实例
	const {
		proxy
	} = getCurrentInstance()

	const props = defineProps({
		articleId: {
			type: [Number, String],
		}
	})


	// 新建评论
	const comment = ref('')
	// 评论列表
	const list = ref([])
	// 新建评论弹窗
	const showInputPop = ref(false)
	// @回复内容显示
	const replyContent = ref('')
	
	// 回复目标ID
	const targetId = ref('')
	// 更多评论弹窗
	const showMoreCommentPop = ref(false)
	// 	一级评论展开的内容
	const moreComment = ref({})

	// 是否显示评论获取的列表结果
	const showCommentResult = ref(false)

	// 点击回复控制textarea的聚焦
	const replyFocus = ref(false)
	// 获取当前设备,如果是Safari,弹框将留在底部
	const isIphone = ref('')
	isIphone.value = /iphone|ipad|ipod|ios/.test(window.navigator.userAgent.toLowerCase())
	// 请求列表需要的page和pagesize
	let page = 1
	let pagesize = 10
	// 是否还有更多评论
	const more = ref(true)
	const hasComment = computed(()=>{
		return comment.value.replace(/@[0-9a-zA-Z\u4e00-\u9fa5]+:/, '').trim().length>0
	})
	const requestList = async () => {
		const params = {
			article_id: props.articleId,
			target: 0,
			page,
			pagesize
		}
		const {
			data
		} = await getCommentList(params)
		if (data) {
			// 判断是否需要显示还有更多
			more.value = Math.ceil(data.total / pagesize) > page

			data.list.forEach(item => {
				item.comment = itFn([], item.comment, '')
				item.comment.length && item.comment.sort((f, s) => (+new Date(s.created_at) -
					+new Date(f.created_at)))
			})
			if (params.page === 1) {
				// 显示有无结果
				showCommentResult.value = true
				list.value = []
			}
			list.value = list.value.concat(data.list)

			if (showMoreCommentPop.value) {
				updateMoreCommentData()
			}
		}
	}

	// 一级评论更多里面回复后刷新数据
	const updateMoreCommentData = () => {
		moreComment.value = list.value.find(item => item.id === moreComment.value.id)
	}

	// 	递归得到所有1级评论的回复
	function itFn(target, comment, text) {
		if (comment.length > 0) {
			for (let i = 0; i < comment.length; i++) {
				let item = comment[i]
				if (text) {
					item.replyContent = text
				}
				target.push(item)
				if (item?.comment?.length > 0) {
					let str = `@${item.username}:${item.content}`
					itFn(target, item.comment, str)
				}
			}
		}
		return target
	}

	requestList()
	// 提交评论
	const commentCommit = async () => {
		let commentStr = comment.value
		commentStr = commentStr.replace(/@[_-0-9a-zA-Z\u4e00-\u9fa5]+:/, '')
		commentStr = commentStr.trim()
		if (commentStr) {
			const {
				data
			} = await commentCreate({
				aid: props.articleId,
				content: commentStr,
				target: targetId.value ? targetId.value : 0
			})
			if (data) {
				uni.showToast({
					title: '评论成功',
					icon: 'none'
				})
				comment.value = ''
				closeInputPop()
				requestList()
			}
		} else {
			uni.showToast({
				title: '内容不能为空哦',
				icon: 'none'
			})
		}
	}

	const openInputPop = () => {
		showInputPop.value = true
	}

	// 组件内回复, type==='first'时是一级直接评论,不用增加@内容
	const onReply = (e, type) => {
		// 组件回复
		if (e) {
			comment.value = type === 'first' ? '' : e.prefixContent
			targetId.value = e.targetId
		}
		openInputPop()

		replyFocus.value = true
	}

	const openMoreComment = (data) => {
		showMoreCommentPop.value = true
		moreComment.value = data
	}

	const closeMoreCommentPop = () => {
		showMoreCommentPop.value = false
		moreComment.value = {}
	}

	const closeInputPop = () => {
		showInputPop.value = false
		replyFocus.value = false
		comment.value = ''
		targetId.value = ''
	}

	const getMoreComment = () => {
		if (more.value) {
			page++
			requestList()
		}
	}

	const disableScroll = () => {
		return
	}
	const bindFocus = () => {
		showInputPop.value = true
		// 1级弹框底部直接触发的
		if (showMoreCommentPop.value) {
			targetId.value = targetId.value ? targetId.value : moreComment.value.id
		}
	}
</script>

<style lang="scss" scoped>
	.comment-wrap {
		padding: 40rpx 32rpx;
		width: 100%;
		height: auto;
		padding-bottom: calc(50rpx + constant(safe-area-inset-bottom));
		padding-bottom: calc(50rpx + env(safe-area-inset-bottom));
		.comment-list{
			padding-bottom: 10vh;
		}
	}

	.title {
		margin-bottom: 40rpx;
		width: 100%;
		height: 48rpx;
		font-size: 34rpx;
		font-family: PingFangSC-Medium, PingFang SC;
		font-weight: 600;
		color: #1F2227;
		line-height: 48rpx;
	}

	.second-list {
		padding-left: 80rpx;
	}

	.more-comment-tip {
		height: 104rpx;
		font-size: 24rpx;
		font-family: PingFangSC-Regular, PingFang SC;
		font-weight: 400;
		color: #8F959F;
		line-height: 94rpx;
		text-align: center;
	}

	.comment-input-popup ::v-deep .u-popup__content,
	.c-popup-content {
		padding: 38rpx 28rpx;
		width: 100%;
		background-color: #fff;
		padding-bottom: calc(50rpx + constant(safe-area-inset-bottom));
		padding-bottom: calc(50rpx + env(safe-area-inset-bottom));
	}

	.comment-input-wrap {
		width: 100%;
		display: flex;
		flex-direction: row;
		justify-content: space-between;
		align-items: center;

		.cancel-text {
			margin-left: 8rpx;
			width: 60rpx;
			font-size: 28rpx;
			color: #286CFB;
			flex-shrink: 0;
		}

		.comment-textarea {
			flex-grow: 1;
		}

	}

	.reply-block {
		padding: 8rpx 16rpx;
		margin: 16rpx 0;
		font-size: 24rpx;
		font-family: PingFangSC-Regular, PingFang SC;
		font-weight: 400;
		color: #8F959F;
		line-height: 34rpx;
		background: #F5F6FB;
		border-radius: 8rpx;
	}

	.check-more {
		margin-left: 56rpx;
		margin-bottom: 32rpx;
		height: 34rpx;
		font-size: 24rpx;
		font-family: PingFangSC-Regular, PingFang SC;
		font-weight: 400;
		color: #999999;
		line-height: 34rpx;
	}

	.popup-title-custom {
		margin-bottom: 28rpx;
		height: 48rpx;
		font-size: 34rpx;
		font-family: PingFangSC-Medium, PingFang SC;
		font-weight: 500;
		color: #1F2227;
		line-height: 48rpx;
		text-align: center;
	}

	.popup-subtitle {
		margin: 32rpx 0;
		height: 40rpx;
		font-size: 28rpx;
		font-family: PingFangSC-Medium, PingFang SC;
		font-weight: 500;
		color: #1F2227;
		line-height: 40rpx;
	}

	.comment-comment {
		max-height: 50vh;
		overflow: scroll;
		padding-bottom: 10vh;
	}

	.nodata {
		display: flex;
		align-items: center;
		justify-content: center;
		flex-direction: column;
		font-size: 28rpx;
		line-height: 40rpx;
		color: #A5A9AF;

		image {
			width: 256rpx;
			height: 256rpx;
		}
	}

	.comment-textarea {
		padding: 5px 15px;
		border: 1px solid #aaa;
		border-radius: 16px;
	}

	.c-mask {
		position: fixed;
		left: 0;
		top: 0;
		z-index: 10075;
		width: 100vw;
		height: 100vh;
		background-color: rgba(0, 0, 0, .5);
	}

	.iinput-view {
		position: fixed;
		left: 0;
		bottom: 0;
		z-index: 10077;
		width: 100vw;
		min-height: 10vh;
		background-color: #fff;
		box-shadow: 5px 0 0 5px #f5f5f5; 
	}
</style>

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值