加载中...

AliveSevenの博客

Vue3实现H5扫描二维码+条形码(在Http环境下也可以)

发表于: 2024-02-19 |

更新于: 2024-02-19 |

字数统计: 8675 |

阅读量: 0

前言:新年接到公司的第一个需求就是要写一个H5,里面类似电商那种扫描枪之类的,用H5扫条形码拿到其中的信息,初步实现是想搞简单一点,直接调H5的相机,把拍到的照片传给后端做解析,但是这样做效果并不好,后来还是决定让前端来做扫描。

H5直接调用摄像头

  • 代码如下(类名是Tailwind CSS)
html 复制代码
<div class="flex relative w-27 h-27">
    <input type="file" accept="image/*" capture="user" class="w-27 h-27 z-10 opacity-0">
    <van-icon class="cursor-pointer scanCode" size="27" style="font-weight: bold;" name="scan" @click="scanCode" />
</div>
  • 主要实现功能的部分还是下面这一段

    html 复制代码
    <input type="file" accept="image/*" capture="user" class="w-27 h-27 z-10 opacity-0">```
  • 解释:通过将其设为0的透明度,配合van-icon的那个scan图标,将其叠加在图标上面,可以实现点击图标,拉起H5的相机。

    • 这样做的效果可以实现拍照,但是并不能实现二维码或者是条形码Bar Code的信息扫描
    • 拍照之后需要将拍到的照片发给后端进行解析
    • 虽然效果不好,但是这样做的话前端是实现是最简单的。

通过三方库实现条形码扫描

  1. 这里我有用到两个三方库,一个是html5-qrcode,而另外一个是@zxing/library
  2. html5-qrcode的效果整体还是不如@zxing/library,而且相关实现的代码,也是@zxing/library比较多,去网上搜的话,所以这里我还是用@zxing/library用于实现该功能

开发环境:Vue3.3 + Vite5 + Vant + zxing/library

安装zxing/library

复制代码
npm install @zxing/library --save

pnpm install @zxing/library --save

yarn add @zxing/library
  • 参考代码:
html 复制代码
<template>
    <div class="page-scan">
        <!--返回-->
        <van-nav-bar :title="$t('title.scanBarCode')" fixed @click-left="clickIndexLeft()" class="scan-index-bar">
            <template #left>
                <van-icon name="arrow-left" size="18" color="#fff" />
                <span style="color: #fff"> {{ $t('text.cancel') }} </span>
            </template>
        </van-nav-bar>
        <!-- 扫码区域 -->
        <video ref="video" id="video" class="scan-video" autoplay></video>
        <!-- 提示语 -->
        <div v-show="tipShow" class="scan-tip"> {{ tipMsg }} </div>
        <!-- 掃碼Text -->
        <div v-show="!tipShow" class="scan-tip"> {{ scanText }} </div>
    </div>
</template>

<script lang="ts" setup>
import { ref, onUnmounted, onBeforeMount } from 'vue';
import { BrowserMultiFormatReader } from '@zxing/library';
import { useI18n } from 'vue-i18n';
import router from '@/router';

const { t } = useI18n();

const scanText = ref()
const decodeFromInputVideoFunc = (firstDeviceId) => {
    codeReader.value.reset(); // 重置
    scanText.value = '';
    codeReader.value.decodeFromInputVideoDeviceContinuously(firstDeviceId, 'video', (result: any, err: string) => {
        tipMsg.value = t('hint.tryToScan');
        scanText.value = '';
        if (result) {
            console.log('扫描结果', result);
            scanText.value = result.text;
            if (scanText.value) {
                tipShow.value = false;
                // 这部分接下去的代码根据需要,读者自行编写了
                // this.$store.commit('app/SET_SCANTEXT', result.text);
                // console.log('已扫描的小票列表', this.$store.getters.scanTextArr);
            }
        }
        if (err && !(err)) {
            tipMsg.value = t('hint.scanFail');
            setTimeout(() => {
                tipShow.value = false;
            }, 2000)
            console.error(err);
        }
    });
}

const tipMsg = ref(t('hint.callingCamera'))
const tipShow = ref(false)
const codeReader: any = ref(null);
const openScan = async () => {
    console.log('codeReader', codeReader.value)
    codeReader.value.getVideoInputDevices().then((videoInputDevices: any) => {
        tipShow.value = true;
        tipMsg.value = t('hint.callingCamera');
        // 默认获取第一个摄像头设备id
        let firstDeviceId = videoInputDevices[0].deviceId;
        // 获取第一个摄像头设备的名称
        const videoInputDeviceslablestr = JSON.stringify(videoInputDevices[0].label);
        if (videoInputDevices.length > 1) {
            // 判断是否后置摄像头
            if (videoInputDeviceslablestr.indexOf('back') > -1) {
                firstDeviceId = videoInputDevices[0].deviceId;
            } else {
                firstDeviceId = videoInputDevices[1].deviceId;
            }
        }
        decodeFromInputVideoFunc(firstDeviceId);
    }).catch((err: string) => {
        tipShow.value = false;
        console.error(err);
    });
}

const openScanTwo = async () => {
    codeReader.value = await new BrowserMultiFormatReader();
    codeReader.value.getVideoInputDevices().then((videoInputDevices) => {
        tipShow.value = true;
        tipMsg.value = t('hint.callingCamera');
        // 默认获取第一个摄像头设备id
        let firstDeviceId = videoInputDevices[0].deviceId;
        // 获取第一个摄像头设备的名称
        const videoInputDeviceslablestr = JSON.stringify(videoInputDevices[0].label);
        if (videoInputDevices.length > 1) {
            // 判断是否后置摄像头
            if (videoInputDeviceslablestr.indexOf('back') > -1) {
                firstDeviceId = videoInputDevices[0].deviceId;
            } else {
                firstDeviceId = videoInputDevices[1].deviceId;
            }
        }
        decodeFromInputVideoFunc(firstDeviceId);
    }).catch((err: string) => {
        tipShow.value = false;
        console.error(err);
    });
}

const clickIndexLeft = () => {  // 返回上一页
    codeReader.value = null;
    router.back();
}

onBeforeMount(() => {
    codeReader.value = new BrowserMultiFormatReader();
    openScan();
})

onUnmounted(() => {
    // codeReader.value.reset();
    console.log("销毁组件");
})
</script>

<style lang="scss" scoped>
.scan-index-bar {
    background-image: linear-gradient(-45deg, #42a5ff, #59cfff);
}

.van-nav-bar__title {
    color: #fff !important;
}

.scan-video {
    display: flex;
    flex: 1;
}

.scan-tip {
    margin: 10px 0;
    width: 100%;
    text-align: center;
    color: white;
    font-size: 5vw;
}

.page-scan {
    display: flex;
    flex-direction: column;
    overflow-y: hidden;
    background-color: #363636;
}
</style>

上面这里有用到i18n的国际化,这里补上相关国际化的文本

javascript 复制代码
hint: {
    inputWaybillNum: '請輸入運單號',
    inputGoodsSearch: '請輸入商品名稱/代碼',
    copySuc: '複製成功',
    copyFail: '複製失敗',
    addFailCaseByLeftFull: '新增失敗,倉庫暫無剩餘了',
    tryToScan: '正在嘗試掃描...',
    scanFail: '掃描識別失敗',
    callingCamera: '正在調用攝像頭...',
    ScanResults: '掃描結果'
 }

实现环境,重点部分

  1. 上面这段代码只能在localhost或者是https环境下执行,但是一般我们本地调试的话,都是通过内网,电脑和手机连同一个WiFi或者局域网,实现手机真机调试H5。
  2. 局域网一般都是http协议为多,所以要调试的话需要对浏览器进行安全性白名单放行和调整。
  3. 解决方案也不难,可以参考一下https://blog.csdn.net/qq_40905132/article/details/126520190
  4. 简单来说就是浏览器输入: chrome://flags/ ,然后Ctrl + F 查:Insecure origins treated as secure
  5. 查到之后在输入框把要白名单的http链接输入之后,按右边的按钮改为Enabled就行了

另外一种方案

  1. 第二种方案可以用html5-qrcode来实现,但是实现效果貌似没有第一种好。
  2. 首先安装html5-qrcode
复制代码
npm install html5-qrcode --save

pnpm install html5-qrcode --save

yarn add html5-qrcode
  1. 然后是参考代码
html 复制代码
<template>
  <div class="container">
    <div id="reader"></div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import { Html5Qrcode } from 'html5-qrcode';
import { Html5QrcodeResult, CameraDevice } from '../../../type';

let cameraId = ref('');
let devicesInfo = ref<any>('');
let html5QrCode: any = ref<any>(null);
const router = useRouter();

onMounted(() => {
  getCameras();
});

onUnmounted(() => {
  stop();
});

const getCameras = () => {
  Html5Qrcode.getCameras()
    .then((devices: CameraDevice[]) => {
      console.log('摄像头信息', devices);
      if (devices && devices.length) {
        // 如果有2个摄像头,1为前置的
        if (devices.length > 1) {
          cameraId.value = devices[1].id;
        } else {
          cameraId.value = devices[0].id;
        }
        devicesInfo.value = devices;
        // start开始扫描
        start();
      }
    })
    .catch((err) => {
      // handle err
      console.log('获取设备信息失败', err); // 获取设备信息失败
    });
};
const start = () => {
  html5QrCode = new Html5Qrcode('reader');
  html5QrCode
    .start(
      cameraId.value, // retreived in the previous step.
      {
        fps: 10, // 设置每秒多少帧
        qrbox: { width: 250, height: 250 }, // 设置取景范围
        // scannable, rest shaded.
      },
      (decodedText: string, decodedResult: Html5QrcodeResult) => {
        console.log('扫描的结果', decodedText, decodedResult);
      },
      (errorMessage: any) => {
        console.log('暂无额扫描结果', errorMessage);
      }
    )
    .catch((err: any) => {
      console.log(`Unable to start scanning, error: ${err}`);
    });
};
const stop = () => {
  html5QrCode
    .stop()
    .then((ignore: any) => {
      // QR Code scanning is stopped.
      console.log('QR Code scanning stopped.', ignore);
    })
    .catch((err: any) => {
      // Stop failed, handle it.
      console.log('Unable to stop scanning.', err);
    });
};
</script>

<style lang="scss" scoped>
.container {
	position: relative;
	height: 100%;
	width: 100%;
    max-width: 100%;
  	background: rgba($color: #000000, $alpha: 0.48);
}
#reader {
  top: 50%;
  left: 0;
  transform: translateY(-50%);
}
</style>
AliveSeven
一个有趣的博客
文章
0
标签
0
分类
0
目录
©2021 - 2025 By AliveSeven
欢迎来到我的博客,谢谢你能在茫茫人海中发现我