• 评论组件--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

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

  • 相关阅读:
    想带着学生做一个操作系统,可行性有多大?
    云赛道---人工智能概述(重点总结)决赛准备
    代码优化技巧
    解决问题:-1: error: LNK1104: 无法打开文件“D3dx9.lib”
    L9170 LGCN 原厂直销5A大电流输出DC双向马达驱动电路IC
    牵着她——表白代码(Python实现)
    AI标注已达人类水平,RLHF或许将再不需要人类
    asp.net+sqlserver团购网站c#
    vue3响应式
    C#.NET CORE .NET6 RSA 私钥签名 公钥验签(验证签名) ver:20230614
  • 原文地址:https://blog.csdn.net/sdaulee/article/details/126322586