js 如何实现扫描枪快速扫码录入的功能(区分手动输入和扫码枪录入)。
实践背景
在近期工作过程中接手了一个让我有些棘手的需求,需求如下:
- 输入框内支持扫码录入商品,且支持连续扫码录入。
- 相同输入框中支持手动输入条码录入商品。
- 页面任意位置用扫码枪扫码都可以成功定位到该输入框且录入对应商品。
需要解决的问题
- 事件注册在那里
- 事件注册什么时候注册,什么时候注销。
- 如何不影响页面上其他 input 元素
- 如何区分手动输入和扫码枪录入
代码实现
1. 事件注册什么时候注册,什么时候注销
因为我们要注册的为全局事件,但是又不能影响到其他页面所以我们可以借助 vue 的生命周期来注册卸载。楼主这里因为页面切换用的是keeplive
所以我是将事件注册放到了 created()
和 activated()
中:
// tips:因为 f5 刷新不会触发 activated() 所以我再 created 中做了创建,但是普通加载又会触发所以有了如下逻辑处理 export default { data() { return { isCreate: false } }, created() { this.isCreate = true this.addKeyUp() }, activated() { if (!this.isCreate) this.addKeyUp() }, destroyed() { this.removeKeyUp() }, deactivated() { this.removeKeyUp() this.isCreate = false }, methods: { // 添加事件 addKeyUp() {}, // 删除事件 removeKeyUp() {} } }
2. 事件注册在那里,注册什么事件,用什么方式注册
- 因为我们要在页面任意位置识别并聚焦到固定的
input
中,所以我们需要把事件放到document
上面。 - 因为
keydown
事件按下不抬起会一直触发,所以这里我们采用keyup
事件更加友好。 - 我们这里使用了
addEventListener
注册事件,removeEventListener
来删除事件。这两个方法有三个参数,分别为(“事件名”,“事件触发的函数”,“采用捕获还是冒泡”)。这里我就不做赘述了,详细说明查看下面文档:官方文档
PS: 此处需注意一个细节,添加事件和删除事件的所有参数必须一致,否则会导致无法删除。
methods:{ // 添加事件 addKeyUp(){ document.addEventListener("keyup",this.documentKeyUp,true) }, // 删除事件 removeKeyUp(){ document.removeEventListener("keyup",this.documentKeyUp,true) } }
3.如何不影响页面上其他 input 元素
因为我们将事件注册到了document
所以我们的keyup
事件必然会影响到输入,我们要避免中情况我们就需要禁止页面上所有的事件冒泡,导致执行 document 上的keyup
事件触发。那么我们怎么去判断呢,我这边采用的分析event
数据中的event.srcElement.tagName
来进行判断。每一个元素的tagName
都不一样所以我们只需要判断我们keyup
时的event.srcElement.tagName
是否等于”INPUT”||”TEXTAREA”
methods:{ documentKeyUp(event){ if(["INPUT","TEXTAREA"].includes(event.srcElement.tagName)) return } }
4. 如何区分手动输入和扫码枪录入(最为关键的部分)
问题分析:
这个问题我们需要从硬件去分析,首先,扫码抢输入特点为快速录入自动在输入结束触发回车,人为按键输入相对扫码枪输入较慢,手动触发回车事件。那么我们从哪儿来拿到这个时间呢,通过分析event
得出timeStamp
字段,返回事件发生时的时间戳,扫码枪每次录入是 8-10 毫秒,手动录入为则在 80-120 毫秒左右,好一点的设备也会有在 30 毫秒左右,那么我们就可以用这个时间差来做区分手动录入和扫码枪录入。
所以代码逻辑如下:
export default { data() { return { // 存放每次 keyup 事件时间戳 keyUpIntervalArray: [], // 储存每一次按键的内容当让输入框组件获取焦点时拿到内容直接录入商品 scanText:"" } }, methods:{ documentKeyUp(event){ // event.key.length>1 if(["TEXTAREA","INPUT"].includes(event.srcElement.tagName) || event.key.length>1) return this.scanText = "" let temp = this.keyUpIntervalArray // 校验输入 let pattern = /d|s|[a-zA-Z]/ if(pattern.test(event.key))this.scanText += event.key // 只存少量时间戳 temp = util.clone(temp.splice(temp.length-5,5)) temp.push(event.timeStamp) this.keyUpIntervalArray = temp // 当储存的小于按键事件时间戳小于 2 无法对比,则不做判断 if(temp.length<2) return for(let i in temp){ let num = Math.ceil(temp[temp.length-1]) - Math.ceil(temp[temp.length-2]) if(num < 20 && num !=0){ this.$refs['selectGoods']?.focus(this.scanText) } } } } }
最终代码
input 组件模块
<el-input ref="selectGoods" v-model="searchText" @focus="selectFocus()" @keyup="keyUpEvent" />
// input 组件代码 export default { data() { return { // 存放每次 keyup 事件时间戳 keyUpIntervalArray: [], // 储存每一次按键的内容当让输入框组件获取焦点时拿到内容直接录入商品 scanText:"" // 是否为扫码录入 scangGun:false } }, methods:{ // input 函数 selectFocus(){ this.$refs["selectGoods"].select() }, // input 函数 keyUpEvent(){ if(event?.keyCode === 13 && this.searchText) this.inputGetData(this.scangGun?"":"handle") this.keyUpIntervalArray = util.clone(this.keyUpIntervalArray.splice(this.keyUpIntervalArray.length-5,5)) this.keyUpIntervalArray.push(event.timeStamp) if(this.keyUpIntervalArray.length<2) return for(let i in this.keyUpIntervalArray){ let num = Math.ceil(this.keyUpIntervalArray[this.keyUpIntervalArray.length-1]) - Math.ceil(this.keyUpIntervalArray[this.keyUpIntervalArray.length-2]) this.scangGun = num < 20 && num !=0 ? true : false if(i>0&&this.keyUpIntervalArray.length === parseInt(i)+1){ if(this.scangGun) return } } } } }
document 相关处理
export default { data() { return { // 存放每次 keyup 事件时间戳 keyUpIntervalArray: [], // 储存每一次按键的内容当让输入框组件获取焦点时拿到内容直接录入商品 scanText:"", // 是否通过 create 生命周期 isCreate: false } }, created() { this.isCreate = true this.addKeyUp() }, activated() { if (!this.isCreate) this.addKeyUp() }, destroyed(){ this.removeKeyUp() }, deactivated() { this.removeKeyUp() this.isCreate = false }, methods:{ // 添加事件 addKeyUp(){ document.addEventListener("keyup",this.documentKeyUp,true) }, // 删除事件 removeKeyUp(){ document.removeEventListener("keyup",this.documentKeyUp,true) }, // document 事件处理 documentKeyUp(event){ // event.key.length>1 if(["TEXTAREA","INPUT"].includes(event.srcElement.tagName) || event.key.length>1) return this.scanText = "" let temp = this.keyUpIntervalArray // 校验输入 let pattern = /d|s|[a-zA-Z]/ if(pattern.test(event.key))this.scanText += event.key // 只存少量时间戳 temp = util.clone(temp.splice(temp.length-5,5)) temp.push(event.timeStamp) this.keyUpIntervalArray = temp // 当储存的小于按键事件时间戳小于 2 无法对比,则不做判断 if(temp.length<2) return for(let i in temp){ let num = Math.ceil(temp[temp.length-1]) - Math.ceil(temp[temp.length-2]) if(num < 20 && num !=0) this.$refs['selectGoods']?.focus(this.scanText) } } } }
结语
本文章是为个人的思路及实现的实践总结,代码并不严谨优雅,希望对各位开发者有所启发,若有不足还望多多指教。