实践背景
在近期工作过程中接手了一个让我有些棘手的需求,需求如下:
- 输入框内支持扫码录入商品,且支持连续扫码录入。
- 相同输入框中支持手动输入条码录入商品。
- 页面任意位置用扫码枪扫码都可以成功定位到该输入框且录入对应商品。
需要解决的问题
- 事件注册在那里
- 事件注册什么时候注册,什么时候注销。
- 如何不影响页面上其他input元素
- 如何区分手动输入和扫码枪录入
代码实现
- 事件注册什么时候注册,什么时候注销
因为我们要注册的为全局事件,但是又不能影响到其他页面所以我们可以借助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(){}
}
}
- 事件注册在那里,注册什么事件,用什么方式注册
(1) 因为我们要在页面任意位置识别并聚焦到固定的input中,所以我们需要把事件放到document上面
(2) 因为keydown事件按下不抬起会一直触发,所以这里我们采用keyup事件更加友好。
(3) 我们这里使用了addEventListener注册事件,removeEventListener来删除事件。这两个方法有三个参数,分别为 (“事件名”,“事件触发的函数”,“采用捕获还是冒泡”) 。这里我就不做赘述了,详细说明查看下面文档:官方文档 或者 相关博客例如:https://blog.youkuaiyun.com/l908825925/article/details/107865676
PS: 此处需注意一个细节,添加事件和删除事件的所有参数必须一致,否则会导致无法删除
methods:{
// 添加事件
addKeyUp(){
document.addEventListener("keyup",this.documentKeyUp,true)
}
// 删除事件
removeKeyUp(){
document.removeEventListener("keyup",this.documentKeyUp,true)
}
}
- 如何不影响页面上其他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
}
}
- 如何区分手动输入和扫码枪录入 (最为关键的部分)
问题分析:
这个问题我们需要从硬件去分析,首先,扫码抢输入特点为快速录入自动在输入结束触发回车,人为按键输入相对扫码枪输入较慢,手动触发回车事件。那么我们从哪儿来拿到这个时间呢,通过分析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)
}
}
}
}
PS: 补充内容:
1、中文输入法扫码枪扫码会导致很多键位识别为229。导致无法识别设备回车。
2、部分老式扫码枪在中文大写模式下录入完成最后一个键位code不是13(回车),而是20(Caps_Lock, 大小写切换)
结语
该文章是为个人的思路及实现的实践总结,代码并不严谨优雅,希望对各位开发者有所启发,若有不足还望多多指教。