大家肯定对@功能不陌生,在如今的各大社交软件中它是一种不可或缺的功能。实现@人的功能并不复杂,只需将@人员的id传给后端,后端下发通知即可。主要的复杂点在于一键删除功能与变色功能,web端可以使用现成库 caret.js 或者 At.js 来实现。但笔者需要在小程序中实现这个功能,而且在 textarea 标签里实现,当然@人名的变色功能自然而然就砍掉了。
准备工作
怎么来实现一键删除呢?首先想到对@人名前后用特殊符号标记+正则来实现,但结果不是很理想,扩展性也比较差,如果还要匹配话题之类的就得多写一套代码,所以就试着找其他方法解决。发现 wx.getSelectedTextRange 可以获取文本框聚焦时的光标,这样就可以将@人员插入文本指定位置。文本框事件 @input 的可以获取到变化的数据与位置,那就可以根据变化的位置与变化的数据来判断是否命中@人员,@人员的位置可以通过计算获取。
// bindinput事件返回值 // value为变化后的值 cursor为变化的位置 keyCode为触发的键值 const {value, cursor, keyCode} = event.detail // 获取光标位置,聚焦时生效 wx.getSelectedTextRange({ complete: res => { console.log('光标位置:', res.start, res.end) } })
准备工作做好了就进入实践环节,毕竟实践是检验真理的唯一标准。设计图呈现:通过点击@按钮到人员列表页面,选择人员后返回,具体如下图。这里涉及页面之间的通信问题,可以通过状态管理器、数据缓存、获取页面栈设置数据等来实现,本例中使用数据缓存。
数据组装
从人员列表返回用 wx.navigateBack ,会触发 onShow 这个生命周期,所以需要在 onShow 里组装@数据。获取到的@人员根据光标位置对文本进行字符串截取组装,若未获取到光标位置则直接将@人员添加到文本末尾。然后对@人员数据、文本数据等进行备份,用于后续的计算。
initAtFn() { // 获取@人员数据 const me = this const initMemberList = wx.getStorageSync('atMemberList') const atMemberArr = initMemberList ? initMemberList : [] // 赋值后清除@人员数据 wx.removeStorageSync('atMemberList') // 获取上一次光标的位置 const preCursor = wx.getStorageSync('blurCursor') ? parseInt(wx.getStorageSync('blurCursor')) : me.content.length // 将 @人员数据 并入内容区域 if (atMemberArr.length > 0) { // 获取人员名称 const atMemberName = `@${atMemberArr[0].name}` // 如果上次光标有记录 就根据光标分割字符串 并入@人员名称 if (preCursor.toString().length !== me.content.length) { const start = me.content.substring(0, preCursor) const end = me.content.substring(preCursor) me.content = `${start}${atMemberName}${end}` } else { me.content += `${atMemberName}` } me.atArr = me.atArr.concat(atMemberArr) // 合并人员 wx.setStorageSync('blurCursor', preCursor + atMemberName.length) }else { wx.setStorageSync('blurCursor', me.content.length) } me.focus = true me.copyContent = me.content me.executeArr = me.getAtMemberPosFn() // 获取@人员位置 }
计算@人员位置
对@人员数组进行遍历,计算@人员在文本中的位置区间。通过indexOf来获取起点(这里有一个缺陷,也是需要优化的点,当手动输入的内容中有和@人员名字相同的字段时,那么位置靠前的那一个将会生效),终点为起点+名字长度。这里有个问题:如果重复@相同的人员,删除时怎么区分呢?笔者想当然的使用了时间戳,结果发现在遍历中使用时间戳并不准确,只有规规矩矩生成唯一值。
计算时收集了人员位置的最值区间,在这个范围之外增减文本不会影响@人员的完整性。下面是代码:
getAtMemberPosFn() { const me = this const [tipArr, left, right] = [ [], [], [] ] // 根据@人员的数组来匹配计算所处位置 me.atArr.map(item => { const name = item.name const userId = item.userId // 此处有一个缺陷 如果手输入的和获取的@人名字相同 第一个会生效 第二个不会生效 let start = me.copyContent.indexOf(name) if (tipArr.length > 0) { const _arr = tipArr.filter(v => v.name.includes(name)) if (_arr.length > 0) { start = me.copyContent.indexOf(name, _arr[_arr.length - 1].end) } } const end = name.length + start // end left.push(start) right.push(end) // 获取唯一标识 是用于重复@的区分 const guid = me.createGuidFn() const tipObj = { start: start - 1, // @ - 1 end, name, atName: `@${name}`, type: item.userId, userId: userId, code: guid } tipArr.push(tipObj) }) // 获取区间左右最值 right.length > 0 ? me.maxAt = Math.max(...right) : me.maxAt = 0 left.length > 0 ? me.minAt = Math.min(...left) : me.minAt = 0 me.atArr = tipArr return tipArr }
一键删除功能