Browse Source
2.开发基于zxing/library的既支持扫二维码也支持扫条码的扫码组件 3.因为自研zxing组件小米手机中的企业微信只能获取到前置摄像头,实现按需加载功能,企业微信环境调用企业微信扫一扫,其它环境调用自研zxing扫码组件lwx_v8
8 changed files with 965 additions and 480 deletions
@ -1,212 +1,184 @@ |
|||||
<template> |
<template> |
||||
<div class="scanCode"> |
<div style="height: 100vh"> |
||||
<div class="container"> |
<qrcode-stream @detect="onDecode" @error="onInit" style="height: 100%"> |
||||
<div class="qrcode"> |
<div> |
||||
|
<div class="qr-scanner"> |
||||
|
<!--顶部左边的返回箭头--> |
||||
|
<div> |
||||
|
<!-- <van-icon class="scanImg" @click="onClickLeft" name="arrow-left" /> --> |
||||
|
<!-- <el-button type="primary" @click="onClickLeft" ></el-button> --> |
||||
|
<el-icon @click="onClickLeft" style="margin:15px;width: 20px;height: 20px;"><Close style="width: 20px;height: 20px;" /></el-icon> |
||||
|
|
||||
|
|
||||
<div class="btn"> |
|
||||
<div class="left-back" style="float: left;"> |
|
||||
|
|
||||
<el-icon @click="clickBack" style="border: white solid 0px;"> |
|
||||
<Back /> |
|
||||
</el-icon> |
|
||||
</div> |
</div> |
||||
<!-- <div class="right-file"> |
<!--中间的扫码框--> |
||||
|
<div class="box"> |
||||
<van-uploader |
<div class="line"></div> |
||||
v-model="fileList" |
<div class="angle"></div> |
||||
:preview-image="false" |
|
||||
:after-read="dealSelectFiles" |
|
||||
> |
|
||||
<el-icon><Picture /></el-icon> |
|
||||
</van-uploader> |
|
||||
</div> --> |
|
||||
</div> |
|
||||
|
|
||||
|
|
||||
<div id="reader"></div> |
|
||||
</div> |
</div> |
||||
|
<div class="txt"> |
||||
|
将二维码/条码放入框内,即自动扫描 |
||||
</div> |
</div> |
||||
<!--<div class="btn"> |
|
||||
<div class="left-back"> |
|
||||
|
|
||||
<el-icon @click="clickBack"> |
|
||||
<Back /> |
|
||||
</el-icon> |
|
||||
</div> |
</div> |
||||
<div class="right-file"> |
|
||||
|
|
||||
<van-uploader |
|
||||
v-model="fileList" |
|
||||
:preview-image="false" |
|
||||
:after-read="dealSelectFiles" |
|
||||
> |
|
||||
<el-icon><Picture /></el-icon> |
|
||||
</van-uploader> |
|
||||
</div> |
</div> |
||||
</div> --> |
</qrcode-stream> |
||||
</div> |
</div> |
||||
</template> |
</template> |
||||
|
<script setup lang="ts"> |
||||
<script setup> |
import {QrcodeStream} from "vue-qrcode-reader"; |
||||
import { reactive } from "vue"; |
import {ref} from "vue"; |
||||
import { defineComponent, toRefs, onMounted, onUnmounted } from "vue"; |
|
||||
import { Html5Qrcode } from "html5-qrcode"; |
|
||||
import { useRouter } from 'vue-router' |
// 定义变量 |
||||
|
const dataList = ref('') |
||||
const router = useRouter() |
const result = ref(true) |
||||
|
const error = ref('') |
||||
const state = reactive({ |
|
||||
html5QrCode: null, |
|
||||
fileList: [], |
// 扫码后返回的结果 |
||||
}); |
const onDecode = (res: any) => { |
||||
const start = () => { |
dataList.value = res |
||||
state.html5QrCode |
result.value = false |
||||
.start( |
//console.log('你好',dataList.value) |
||||
{ facingMode: "environment" }, |
//alert(dataList.value) |
||||
{ |
//showSuccessToast('扫描成功')--------------------------------------- |
||||
fps: 5, |
window.location.href = dataList.value[0].rawValue; |
||||
qrbox: { width: 300, height: 210 }, |
// 调用后台接口存入数据库 |
||||
aspectRatio: 1.7,// |
// 数据存入数据库后跳转页面 |
||||
}, |
} |
||||
(decodedText, decodedResult) => { |
// 初始化摄像头 |
||||
window.location.href = decodedText; |
const onInit = async (promise: any) => { |
||||
if (state.html5QrCode&&state.html5QrCode.isScanning) { |
console.log('初始化摄像头',promise) |
||||
stop(); |
|
||||
} |
|
||||
console.log("decodedText", decodedText); |
|
||||
console.log("decodedResult", decodedResult); |
|
||||
} |
|
||||
) |
|
||||
.catch((err) => { |
|
||||
console.log("扫码错误信息", err); |
|
||||
let message = ""; // 错误信息处理仅供参考,具体描述自定义 |
|
||||
if (typeof err == "string") { |
|
||||
message = "二维码识别失败!"; |
|
||||
alert(message) |
|
||||
} else { |
|
||||
if (err.name == "NotAllowedError") { |
|
||||
message = "您需要授予相机访问权限!"; |
|
||||
} |
|
||||
if (err.name == "NotFoundError") { |
|
||||
message = "这个设备上没有摄像头!"; |
|
||||
} |
|
||||
if (err.name == "NotSupportedError") { |
|
||||
message = |
|
||||
"摄像头访问只支持在安全的上下文中,如https或localhost!"; |
|
||||
} |
|
||||
if (err.name == "NotReadableError") { |
|
||||
message = "相机被占用!"; |
|
||||
} |
|
||||
if (err.name == "OverconstrainedError") { |
|
||||
message = "安装摄像头不合适!"; |
|
||||
} |
|
||||
if (err.name == "StreamApiNotSupportedError") { |
|
||||
message = "此浏览器不支持流API!"; |
|
||||
} |
|
||||
alert(message) |
|
||||
} |
|
||||
}); |
|
||||
}; |
|
||||
const clickBack = () => { |
|
||||
//console.log(1) |
|
||||
if (state.html5QrCode&&state.html5QrCode.isScanning) { |
|
||||
stop(); |
|
||||
} |
|
||||
router.back(); |
|
||||
} |
|
||||
const getCameras = () => { |
|
||||
Html5Qrcode.getCameras() |
|
||||
.then((devices) => { |
|
||||
if (devices && devices.length) { |
|
||||
state.html5QrCode = new Html5Qrcode("reader"); |
|
||||
start(); |
|
||||
} |
|
||||
}) |
|
||||
.catch((err) => { |
|
||||
console.log(err) |
|
||||
alert("摄像头无访问权限!"); |
|
||||
}); |
|
||||
}; |
|
||||
const stop = () => { |
|
||||
state.html5QrCode |
|
||||
.stop() |
|
||||
.then((ignore) => { |
|
||||
console.log("停止扫码", ignore); |
|
||||
}) |
|
||||
.catch((err) => { |
|
||||
console.log(err); |
|
||||
alert("停止扫码失败"); |
|
||||
}); |
|
||||
}; |
|
||||
const dealSelectFiles = () => { |
|
||||
try { |
try { |
||||
window.qrcode.callback = (result) => { |
await promise |
||||
alert("成功了,结果是:" + result); |
}catch (err: any) { |
||||
}; // get select files. |
if (err.name === 'NotAllowedError') { |
||||
let file = state.fileList[0].file; |
error.value = 'ERROR: 您需要授予相机访问权限'; |
||||
var reader = new FileReader(); |
} else if (err.name === 'NotFoundError') { |
||||
reader.onload = (function () { |
error.value = 'ERROR: 这个设备上没有摄像头'; |
||||
return function (e) { |
} else if (err.name === 'NotSupportedError') { |
||||
window.qrcode.decode(e.target.result); |
error.value = 'ERROR: 所需的安全上下文(HTTPS、本地主机)'; |
||||
}; |
} else if (err.name === 'NotReadableError') { |
||||
})(file); |
error.value = 'ERROR: 相机被占用'; |
||||
reader.readAsDataURL(file); |
} else if (err.name === 'OverconstrainedError') { |
||||
} catch (error) { |
error.value = 'ERROR: 安装摄像头不合适'; |
||||
alert("图片识别失败!"); |
} else if (err.name === 'StreamApiNotSupportedError') { |
||||
} |
error.value = 'ERROR: 此浏览器不支持流API'; |
||||
}; |
} |
||||
onMounted(() => { |
} |
||||
getCameras(); |
|
||||
}); |
|
||||
onUnmounted(() => { |
|
||||
//扫描设备是否在运行 |
|
||||
if (state.html5QrCode&&state.html5QrCode.isScanning) { |
|
||||
stop(); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
||||
|
// 返回上一页箭头 |
||||
|
const onClickLeft = () => { |
||||
|
history.back(); |
||||
|
} |
||||
</script> |
</script> |
||||
|
<style scoped> |
||||
<style lang="scss" scoped> |
:deep(i.van-badge__wrapper.van-icon.van-icon-arrow-left.scanImg){ |
||||
.scanCode { |
font-size: 40px; |
||||
height: 100vh; |
color: white; |
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
background: rgba(0, 0, 0); |
|
||||
} |
} |
||||
.container { |
.scanImg{ |
||||
|
margin-top: 30px; |
||||
|
margin-left: 10px; |
||||
|
} |
||||
|
.error { |
||||
|
font-weight: bold; |
||||
|
color: red; |
||||
|
} |
||||
|
.cameraMessage { |
||||
|
width: 100%; |
||||
|
height: 60px; |
||||
|
} |
||||
|
.qr-scanner { |
||||
|
background-size: 3rem 3rem; |
||||
|
background-position: -1rem -1rem; |
||||
|
width: 100%; |
||||
|
/* height: 100%; */ |
||||
height: 100vh; |
height: 100vh; |
||||
|
/* height: 288px; */ |
||||
position: relative; |
position: relative; |
||||
|
background-color: #1110; |
||||
|
} |
||||
|
.qr-scanner .box { |
||||
|
width: 213px; |
||||
|
height: 213px; |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
top: 40%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
overflow: hidden; |
||||
|
border: 1px solid #3aa5ff; |
||||
|
} |
||||
|
.qr-scanner .txt { |
||||
width: 100%; |
width: 100%; |
||||
/* border: red solid 7px; */ |
height: 35px; |
||||
|
line-height: 35px; |
||||
|
font-size: 14px; |
||||
|
text-align: center; |
||||
|
/* color: #f9f9f9; */ |
||||
|
margin: 0 auto; |
||||
|
position: absolute; |
||||
|
top: 60%; |
||||
|
left: 0; |
||||
} |
} |
||||
.qrcode { |
.qr-scanner .myQrcode { |
||||
height: 90%; |
text-align: center; |
||||
/* width: 100%;*/ |
color: #3aa5ff; |
||||
/* border: white solid 7px; */ |
|
||||
} |
} |
||||
#reader { |
.qr-scanner .line { |
||||
|
height: calc(100% - 2px); |
||||
|
width: 100%; |
||||
|
background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #3aa5ff 211%); |
||||
|
border-bottom: 1px solid #3aa5ff; |
||||
|
transform: translateY(-100%); |
||||
|
animation: radar-beam 2s infinite alternate; |
||||
|
animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99); |
||||
|
animation-delay: 1.4s; |
||||
|
} |
||||
|
|
||||
|
.qr-scanner .box:after, |
||||
|
.qr-scanner .box:before, |
||||
|
.qr-scanner .angle:after, |
||||
|
.qr-scanner .angle:before { |
||||
|
content: ''; |
||||
display: block; |
display: block; |
||||
|
position: absolute; |
||||
|
width: 3vw; |
||||
|
height: 3vw; |
||||
|
} |
||||
|
|
||||
|
.qr-scanner .box:after, |
||||
|
.qr-scanner .box:before { |
||||
|
top: 0; |
||||
|
border-top-color: #3aa5ff; |
||||
|
} |
||||
|
|
||||
|
.qr-scanner .angle:after, |
||||
|
.qr-scanner .angle:before { |
||||
|
bottom: 0; |
||||
|
border-bottom-color: #3aa5ff; |
||||
|
} |
||||
|
|
||||
height: 90vh; |
.qr-scanner .box:before, |
||||
/* top: 50%; |
.qr-scanner .angle:before { |
||||
left: 0; |
left: 0; |
||||
transform: translateY(-50%); */ |
border-left-color: #3aa5ff; |
||||
/* border: yellow solid 5px; */ |
} |
||||
|
|
||||
|
.qr-scanner .box:after, |
||||
|
.qr-scanner .angle:after { |
||||
|
right: 0; |
||||
|
border-right-color: #3aa5ff; |
||||
} |
} |
||||
|
|
||||
.btn { |
@keyframes radar-beam { |
||||
flex: 1; |
0% { |
||||
padding-top: 2vw; |
transform: translateY(-100%); |
||||
display: flex; |
} |
||||
padding-left: 3vw; |
|
||||
color: #fff; |
100% { |
||||
font-size: 6vw; |
transform: translateY(0); |
||||
align-items: flex-start; |
} |
||||
/* border: green solid 7px; */ |
|
||||
} |
} |
||||
</style> |
</style> |
||||
|
|
||||
@ -0,0 +1,195 @@ |
|||||
|
<template> |
||||
|
<div style="height: 100vh"> |
||||
|
<qrcode-stream @detect="onDecode" @error="onInit" style="height: 100%"> |
||||
|
<div> |
||||
|
<div class="qr-scanner"> |
||||
|
<!--顶部左边的返回箭头--> |
||||
|
<div> |
||||
|
<el-icon @click="onClickLeft" style="margin:15px;width: 20px;height: 20px;"> |
||||
|
<Close style="width: 20px;height: 20px;" /> |
||||
|
</el-icon> |
||||
|
</div> |
||||
|
<!--中间的扫码框--> |
||||
|
<div class="box"> |
||||
|
<div class="line"></div> |
||||
|
<div class="angle"></div> |
||||
|
</div> |
||||
|
<div class="txt"> |
||||
|
将二维码/条码放入框内,即自动扫描 |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</qrcode-stream> |
||||
|
</div> |
||||
|
</template> |
||||
|
<script setup lang="ts"> |
||||
|
import { QrcodeStream } from "vue-qrcode-reader"; |
||||
|
import { ref } from "vue"; |
||||
|
|
||||
|
const emits = defineEmits<{ |
||||
|
(e: 'update-qrScaning', value: any): void |
||||
|
(e: 'update-scanResult', value: any): void |
||||
|
}>() |
||||
|
// 定义变量 |
||||
|
const dataList = ref('') |
||||
|
const result = ref(true) |
||||
|
const error = ref('') |
||||
|
|
||||
|
|
||||
|
// 扫码后返回的结果 |
||||
|
const onDecode = (res: any) => { |
||||
|
dataList.value = res |
||||
|
result.value = false |
||||
|
//console.log('你好',dataList.value) |
||||
|
//alert(dataList.value) |
||||
|
//showSuccessToast('扫描成功')--------------------------------------- |
||||
|
//console.log('扫描成功'+dataList.value[0].rawValue) |
||||
|
emits('update-scanResult', dataList.value[0].rawValue) // 触发父组件事件 |
||||
|
// 调用后台接口存入数据库 |
||||
|
// 数据存入数据库后跳转页面 |
||||
|
} |
||||
|
// 初始化摄像头 |
||||
|
const onInit = async (promise: any) => { |
||||
|
console.log('初始化摄像头', promise) |
||||
|
try { |
||||
|
await promise |
||||
|
} catch (err: any) { |
||||
|
if (err.name === 'NotAllowedError') { |
||||
|
error.value = 'ERROR: 您需要授予相机访问权限'; |
||||
|
} else if (err.name === 'NotFoundError') { |
||||
|
error.value = 'ERROR: 这个设备上没有摄像头'; |
||||
|
} else if (err.name === 'NotSupportedError') { |
||||
|
error.value = 'ERROR: 所需的安全上下文(HTTPS、本地主机)'; |
||||
|
} else if (err.name === 'NotReadableError') { |
||||
|
error.value = 'ERROR: 相机被占用'; |
||||
|
} else if (err.name === 'OverconstrainedError') { |
||||
|
error.value = 'ERROR: 安装摄像头不合适'; |
||||
|
} else if (err.name === 'StreamApiNotSupportedError') { |
||||
|
error.value = 'ERROR: 此浏览器不支持流API'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
// 返回上一页箭头 |
||||
|
const onClickLeft = () => { |
||||
|
//history.back(); |
||||
|
emits('update-qrScaning', '1') // 触发父组件事件 |
||||
|
} |
||||
|
</script> |
||||
|
<style scoped> |
||||
|
:deep(i.van-badge__wrapper.van-icon.van-icon-arrow-left.scanImg) { |
||||
|
font-size: 40px; |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
.scanImg { |
||||
|
margin-top: 30px; |
||||
|
margin-left: 10px; |
||||
|
} |
||||
|
|
||||
|
.error { |
||||
|
font-weight: bold; |
||||
|
color: red; |
||||
|
} |
||||
|
|
||||
|
.cameraMessage { |
||||
|
width: 100%; |
||||
|
height: 60px; |
||||
|
} |
||||
|
|
||||
|
.qr-scanner { |
||||
|
background-size: 3rem 3rem; |
||||
|
background-position: -1rem -1rem; |
||||
|
width: 100%; |
||||
|
/* height: 100%; */ |
||||
|
height: 100vh; |
||||
|
/* height: 288px; */ |
||||
|
position: relative; |
||||
|
background-color: #1110; |
||||
|
} |
||||
|
|
||||
|
.qr-scanner .box { |
||||
|
width: 213px; |
||||
|
height: 213px; |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
top: 40%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
overflow: hidden; |
||||
|
border: 1px solid #3aa5ff; |
||||
|
} |
||||
|
|
||||
|
.qr-scanner .txt { |
||||
|
width: 100%; |
||||
|
height: 35px; |
||||
|
line-height: 35px; |
||||
|
font-size: 14px; |
||||
|
text-align: center; |
||||
|
/* color: #f9f9f9; */ |
||||
|
margin: 0 auto; |
||||
|
position: absolute; |
||||
|
top: 60%; |
||||
|
left: 0; |
||||
|
} |
||||
|
|
||||
|
.qr-scanner .myQrcode { |
||||
|
text-align: center; |
||||
|
color: #3aa5ff; |
||||
|
} |
||||
|
|
||||
|
.qr-scanner .line { |
||||
|
height: calc(100% - 2px); |
||||
|
width: 100%; |
||||
|
background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #3aa5ff 211%); |
||||
|
border-bottom: 1px solid #3aa5ff; |
||||
|
transform: translateY(-100%); |
||||
|
animation: radar-beam 2s infinite alternate; |
||||
|
animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99); |
||||
|
animation-delay: 1.4s; |
||||
|
} |
||||
|
|
||||
|
.qr-scanner .box:after, |
||||
|
.qr-scanner .box:before, |
||||
|
.qr-scanner .angle:after, |
||||
|
.qr-scanner .angle:before { |
||||
|
content: ''; |
||||
|
display: block; |
||||
|
position: absolute; |
||||
|
width: 3vw; |
||||
|
height: 3vw; |
||||
|
} |
||||
|
|
||||
|
.qr-scanner .box:after, |
||||
|
.qr-scanner .box:before { |
||||
|
top: 0; |
||||
|
border-top-color: #3aa5ff; |
||||
|
} |
||||
|
|
||||
|
.qr-scanner .angle:after, |
||||
|
.qr-scanner .angle:before { |
||||
|
bottom: 0; |
||||
|
border-bottom-color: #3aa5ff; |
||||
|
} |
||||
|
|
||||
|
.qr-scanner .box:before, |
||||
|
.qr-scanner .angle:before { |
||||
|
left: 0; |
||||
|
border-left-color: #3aa5ff; |
||||
|
} |
||||
|
|
||||
|
.qr-scanner .box:after, |
||||
|
.qr-scanner .angle:after { |
||||
|
right: 0; |
||||
|
border-right-color: #3aa5ff; |
||||
|
} |
||||
|
|
||||
|
@keyframes radar-beam { |
||||
|
0% { |
||||
|
transform: translateY(-100%); |
||||
|
} |
||||
|
|
||||
|
100% { |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue