前言

大家肯定对@功能不陌生,在如今的各大社交软件中它是一种不可或缺的功能。实现@人的功能并不复杂,只需将@人员的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

}

一键删除功能

@人员的位置区间已经计算出来了,接下来监听输入框的内容变化实现一键删除功能,当输入框文本内容变化,会触发 @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}${item.atName} `

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

}

以上就实现了纯文本的@功能,通过计算位置来实现的优点是具有扩展性,比如一套代码可以实现#话题功能和@功能共存,只需加个type作为区分即可。缺点是一键删除时体验不是很好,并且删除后不能控制光标位置,不能实现人员名称变色等。虽然功能比较ZZ,但也比较有趣,所以就分享给大家,如果大家有更好的解决方案,评论区有请。

完整代码请移步 语雀

总结

到此这篇关于微信小程序纯文本实现@功能的文章就介绍到这了,更多相关小程序@功能内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

微信聊天自动解析html文本,微信小程序纯文本实现@功能相关推荐

  1. 微信开发者工具如何模拟调试扫描小程序二维码功能

    本文主要介绍如何在微信开发者工具中模拟调试现场扫描小程序二维码功能场景. 1.添加自定义编译模式 在微信开发者工具内如下图所示,添加编译模式: 2.启动页面设置 在启动页选项中填入扫描小程序二维码后要 ...

  2. layui获取select 文本_小程序富文本编辑器editor初体验

    终于,微信在5月9号的v2.7.0版本中新增了 editor富文本编辑器组件,今天有时间了准备体验一下 在5月6日的时候写了一篇小程序富文本解析的「伪需求」,从wxParse到towxml的坑,当时还 ...

  3. onenetsim定位功能吗_微信小程序新增后台定位功能,你会卸载地图APP用微信导航吗...

    [PConline]8月8日,根据微信官方发布的消息显示,小程序新增后台定位功能,并对自动化测试功能进行了升级.据介绍,为了满足线路导航.路线记录等服务场景下,小程序需要长时间持续定位来提供服务.当用 ...

  4. 微信小程序富文本解析

    微信小程序富文本解析 *人狠话不多,直接代码搞起* html代码(后台返回的html代码) <p>这是一段文字</p><p><strong>这是加粗的字 ...

  5. uni-app 富文本 小程序 富文本 (微信小程序、支付宝小程序、百度小程序)直接套用pc端富文本问题解析

    微信跟百度 直接使用 u-parse组件,效果还是不错的,注意一点是a标签的复制提示语,自己需要uni.hideLoading()一下,再重置. 支付宝相对复杂一点,采用原生rich-text 标签, ...

  6. 微信小程序富文本编辑器获取内容

    1.新建wxParse文件夹 里面的结构是这样:wxParse :{ emojis (文件夹) html2json.js (文件) htmlparser.js(文件) showdown.js (文件) ...

  7. 微信小程序 富文本组件使用

    能够使用微信小程序提供的富文本组件及引入的第三方富文本组件 掌握使用微信小程序的表单组件 掌握使用微信小程序的form表单及如何提交数据 掌握使用微信小程序的导航及跳转组件 一.基础内容组件  1.1 ...

  8. 微信小程序富文本编辑器editor初体验-图片上传

    https://developers.weixin.qq.com/miniprogram/dev/component/editor.html 以前没有小程序富文本编辑器,只能输入文字,图片上传后,在服 ...

  9. 微信小程序富文本处理

    微信小程序富文本处理 wxml页面代码: <rich-text nodes ="{{content.content}}"></rich-text> ts代码 ...

最新文章

  1. 一场稳定、高清、流畅的大型活动直播是怎么炼成的?
  2. 关于while 和if
  3. Win10下MySQL5.7.20 Mysql(64位)解压版安装及bug修复
  4. 窥探黑盒-卷积神经网络的可视化
  5. css应用网页设计,CSS技术在网页设计中的运用
  6. MySQL常用语句一、连接MySQL格式:mysql-h主机地址-u用户名-p用户密
  7. 太阳能充电调节代码_太阳能LED路灯控制器有什么作用
  8. android之在view中内嵌浏览器的方法
  9. 显示多个页面退出登陆_软件测试小白如何第一次登陆时给LINUX的配置网络
  10. 我的Markdown的利器——Markdown Here、有道云笔记、iPic
  11. Python中yield的作用??
  12. 谷歌中巨大的 SEO 骗局!排名靠前的 HTML 编辑器也不可信
  13. 用es5实现es6的promise,彻底搞懂promise的原理
  14. #动态规划 LeetCode 120 三角形最小路径和
  15. 阿里巴巴Java开发手册(泰山版)
  16. ADXL345传感器小结
  17. 运营数据分析,怎么做才有深度
  18. 求n个整数的平均值与中位数
  19. Android 讯飞离线语音听写/离线语音识别SDK
  20. 老铁,你这什么键盘布局? 当然是Colemak

热门文章

  1. ExoPlayer在开启循环播放时 统计播放次数和索引
  2. 首屈一指的全球招聘与薪资支付平台Deel收购Zeitgold,以增强薪资和人工智能服务
  3. 阿里云80 端口别阿里云盾占用
  4. 初学者基于paddle的计算机视觉快速上手项目
  5. STM32 freertos堆栈溢出检查方法
  6. navicat删除注册表文件_如何彻底删除mysql服务(清理注册表)详解
  7. Linux- Showdown 命令详解
  8. java web 站内信 设计
  9. Decoder原理和浅解
  10. convexity and concavity(凸面和凹面)