Vue + 扫码枪接入

5,199 阅读2分钟

扫码枪扫码原理

扫码枪会将扫描到的数据代入到获取焦点的输入框中,并且触发输入框的“Enter”事件

1、中文输入法扫码枪扫码会导致很多键位识别为229。导致无法识别设备回车。

2、部分老式扫码枪在中文大写模式下录入完成最后一个键位code不是13(回车),而是20(Caps_Lock, 大小写切换)

扫码功能实现

代码示例是实际项目中使用的扫码枪功能,router 只是用作扫码回调函数的函数名

核心是监听 keypress 事件

有其他实现是使用 keyup 或者 keydown

import Vue from 'vue'
import router from '@/router/routers'

// 触发器总集合
const SCAN_LISTENER_MANAGER = []

function execFunc(listener, param) {
  listener.callback.call(listener.thisArg, param)
}

const scanListener = {

  /**
   * 添加监听器
   * @param {string} name
   * @param {Function|*} listener
   */
  on(name, listener) {
    if (!name || !isNaN(parseInt(name))) { throw TypeError('监听器名称只能为英文字母以及下划线!') }

    // 判断当前监听器是否存在,不存在则直接创建一个空数组
    if (SCAN_LISTENER_MANAGER[name] === undefined) { SCAN_LISTENER_MANAGER[name] = [] }

    if (typeof listener !== 'object') {
      listener = { callback: listener }
    }

    if (typeof listener.callback !== 'function') {
      throw TypeError('监听器必须是一个function!')
    }

    SCAN_LISTENER_MANAGER[name].push(listener)
    return listener
  },

  /**
   * 添加监听器 - 只执行一次
   * @param {string} name
   * @param {Function|*} listener
   */
  once(name, listener) {
    if (typeof listener !== 'object') {
      listener = { callback: listener }
    }
    listener.once = true
    return this.on(name, listener)
  },

  /**
   * 移除监听器
   * @param {string} name
   * @param {Function} listener
   */
  off(name, listener) {
    if (!(typeof listener === 'function' || typeof listener === 'object')) return

    // 处理器
    const handler = (listeners, listener) => {
      if (typeof listener === 'object') {
        const index = listeners.indexOf(listener)
        if (index !== -1) listeners.splice(index, 1)
      } else if (typeof listener === 'function') {
        for (const i in listeners) {
          if (listeners[i].callback == listener) listeners.splice(i, 1)
        }
      }
    }

    if (!name || !isNaN(parseInt(name))) {
      // 所有监听器都移除这个回调函数
      SCAN_LISTENER_MANAGER.forEach(listeners => handler(listeners, listener))
    } else {
      handler(SCAN_LISTENER_MANAGER[name] || [], listener)
    }
  },

  /**
   * 移除监听器, 移除指定事件名称下的所有回调方法
   * @param {string} name
   */
  offAll(name) {
    name &&
    SCAN_LISTENER_MANAGER[name] &&
    !SCAN_LISTENER_MANAGER[name].length &&
    (SCAN_LISTENER_MANAGER[name] = [])
  },

  /**
   * 触发监听器
   * @param {string} name
   * @param {*} [param]
   */
  emit(name, param) {
    if (!name || !isNaN(parseInt(name))) { throw TypeError('监听器名称只能为英文字母以及下划线!') }

    const listeners = SCAN_LISTENER_MANAGER[name] || []
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      if (listener.once) {
        listeners.splice(i, 1)
        i--
      }
      execFunc(listener, param)
    }
  }

}

const scanMixin = {
  /* beforeDestroy() {
    // 当前组件销毁时,也移除扫码事件
    scanListener.offAll(router.currentRoute.path)
  },*/
  methods: {
    // 移除扫码事件方法
    $offScanEvent(listener) {
      scanListener.off(this.$route.path, listener)
    },
    // 监听扫码事件
    $onScanEvent(listener) {
      const path = this.$route.path
      const _listener = scanListener.on(path, listener)
      this.$once('hook:beforeDestroy', function() {
        scanListener.off(path, _listener)
      })
    }
  }
}
// 扫码监听器全局混入
Vue.mixin(scanMixin)

核心

/*
* 监听整个文档的keypress事件,识别键盘输入和扫码枪输入
* 扫码枪输入在回车符号后,向当前路由名称emit事件
* */
let lastTimeStamp = null // 上一次记录的时间戳
let codeString = '' // 记录内容
let isScanInput = false // 是否处在扫码枪模式
document.addEventListener('keypress', function(e) {
  const timeStamp = e.timeStamp
  // 第一次记录字符
  if (lastTimeStamp == null) {
    lastTimeStamp = timeStamp
  }
  const diffTime = timeStamp - lastTimeStamp // 时间间隔
  const isNormalDiffTime = diffTime < 50 // 是否在 正常的扫码枪输入的时间间隔 范围之内
  // 是否 进入扫码模式 且在允许的时间间隔 范围之内
  const isScanDiffTime = isScanInput && !isNormalDiffTime && diffTime < 1000
  // 距上次记录间隔小于指定时间,或者 如果是扫码枪模式,允许一次较大时间
  if (isNormalDiffTime || isScanDiffTime) {
    if (isScanDiffTime) {
      isScanInput = false
    }
    // 如果是回车
    if (e.keyCode == '13') {
      // 有内容,则发送事件, 没有则跳出
      if (!codeString) return
      // 提取出记录到的内容
      const Result = codeString
      codeString = ''
      // 触发事件
      setTimeout(function() {
        scanListener.emit(router.currentRoute.path, Result)
      }, 0)
    } else {
      // 小于100ms认为接下来是扫码枪模式
      if (isNormalDiffTime) isScanInput = true
      // 则堆加入内容
      codeString += String.fromCharCode(e.keyCode)
    }
  } else {
    isScanInput = false
    if (e.keyCode != '13') codeString = String.fromCharCode(e.keyCode)
  }
  // 记录当前输入的时间
  lastTimeStamp = timeStamp
})

使用

mounted() {
    this.$onScanEvent(this.scanCode)
},
methods: {
    scanCode(code) {
        console.log('扫码结果:', code)
    }
}