最近遇到一个使用扫码枪输入的问题,本来是使用 Tauri 来开发成 PC 软件,但是因为页面是使用 Vue 3.0 开发,所以本质是个前端问题,遇到的问题主要就是避免人为键盘误触、避免中文输入法的干扰。

扫码枪的输入流程

目前市面上的扫码枪,都是扫码枪内置解码功能,所以输入流程大致是这样:

  1. 扫码枪扫码
  2. 扫码枪解码一维码/二维码
  3. 扫码枪模拟键盘输入
  4. 键盘输入的内容自动展示到输入区域

这里要解决的问题主要就是两个:

  • 如果用户把输入法设置为中文输入法,如何避免异常?
  • 如何区分是扫码枪输入还是人为输入?

解决方案

查询目前的解决方案,主要有以下几种:

ime-mode属性

部分网上方案提示可以使用css的ime-mode属性,主要有 5 个值:

  • auto: 不更改现在输入法的状态,默认值。
  • normal: 输入法状态应该为正常,此值可在css中用于覆盖页面的设置。
  • active: 指定所有使用ime输入的字符。即激活本地语言输入法。用户仍可以撤销激活ime
  • inactive: 指定所有不使用ime输入的字符。即激活非本地语言。用户仍可以撤销激活ime
  • disabled: 完全禁用ime。对于有焦点的控件(如输入框),用户不可以激活ime。

所以当某个 input 文本域不需要输入中文的时候,就可以把ime-mode设置为inactivedisabled

但是该属性大部分浏览器都不支持,所以基本可以不用考虑。

password方案

当把input的类型设置为password,并把focus到输入框,确实输入法会强制切换为英文模式。

但是此种方式输入的内容不可见,还可能会触发浏览器的自动填充功能,并且表单提交后,还会提示是否要保存密码。

<template>
    <input id="code-input" type="password" autocomplete="off" autofocus />
</template>

<script setup lang="ts">
    import { onMounted } from 'vue';

    const focusInput = () => {
        window.setTimeout(function () {
            document.getElementById('code-input')?.focus();
        }, 0);
    }

    onMounted(() => {
        focusInput();
    })
</script>

监听键盘输入事件获取输入

直接不放input框,通过监听键盘输入,来获取输入内容。

考虑到扫码枪输入很快,我们可以判断键盘输入间隔,如果小于 50ms,就认为是扫码枪输入,否则为人为输入。

没有使用keyup事件是因为无法区分大小写状态。

<script setup lang="ts">
import { ref, onMounted } from 'vue';

const code = ref('');

interface TimeDiff {
    lastTime: number; // 上一次按键时间
    nextTime: number; // 这一次按键时间
}

let timeDiff: TimeDiff = {
    lastTime: 0,
    nextTime: 0,
};

const keyPressEvent = async (e: KeyboardEvent) => {
    const keyCode = e.code;
    const key = e.key;
    // ascii 码可见字符范围为 33-126(不包含空格)
    const minAsciiCode = 33;
    const maxAsciiCode = 126;
    timeDiff.nextTime = new Date().getTime();
    const diff = timeDiff.nextTime - timeDiff.lastTime;
    const machineDiff = 50;
    // 如果两次键盘事件的间隔事件超出指定值,就认为是人为输入,直接清空,且不响应
    if (diff > machineDiff) {
        code.value = "";
        timeDiff.lastTime = 0;
    }
    if (timeDiff.lastTime === 0 || diff < machineDiff) {
        // 如果在可见字符范围内,那就拼接数据
        if (key.length === 1 && key.charCodeAt(0) >= minAsciiCode && key.charCodeAt(0) <= maxAsciiCode) {
            vode.value += key;
            timeDiff.lastTime = timeDiff.nextTime;
        }
        if (keyCode === 'Enter' && diff < machineDiff) {
            
            // 这里可以进行数据的提交
            // ...

            code.value = "";
            timeDiff = {
                lastTime: 0,
                nextTime: 0,
            };
        }
    }
};

onMounted(() => {
    window.addEventListener('keypress', keyPressEvent);
})
</script>