@人员的位置区间已经计算出来了,接下来监听输入框的内容变化实现一键删除功能,当输入框文本内容变化,会触发 @input 事件,它会返回变化后的值 value ,变化的位置 cursor ,我们将利用这两个数据作为是否 命中@人员的判断依据 。将情况分为以下几种:
变化后的value为空,即清空了输入框。
数据变化的光标位置大于@人员位置最值区间的最大值,即不影响@人员位置。
当数据变化影响@人员时,这里对增加减少内容做了区分处理:
增加时,如果增加位置小于最值的最小值,则直接重新计算位置。如果增加值的位置命中@人员位置,则过滤掉失效人员,再重新计算。这里需要注意,移动端输入法会有一次性输入多个字符,变化的位置不再是返回的光标位置,而是以光标位置减去变化前后数据的差值。
删除时,获取删除的起始位置 (A,B) ,然后与@人员位置 (start, end) 作比较。 当 !(A < start || B > end) 时,则为命中,将命中的@人员过滤掉即可。
changeFn(txt) { const me = this const { value, cursor, keyCode } = txt.detail // 改变后的值,改变的位置,按键 // 如果改变后的值为'', 就直接返回 if(!value) { me.content = value me.copyContent = value me.atArr = [] return false } // 判断值改变的增减 const changeLen = value.length - me.copyContent.length // 值改变的光标位置 不影响@人员的则不管 if (cursor > me.maxAt) { me.copyContent = me.content return false } // 判断为 增加值 if (changeLen > 0) { const addCursor = cursor - changeLen // 重新计算增加位置 防止移动端一次性粘贴导致失效问题 me.copyContent = me.content // 增加值的位置 小于左区间最值 则重新计算位置 if(addCursor < me.minAt) { me.executeArr = me.getAtMemberPosFn() return false } me.executeArr.map(item => { const { start, end, name, code } = item if (addCursor < end && addCursor > start) { // 删除命中人员,则该人员失效 me.atArr = me.atArr.filter(v => v.code !== code) } }) // 需要重新计算位置 me.executeArr = me.getAtMemberPosFn() } else { let replaceStr = '' // 应被删除的字段 const left = [] // 删除左值集合 const right = [] // 删除右值集合 const delLen = cursor - changeLen // 本身删除的长度 const deleteString = me.copyContent.substring(cursor, delLen) // 本身删除的字段 [cursor, changeLen) // 获取应被删除的左右位置 function pushArrEvent(s, e) { left.push(s) right.push(e) } me.executeArr.map(item => { let { start, end, name, code } = item // D大 <= B小 || D小 >= B大 // 命中部分为 删除部分与@人员的交集 if (!(delLen <= start || cursor >= end)) { // 命中判定,命中位置在名字区间 左边/右边/之间/或者多选中删除的 if (delLen <= end && cursor >= start) { pushArrEvent(start, end) } else { if (cursor > start) { if (delLen > end) { pushArrEvent(start, delLen) } else { pushArrEvent(start, end) } } else if (cursor < start) { if (delLen > end) { pushArrEvent(cursor, delLen) } else { pushArrEvent(cursor, end) } } else { pushArrEvent(cursor, delLen) } } // 获取一键删除区间 const del_left = Math.min(...left) const del_right = Math.max(...right) // 根据区间获取一键删除字段 replaceStr = me.copyContent.substring(del_left, del_right) // 删除后的赋值 me.content = me.copyContent.substring(0, del_left) + me.copyContent.substring(del_right) // @人员数组生成 me.atArr = me.atArr.filter(v => v.code !== code) } }) // 执行完后 重新赋值计算 me.copyContent = me.content me.executeArr = me.getAtMemberPosFn() } }
添加标签
我们还差最后一步,那就是给@人名添加标签,用于显示时与一般文本做区分。这里踩了一个坑,用正则替换时,如果名字与名字之间存在包含关系,则会失效,所以用记录位置的方式来对文本进行截取组装。
submitTxtFn() { const copyTxt = this.content const arr = JSON.parse(JSON.stringify(this.atArr)) const atUserIds = [...new Set(arr.map(v=>v.userId))] // 获取@人员id let targetContent = '' let count = 0 // 给@人员添加wxml标签,此处用了jyf-Parser富文本解析插件,href里面的值用于点击传参 if(arr.length > 0) { arr.forEach((item, index)=>{ let _tip = '' const txt = copyTxt.substring(count, item.start) // 加空格 _tip = `${txt}<a href="https://www.jb51.net/${item.name}" >${item.atName} </a>` targetContent += _tip // 处理最后一个标签后面的文本 if(index + 1 === arr.length) { if(item.end < copyTxt.length) { targetContent += copyTxt.substring(item.end) } } count = item.end }) }else { targetContent = this.content } // 目标数据 const targetObj = { content: targetContent, atIds: atUserIds } this.submitData = targetObj return targetObj }