赞
踩
最近使用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}}条精彩评论 ></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>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。