浏览器扫一扫功能,PC端已测试成功,移动端需要https没测试
1.html5-qrcode,安装 npm i html5-qrcode
<template>
<div style="height: 100%; width: 100%">
<MyHeader :name="'调用摄像头扫码'" left="arrow-left" @goBackEv="$emit('goBack')" />
<div class="qrcode">
<div id="reader"></div>
</div>
</div>
</template>
<script>
import {
Html5Qrcode } from "html5-qrcode"
export default {
components: {
Html5Qrcode },
data() {
return {
html5QrCode: null
}
},
created() {
this.getCameras()
},
beforeDestroy() {
if (this.html5QrCode) this.stop()
},
methods: {
getCameras() {
Html5Qrcode.getCameras()
.then((devices) => {
if (devices && devices.length) {
this.html5QrCode = new Html5Qrcode("reader")
this.start()
}
})
.catch((err) => {
// handle err
this.html5QrCode = new Html5Qrcode("reader")
this.tools.errorPrompt('您需要授予相机访问权限')
})
},
start() {
this.html5QrCode
.start(
// environment后置摄像头 user前置摄像头
{
facingMode: "environment" },
{
fps: 2, // 可选,每秒帧扫描二维码
qrbox: {
width: 250, height: 250 }, // 可选,如果你想要有界框UI
// aspectRatio: 1.777778 // 可选,视频馈送需要的纵横比,(4:3--1.333334, 16:9--1.777778, 1:1--1.0)传递错误的纵横比会导致视频不显示
},
(decodedText, decodedResult) => {
// do something when code is read
console.log('decodedText', decodedText);
console.log('decodedResult', decodedResult);
this.$emit("goBack", decodedText)
}
)
.catch((err) => {
console.log('扫码错误信息', err);
// 错误信息处理仅供参考,具体情况看输出!!!
if (typeof err == 'string') {
this.tools.errorPrompt(err)
} else {
if (err.name == 'NotAllowedError') return this.tools.errorPrompt("您需要授予相机访问权限")
if (err.name == 'NotFoundError') return this.tools.errorPrompt('这个设备上没有摄像头')
if (err.name == 'NotSupportedError') return this.tools.errorPrompt('摄像头访问只支持在安全的上下文中,如https或localhost')
if (err.name == 'NotReadableError') return this.tools.errorPrompt('相机被占用')
if (err.name == 'OverconstrainedError') return this.tools.errorPrompt('安装摄像头不合适')
if (err.name == 'StreamApiNotSupportedError') return this.tools.errorPrompt('此浏览器不支持流API')
}
})
},
stop() {
this.html5QrCode.stop().then((ignore) => {
// QR Code scanning is stopped.
console.log("QR Code scanning stopped.")
})
.catch((err) => {
// Stop failed, handle it.
console.log("Unable to stop scanning.")
})
},
}
}
</script>
<style lang="less" scoped>
.qrcode {
position: relative;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.1);
}
#reader {
top: 20%;
left: 0;
}
</style>
2.vue-qrcode-reader 安装 npm install --save vue-qecode-reader
<template>
<div style="height: 100%; width: 100%">
<MyHeader :name="'扫码'" left="arrow-left" @goBackEv="$emit('goBack')" />
<qrcode-stream
:key="_uid"
:track="this.paintBoundingBox"
@decode="onDecode"
@init="onInit"
/>
</div>
</template>
<script>
import {
QrcodeStream } from 'vue-qrcode-reader'
export default {
components: {
QrcodeStream },
name:'qrCodeReader',
methods: {
onDecode(result) {
console.log('扫码结果', result)
this.$emit('goBack', result)
},
async onInit(promise) {
try {
await promise
} catch (error) {
if (error.name === 'NotAllowedError') {
// ERROR: you need to grant camera access permission
this.tools.errorPrompt("您需要授予摄像机访问权限")
} else if (error.name === 'NotFoundError') {
// ERROR: no camera on this device
this.tools.errorPrompt("这个设备上没有摄像头")
} else if (error.name === 'NotSupportedError') {
// ERROR: secure context required (HTTPS, localhost)
this.tools.errorPrompt("需要安全上下文(HTTPS, localhost)")
} else if (error.name === 'NotReadableError') {
// ERROR: is the camera already in use?
this.tools.errorPrompt("摄像机已经在使用了吗?")
} else if (error.name === 'OverconstrainedError') {
// ERROR: installed cameras are not suitable
this.tools.errorPrompt("安装的摄像头不合适")
} else if (error.name === 'StreamApiNotSupportedError') {
// ERROR: Stream API is not supported in this browser
this.tools.errorPrompt("此浏览器不支持流API")
} else if (error.name === 'InsecureContextError') {
// ERROR: Camera access is only permitted in secure context.
// Use HTTPS or localhost rather than HTTP.
this.tools.errorPrompt(`摄像头只能在安全环境下使用。使用HTTPS或本地主机,而不是HTTP`)
} else {
this.tools.errorPrompt(`ERROR: Camera error (${
error.name})`)
}
}
},
/** 追踪二维码的样式
* 注意: 避免访问这个函数中的变量、计算属性以及vuex,该函数是每秒调用,会造成内存泄漏
* detectedCodes:位置对象
* ctx:CanvasRenderingContext2D实例
*/
paintBoundingBox(detectedCodes, ctx) {
for (const detectedCode of detectedCodes) {
const {
boundingBox: {
x, y, width, height } } = detectedCode
ctx.lineWidth = 2
ctx.strokeStyle = '#007bff'
ctx.strokeRect(x, y, width, height)
}
}
}
}
</script>
<style>
</style>
3.@zxing/library 安装npm i @zxing/library --save
<template>
<div class="page-scan">
<!--返回-->
<van-nav-bar title="扫描二维码/条形码" fixed
@click-left="clickIndexLeft()"
class="scan-index-bar">
<template #left>
<van-icon name="arrow-left" size="18" color="#fff"/>
<span style="color: #fff"> 取消 </span>
</template>
</van-nav-bar>
<!-- 扫码区域 -->
<video ref="video" id="video" class="scan-video" autoplay></video>
<!-- 提示语 -->
<div v-show="tipShow" class="scan-tip"> {
{
tipMsg}} </div>
</div>
</template>
<script>
import {
BrowserMultiFormatReader } from '@zxing/library';
export default {
name: 'scanCodePage',
data() {
return {
loadingShow: false,
codeReader: null,
scanText: '',
vin: null,
tipMsg: '正在尝试识别....',
tipShow: false
}
},
created() {
this.codeReader = new BrowserMultiFormatReader();
if (window.stream) {
window.stream.getTracks().forEach(track => {
track.stop();
});
}
this.openScan();
},
destroyed(){
this.codeReader.reset();
},
watch: {
'$route'(to, from) {
if(to.path == '/scanCodePage'){
this.codeReader = new BrowserMultiFormatReader();
if (window.stream) {
window.stream.getTracks().forEach(track => {
track.stop();
});
}
this.openScanTwo();
}
}
},
methods: {
async openScan() {
this.codeReader.getVideoInputDevices().then((videoInputDevices) => {
this.tipShow = true;
this.tipMsg = '正在调用摄像头...';
console.log('videoInputDevices', videoInputDevices);
// 默认获取第一个摄像头设备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;
}
}
this.tools.errorPrompt(0+firstDeviceId);
this.decodeFromInputVideoFunc(firstDeviceId);
}).catch(err => {
this.tipShow = false;
this.tools.errorPrompt(0+err);
});
},
async openScanTwo() {
this.codeReader = await new BrowserMultiFormatReader();
this.codeReader.getVideoInputDevices().then((videoInputDevices) => {
this.tipShow = true;
this.tipMsg = '正在调用摄像头...';
this.tools.errorPrompt('videoInputDevices', videoInputDevices);
// 默认获取第一个摄像头设备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;
}
}
this.decodeFromInputVideoFunc(firstDeviceId);
}).catch(err => {
this.tipShow = false;
this.tools.errorPrompt(1+err);
});
},
decodeFromInputVideoFunc(firstDeviceId) {
this.codeReader.reset(); // 重置
this.scanText = '';
this.codeReader.decodeFromInputVideoDeviceContinuously(firstDeviceId, 'video', (result, err) => {
this.tipMsg = '正在尝试识别...';
this.scanText = '';
if (result) {
console.log('扫描结果', result);
this.scanText = result.text;
if (this.scanText) {
this.tipShow = false;
this.$emit("getScanResult", this.scanText);
// 这部分接下去的代码根据需要,读者自行编写了
// this.$store.commit('app/SET_SCANTEXT', result.text);
// console.log('已扫描的小票列表', this.$store.getters.scanTextArr);
}
}
if (err && !(err)) {
this.tipMsg = '识别失败';
setTimeout(() => {
this.tipShow = false;
}, 2000)
this.tools.errorPrompt(err);
}
});
},
clickIndexLeft(){
// 返回上一页
this.codeReader = null;
this.$destroy();
this.$router.back();
}
}
}
</script>
<style lang="scss">
.scan-index-bar{
background-image: linear-gradient( -45deg, #42a5ff ,#59cfff);
}
.van-nav-bar__title{
color: #fff !important;
}
.scan-video{
height: 80vh;
}
.scan-tip{
width: 100vw;
text-align: center;
margin-bottom: 10vh;
color: white;
font-size: 5vw;
}
.page-scan{
overflow-y: hidden;
background-color: #363636;
}
</style>