当前位置:   article > 正文

评论组件--uniapp textarea focus,string.replace_uniapp评论插件

uniapp评论插件

最近使用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

  1. <template>
  2. <view class="comment-wrap">
  3. <view class="title">评论</view>
  4. <view class="comment-list" v-if="list.length">
  5. <view v-for="item in list" :key="`comment-${item.id}`">
  6. <Item :item="item" @reply="onReply" />
  7. <view class="second-list" v-if="item.comment.length>0">
  8. <Item avatar-size="22" v-for="jtem in item.comment.slice(0, 2)" :item="jtem"
  9. :key="`reply-${jtem.id}`" @reply="onReply" />
  10. <view class="check-more" v-if="item.comment.length>2" @click="openMoreComment(item)">
  11. 查看全部{{item.comment.length}}条精彩评论&nbsp;&nbsp;></view>
  12. </view>
  13. </view>
  14. <view class="more-comment-tip" @click="getMoreComment">{{more?'查看更多':'已经到底了'}}</view>
  15. </view>
  16. <view class="nodata" v-show="showCommentResult&&list.length===0">
  17. <image src="../../static/noContent.png" mode=""></image>
  18. <text>暂无评论</text>
  19. </view>
  20. </view>
  21. <!-- 显示一级评论的全部回复 -->
  22. <u-popup :show="showMoreCommentPop" :closeable="true" mode="bottom" round="32" @close="closeMoreCommentPop"
  23. class="comment-input-popup">
  24. <view class="popup-title-custom">显示全部({{moreComment?.comment?.length}})</view>
  25. <view class="main-comment">
  26. <Item :item="moreComment" @reply="e=>onReply(e, 'first')" />
  27. </view>
  28. <view class="popup-subtitle">全部回复</view>
  29. <view class="comment-comment" v-show="moreComment?.comment?.length">
  30. <Item :item="jtem" v-for="jtem in moreComment.comment" @reply="onReply" />
  31. <view class="more-comment-tip">已经到底了</view>
  32. </view>
  33. </u-popup>
  34. <view v-show="showInputPop" class="c-mask" @click="closeInputPop" @touchmove.stop.prevent="disableScroll"></view>
  35. <view class="iinput-view" :style="{bottom: showInputPop?isIphone?'0%':'50%':'0%'}" @touchmove.stop.prevent="disableScroll">
  36. <view class="c-popup-content">
  37. <view class="comment-input-wrap">
  38. <textarea v-model="comment" class="comment-textarea" :maxlength="250" :focus="replyFocus"
  39. placeholder="欢迎发表你的观点" confirm-type="send" auto-height @confirm="commentCommit"
  40. @focus="bindFocus" />
  41. <uni-icons v-if="hasComment" type="paperplane" size="30" color="#286CFB"
  42. @click="commentCommit"></uni-icons>
  43. <view class="cancel-text" v-if="showInputPop&&!hasComment" @click="closeInputPop">取消
  44. </view>
  45. </view>
  46. <!-- <view class="reply-block" v-if="replyContent">{{replyContent}}</view> -->
  47. </view>
  48. </view>
  49. </template>
  50. <script setup>
  51. import {
  52. ref,
  53. getCurrentInstance,
  54. computed
  55. } from 'vue'
  56. import Item from './Item.vue'
  57. import {
  58. getCommentList,
  59. commentCreate
  60. } from '../../api/index.js'
  61. // 当前实例
  62. const {
  63. proxy
  64. } = getCurrentInstance()
  65. const props = defineProps({
  66. articleId: {
  67. type: [Number, String],
  68. }
  69. })
  70. // 新建评论
  71. const comment = ref('')
  72. // 评论列表
  73. const list = ref([])
  74. // 新建评论弹窗
  75. const showInputPop = ref(false)
  76. // @回复内容显示
  77. const replyContent = ref('')
  78. // 回复目标ID
  79. const targetId = ref('')
  80. // 更多评论弹窗
  81. const showMoreCommentPop = ref(false)
  82. // 一级评论展开的内容
  83. const moreComment = ref({})
  84. // 是否显示评论获取的列表结果
  85. const showCommentResult = ref(false)
  86. // 点击回复控制textarea的聚焦
  87. const replyFocus = ref(false)
  88. // 获取当前设备,如果是Safari,弹框将留在底部
  89. const isIphone = ref('')
  90. isIphone.value = /iphone|ipad|ipod|ios/.test(window.navigator.userAgent.toLowerCase())
  91. // 请求列表需要的page和pagesize
  92. let page = 1
  93. let pagesize = 10
  94. // 是否还有更多评论
  95. const more = ref(true)
  96. const hasComment = computed(()=>{
  97. return comment.value.replace(/@[0-9a-zA-Z\u4e00-\u9fa5]+:/, '').trim().length>0
  98. })
  99. const requestList = async () => {
  100. const params = {
  101. article_id: props.articleId,
  102. target: 0,
  103. page,
  104. pagesize
  105. }
  106. const {
  107. data
  108. } = await getCommentList(params)
  109. if (data) {
  110. // 判断是否需要显示还有更多
  111. more.value = Math.ceil(data.total / pagesize) > page
  112. data.list.forEach(item => {
  113. item.comment = itFn([], item.comment, '')
  114. item.comment.length && item.comment.sort((f, s) => (+new Date(s.created_at) -
  115. +new Date(f.created_at)))
  116. })
  117. if (params.page === 1) {
  118. // 显示有无结果
  119. showCommentResult.value = true
  120. list.value = []
  121. }
  122. list.value = list.value.concat(data.list)
  123. if (showMoreCommentPop.value) {
  124. updateMoreCommentData()
  125. }
  126. }
  127. }
  128. // 一级评论更多里面回复后刷新数据
  129. const updateMoreCommentData = () => {
  130. moreComment.value = list.value.find(item => item.id === moreComment.value.id)
  131. }
  132. // 递归得到所有1级评论的回复
  133. function itFn(target, comment, text) {
  134. if (comment.length > 0) {
  135. for (let i = 0; i < comment.length; i++) {
  136. let item = comment[i]
  137. if (text) {
  138. item.replyContent = text
  139. }
  140. target.push(item)
  141. if (item?.comment?.length > 0) {
  142. let str = `@${item.username}:${item.content}`
  143. itFn(target, item.comment, str)
  144. }
  145. }
  146. }
  147. return target
  148. }
  149. requestList()
  150. // 提交评论
  151. const commentCommit = async () => {
  152. let commentStr = comment.value
  153. commentStr = commentStr.replace(/@[_-0-9a-zA-Z\u4e00-\u9fa5]+:/, '')
  154. commentStr = commentStr.trim()
  155. if (commentStr) {
  156. const {
  157. data
  158. } = await commentCreate({
  159. aid: props.articleId,
  160. content: commentStr,
  161. target: targetId.value ? targetId.value : 0
  162. })
  163. if (data) {
  164. uni.showToast({
  165. title: '评论成功',
  166. icon: 'none'
  167. })
  168. comment.value = ''
  169. closeInputPop()
  170. requestList()
  171. }
  172. } else {
  173. uni.showToast({
  174. title: '内容不能为空哦',
  175. icon: 'none'
  176. })
  177. }
  178. }
  179. const openInputPop = () => {
  180. showInputPop.value = true
  181. }
  182. // 组件内回复, type==='first'时是一级直接评论,不用增加@内容
  183. const onReply = (e, type) => {
  184. // 组件回复
  185. if (e) {
  186. comment.value = type === 'first' ? '' : e.prefixContent
  187. targetId.value = e.targetId
  188. }
  189. openInputPop()
  190. replyFocus.value = true
  191. }
  192. const openMoreComment = (data) => {
  193. showMoreCommentPop.value = true
  194. moreComment.value = data
  195. }
  196. const closeMoreCommentPop = () => {
  197. showMoreCommentPop.value = false
  198. moreComment.value = {}
  199. }
  200. const closeInputPop = () => {
  201. showInputPop.value = false
  202. replyFocus.value = false
  203. comment.value = ''
  204. targetId.value = ''
  205. }
  206. const getMoreComment = () => {
  207. if (more.value) {
  208. page++
  209. requestList()
  210. }
  211. }
  212. const disableScroll = () => {
  213. return
  214. }
  215. const bindFocus = () => {
  216. showInputPop.value = true
  217. // 1级弹框底部直接触发的
  218. if (showMoreCommentPop.value) {
  219. targetId.value = targetId.value ? targetId.value : moreComment.value.id
  220. }
  221. }
  222. </script>
  223. <style lang="scss" scoped>
  224. .comment-wrap {
  225. padding: 40rpx 32rpx;
  226. width: 100%;
  227. height: auto;
  228. padding-bottom: calc(50rpx + constant(safe-area-inset-bottom));
  229. padding-bottom: calc(50rpx + env(safe-area-inset-bottom));
  230. .comment-list{
  231. padding-bottom: 10vh;
  232. }
  233. }
  234. .title {
  235. margin-bottom: 40rpx;
  236. width: 100%;
  237. height: 48rpx;
  238. font-size: 34rpx;
  239. font-family: PingFangSC-Medium, PingFang SC;
  240. font-weight: 600;
  241. color: #1F2227;
  242. line-height: 48rpx;
  243. }
  244. .second-list {
  245. padding-left: 80rpx;
  246. }
  247. .more-comment-tip {
  248. height: 104rpx;
  249. font-size: 24rpx;
  250. font-family: PingFangSC-Regular, PingFang SC;
  251. font-weight: 400;
  252. color: #8F959F;
  253. line-height: 94rpx;
  254. text-align: center;
  255. }
  256. .comment-input-popup ::v-deep .u-popup__content,
  257. .c-popup-content {
  258. padding: 38rpx 28rpx;
  259. width: 100%;
  260. background-color: #fff;
  261. padding-bottom: calc(50rpx + constant(safe-area-inset-bottom));
  262. padding-bottom: calc(50rpx + env(safe-area-inset-bottom));
  263. }
  264. .comment-input-wrap {
  265. width: 100%;
  266. display: flex;
  267. flex-direction: row;
  268. justify-content: space-between;
  269. align-items: center;
  270. .cancel-text {
  271. margin-left: 8rpx;
  272. width: 60rpx;
  273. font-size: 28rpx;
  274. color: #286CFB;
  275. flex-shrink: 0;
  276. }
  277. .comment-textarea {
  278. flex-grow: 1;
  279. }
  280. }
  281. .reply-block {
  282. padding: 8rpx 16rpx;
  283. margin: 16rpx 0;
  284. font-size: 24rpx;
  285. font-family: PingFangSC-Regular, PingFang SC;
  286. font-weight: 400;
  287. color: #8F959F;
  288. line-height: 34rpx;
  289. background: #F5F6FB;
  290. border-radius: 8rpx;
  291. }
  292. .check-more {
  293. margin-left: 56rpx;
  294. margin-bottom: 32rpx;
  295. height: 34rpx;
  296. font-size: 24rpx;
  297. font-family: PingFangSC-Regular, PingFang SC;
  298. font-weight: 400;
  299. color: #999999;
  300. line-height: 34rpx;
  301. }
  302. .popup-title-custom {
  303. margin-bottom: 28rpx;
  304. height: 48rpx;
  305. font-size: 34rpx;
  306. font-family: PingFangSC-Medium, PingFang SC;
  307. font-weight: 500;
  308. color: #1F2227;
  309. line-height: 48rpx;
  310. text-align: center;
  311. }
  312. .popup-subtitle {
  313. margin: 32rpx 0;
  314. height: 40rpx;
  315. font-size: 28rpx;
  316. font-family: PingFangSC-Medium, PingFang SC;
  317. font-weight: 500;
  318. color: #1F2227;
  319. line-height: 40rpx;
  320. }
  321. .comment-comment {
  322. max-height: 50vh;
  323. overflow: scroll;
  324. padding-bottom: 10vh;
  325. }
  326. .nodata {
  327. display: flex;
  328. align-items: center;
  329. justify-content: center;
  330. flex-direction: column;
  331. font-size: 28rpx;
  332. line-height: 40rpx;
  333. color: #A5A9AF;
  334. image {
  335. width: 256rpx;
  336. height: 256rpx;
  337. }
  338. }
  339. .comment-textarea {
  340. padding: 5px 15px;
  341. border: 1px solid #aaa;
  342. border-radius: 16px;
  343. }
  344. .c-mask {
  345. position: fixed;
  346. left: 0;
  347. top: 0;
  348. z-index: 10075;
  349. width: 100vw;
  350. height: 100vh;
  351. background-color: rgba(0, 0, 0, .5);
  352. }
  353. .iinput-view {
  354. position: fixed;
  355. left: 0;
  356. bottom: 0;
  357. z-index: 10077;
  358. width: 100vw;
  359. min-height: 10vh;
  360. background-color: #fff;
  361. box-shadow: 5px 0 0 5px #f5f5f5;
  362. }
  363. </style>

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/683524
推荐阅读
相关标签
  

闽ICP备14008679号