Browse Source
2.开发基于zxing/library的既支持扫二维码也支持扫条码的扫码组件 3.因为自研zxing组件小米手机中的企业微信只能获取到前置摄像头,实现按需加载功能,企业微信环境调用企业微信扫一扫,其它环境调用自研zxing扫码组件lwx_v8
8 changed files with 965 additions and 480 deletions
@ -1,212 +1,184 @@ |
|||
<template> |
|||
<div class="scanCode"> |
|||
<div class="container"> |
|||
<div class="qrcode"> |
|||
<div style="height: 100vh"> |
|||
<qrcode-stream @detect="onDecode" @error="onInit" style="height: 100%"> |
|||
<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 class="right-file"> |
|||
|
|||
<van-uploader |
|||
v-model="fileList" |
|||
:preview-image="false" |
|||
:after-read="dealSelectFiles" |
|||
> |
|||
<el-icon><Picture /></el-icon> |
|||
</van-uploader> |
|||
</div> --> |
|||
</div> |
|||
|
|||
|
|||
<div id="reader"></div> |
|||
<!--中间的扫码框--> |
|||
<div class="box"> |
|||
<div class="line"></div> |
|||
<div class="angle"></div> |
|||
</div> |
|||
<div class="txt"> |
|||
将二维码/条码放入框内,即自动扫描 |
|||
</div> |
|||
<!--<div class="btn"> |
|||
<div class="left-back"> |
|||
|
|||
<el-icon @click="clickBack"> |
|||
<Back /> |
|||
</el-icon> |
|||
</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> --> |
|||
</qrcode-stream> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { reactive } from "vue"; |
|||
import { defineComponent, toRefs, onMounted, onUnmounted } from "vue"; |
|||
import { Html5Qrcode } from "html5-qrcode"; |
|||
import { useRouter } from 'vue-router' |
|||
|
|||
const router = useRouter() |
|||
|
|||
const state = reactive({ |
|||
html5QrCode: null, |
|||
fileList: [], |
|||
}); |
|||
const start = () => { |
|||
state.html5QrCode |
|||
.start( |
|||
{ facingMode: "environment" }, |
|||
{ |
|||
fps: 5, |
|||
qrbox: { width: 300, height: 210 }, |
|||
aspectRatio: 1.7,// |
|||
}, |
|||
(decodedText, decodedResult) => { |
|||
window.location.href = decodedText; |
|||
if (state.html5QrCode&&state.html5QrCode.isScanning) { |
|||
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 = () => { |
|||
<script setup lang="ts"> |
|||
import {QrcodeStream} from "vue-qrcode-reader"; |
|||
import {ref} from "vue"; |
|||
|
|||
|
|||
// 定义变量 |
|||
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('扫描成功')--------------------------------------- |
|||
window.location.href = dataList.value[0].rawValue; |
|||
// 调用后台接口存入数据库 |
|||
// 数据存入数据库后跳转页面 |
|||
} |
|||
// 初始化摄像头 |
|||
const onInit = async (promise: any) => { |
|||
console.log('初始化摄像头',promise) |
|||
try { |
|||
window.qrcode.callback = (result) => { |
|||
alert("成功了,结果是:" + result); |
|||
}; // get select files. |
|||
let file = state.fileList[0].file; |
|||
var reader = new FileReader(); |
|||
reader.onload = (function () { |
|||
return function (e) { |
|||
window.qrcode.decode(e.target.result); |
|||
}; |
|||
})(file); |
|||
reader.readAsDataURL(file); |
|||
} catch (error) { |
|||
alert("图片识别失败!"); |
|||
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'; |
|||
} |
|||
}; |
|||
onMounted(() => { |
|||
getCameras(); |
|||
}); |
|||
onUnmounted(() => { |
|||
//扫描设备是否在运行 |
|||
if (state.html5QrCode&&state.html5QrCode.isScanning) { |
|||
stop(); |
|||
} |
|||
}); |
|||
|
|||
|
|||
|
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.scanCode { |
|||
height: 100vh; |
|||
display: flex; |
|||
flex-direction: column; |
|||
background: rgba(0, 0, 0); |
|||
} |
|||
.container { |
|||
// 返回上一页箭头 |
|||
const onClickLeft = () => { |
|||
history.back(); |
|||
} |
|||
</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%; |
|||
/* border: red solid 7px; */ |
|||
} |
|||
.qrcode { |
|||
height: 90%; |
|||
/* width: 100%;*/ |
|||
/* border: white solid 7px; */ |
|||
} |
|||
#reader { |
|||
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; |
|||
|
|||
height: 90vh; |
|||
/* top: 50%; |
|||
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; |
|||
transform: translateY(-50%); */ |
|||
/* border: yellow solid 5px; */ |
|||
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%); |
|||
} |
|||
|
|||
.btn { |
|||
flex: 1; |
|||
padding-top: 2vw; |
|||
display: flex; |
|||
padding-left: 3vw; |
|||
color: #fff; |
|||
font-size: 6vw; |
|||
align-items: flex-start; |
|||
/* border: green solid 7px; */ |
|||
100% { |
|||
transform: translateY(0); |
|||
} |
|||
</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