基于vue2.x + vant2.x 实现的仿微信@功能(稍作修改即可支持原生js和react),兼容pc端和手机端,如有bug,欢迎提出~
年后分配到一个新项目 - 企业微信论坛。项目涉及到发帖子、评论、留言等功能,刚好和我的个人博客功能类似,不能说一模一样吧,也八九不离十了(内心偷乐😁😁)。
但是,令人头疼的问题来了,附加了一个@功能
,即在评论框中输入@
实现通知用户的功能。这个可以说触及到我的的知识盲点了,但其实很多应用都有这类功能了,如:微博、QQ、微信、企业微信… 哎,需求需要,不得不做,静下心来好好研究研究叭~
一、技术方案分析
方案很多,适合自己、适合团队才称得上完美。
这里归纳了几种常用的方案:
- textarea、input (例:新浪微博)
- 这种方式我感觉是最最最复杂的,而且坑也巨多。具体原因参考@提及如何实现-包含了使用textarea实现@功能的解析。相信我如果你手写,你不会快乐的!!!所以推荐下面的库给大家、只要稍作改动就可以使用啦~~
- Tribute.js(推荐)
- At.js (JQ)
- contenteditable (例:QQ空间, 掘金)
- HTML5新属性,表示元素是否可被用户编辑,即像textarea文本框那样可以输入内容。自己试了下这种方法,注意点很多,bug也不少 (比如:实现到最后,刚看到了一丝丝希望QAQ,但是,光标移至中间位置继续向右移动,光标最终会消失),没找到解决办法,最终,放弃了😂😂。
- contenteditable-MDN
- contenteditable实现编辑器,光标、输入法处理
- 富文本 (例:企业微信TAPD)
- 拥有丰富的配置与强大的API,支持 文本、富文本、图片、表情等。
- 考虑到扩展性与踩坑的深浅、api的丰富程度,最终选择 quill富文本 做为最终的方案。(PS:因为使用vue开发所以这里使用的是vue-quill-editor)
- 推荐几个常用的富文本quill、wangeditor(不得不夸一下
wangeditor
,前一天在Issues
提出@功能需求,隔天就做出了一版@功能的插件,可惜我已经用前者实现了👽👽)
二、效果和功能描述
最终目标:实现仿微信@用户功能。
demo地址: zugelu - 仿微信@功能
1. 效果图
2. 主要功能
- 在输入框内任何地方输入
@
,或点击@按钮
(demo中Show @ Menu
按钮),弹出选择用户弹框 - 人员弹框支持搜索功能、支持异步
- 选择人员,在光标后追加
@名称
,并绑定人员相关数据 @名称
作为整体,光标不可游走其中- 删除时,将
@名称
作为一个整体进行删除
实现以上功能,可以说已经完成了一个高仿微信@功能。
三、准备工作
本案例是基于vue-quill-editor富文本编辑器(v3.0.6)和quill-mention(v3.1.0)来实现的。需下载quill相关依赖。
npm i vue-quill-editor quill-mmention --save
初始化项目结构
<template>
<div id="at">
<!-- editor -->
<quill-editor
ref="myQuillEditor"
v-model="content"
:options="editorOption"
/>
<!-- toolbar -->
<div id="toolbar"></div>
</div>
</template>
<script>
import { quillEditor } from 'vue-quill-editor'
import 'quill/dist/quill.snow.css'
import 'quill-mention'
export default {
name: 'at-mention',
components: {
quillEditor
},
data () {
return {
content: '', // 富文本html
isClickMention: false, // 是否点击@按钮打开@菜单弹框
isChineseInputMethod: false, // 是否中文输入法状态
show: false, // 弹框显示状态
editorOption: {
placeholder: '说点什么呢。。。',
modules: {
toolbar: { // 自定义toolbar,本案例隐藏
container: '#toolbar',
},
mention: { // 将 quill-mention 配置传递给 quill
mentionDenotationChars: ["@"], // 指定哪些字符可以触发@提及
source: function (searchTerm, renderList, mentionChar) { // @提及回调
console.log(searchTerm, renderList, mentionChar)
renderList([], searchTerm) // 因为我们要自定义弹框,所以这里把默认的@用户数据置空
}
}
}
}
}
},
computed: {
editor () {
return this.$refs.myQuillEditor.quill
}
},
}
</script>
<style >
/* quill-toolbar */
.ql-toolbar {
padding: 0 !important;
border: 0 !important;
}
</style>
初始化完成,就可以使用基于quill-mention
实现的@
功能了,但现有功能并不能满足我的需求(@之后从底部弹出用户列表),需要在现有基础上进行改造升级。
提示:
- mention -> source 中,
renderList([], searchTerm)
, 因为我们要自定义弹框,所以这里把默认的@用户数据置空。 - css样式中
.ql-toolbar
,去除默认边距,目的隐藏toolbar
- 后续
data
所用到的数据,在初始化阶段已全部列出
四、实现步骤
1. 中文输入法的判断
中文模式下输入@要进行判断,如:输入“说点什么呢@”,这个时候不能监听@事件。
如何判断中文输入?
当用户使用中文输入法开始输入中文时,compositionstart
事件就会被触发。当文中文输入完成或取消时,compositionend
事件将被触发。利用这个机制我们就可以判断是否中文状态。
- compositionstart事件 当用户使用拼音输入法开始输入汉字时,事件就会被触发。
- compositionend事件 当中文输入完成或取消时,事件将被触发。
<template>
<div id="at">
<!-- editor -->
<quill-editor
v-model="content"
@compositionstart.native="onCompositionstart"
@compositionend.native="onCompositionend"
/>
</div>
</template>
<script>
export default {
data () {
return {
isChineseInputMethod: false, // 是否中文输入法状态
}
},
methods: {
// 中文输入触发
onCompositionstart () {
this.isChineseInputMethod = true
},
// 中文输入关闭
onCompositionend () {
this.isChineseInputMethod = false
}
...
}
}
</script>
2. @事件的监听
判断设备类型(适配电脑和手机端)
// 判断设备是否为 Mobile (提示:请勿在浏览器调试模式下,切换手机模式,否则会导致@功能失效)
isMobile () {
return navigator.userAgent.match(/(iPhone|iPod|Android|ios|iOS|iPad|Backerry|WebOS|Symbian|Windows Phone|Phone)/i)
},
根据终端判断用户是否输入@符号
// 判断是否输入 @符号
isAtCode (e) {
return this.isMobile() ?
((e.keyCode === 229 || e.keyCode === 50) && e.code === 'Digit2' && e.key === '@')
: (((e.keyCode === 50 && e.key === '@') || (e.keyCode === 229 && e.code === 'Digit2')) && e.shiftKey)
}
监听富文本keydown事件,判断是否为非中文状态下键入的@符号,如是,则打开用户列表弹框
// 键盘按下
onKeyDown (e) {
// 判断状态非中文输入法,并且监听到了@的事件
if (!this.isChineseInputMethod && this.isAtCode(e)) {
// 输入@打开@菜单弹框
this.show = true
}
},
PS:本案例用户列表的代码就不贴了,可根据需求自定义,或前往at-mention获取demo代码。
3. 点击用户生成@标签
使用quill-mention
提供的API - insertItem
来实现。
当点击@列表中的想要@的用户,则触发selectItem
事件,把@的内容插入到对应位置。
// 选择要@的用户
selectItem (item) {
this.insertItem(item)
this.show = false
},
// 插入@内容,item即为用户信息
insertItem (item) {
const mention = this.editor.getModule('mention')
mention.insertItem({ id: item.id, value: item.name, denotationChar: '@' }, true)
}
这样会有一个问题,@用户的时候多了一个@符号,原因是insertItem
会默认帮我们添加一个@符号,加上手动输入的@符号就多出一个@。
所以这里需要特殊处理一下,调用history.undo()
撤销API(需要额外配置下history
参数):
editorOption: {
modules: {
...
history: { // history 配置(History模块负责处理Quill的撤销和重做)
delay: 0 // 将延迟设置为0,那么几乎每次输入字符都会被记录成一个变化,然后,撤销动作就会一次撤销一个字符
}
}
},
// 选择要@的用户
selectItem (item) {
// 如果不是通过@按钮打开的弹框,则需要删除多余的@符号
if (!this.isClickMention) {
// 撤销一步,删除手动输入的@符号(需在modules中配置history: { delay: 0 })
this.undo()
}
this.insertItem(item)
this.show = false
},
// 撤销
undo () {
this.editor.history.undo();
},
4. 点击外部的@按钮插入@标签
非常简单,直接调用API - insertItem
来插入@标签即可。(会默认添加@符号)
五、总结
这个功能我自己测试没问题,兼容性也不错(并未进行详细测试、可能会存在其他问题),如果使用中遇到问题,欢迎提出反馈,希望能和大家一起进步~