Browse Source

二维码扫描动画效果优化v0.1

lwx_v6
liwenxuan 10 months ago
parent
commit
13a80732bf
  1. 57
      package-lock.json
  2. 1
      package.json
  3. 404
      src/views/home/scanQrCode.vue

57
package-lock.json

@ -26,6 +26,7 @@
"ts-md5": "^1.3.1", "ts-md5": "^1.3.1",
"vue": "^3.5.10", "vue": "^3.5.10",
"vue-pdf-embed": "^1.2.1", "vue-pdf-embed": "^1.2.1",
"vue-qrcode-reader": "^5.7.0",
"vue-router": "^4.4.5", "vue-router": "^4.4.5",
"vue3-pdf-embed": "^1.1.7", "vue3-pdf-embed": "^1.1.7",
"vue3-pdfjs": "^0.1.6" "vue3-pdfjs": "^0.1.6"
@ -1458,6 +1459,16 @@
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
"node_modules/@types/dom-webcodecs": {
"version": "0.1.13",
"resolved": "https://registry.npmmirror.com/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz",
"integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ=="
},
"node_modules/@types/emscripten": {
"version": "1.40.0",
"resolved": "https://registry.npmmirror.com/@types/emscripten/-/emscripten-1.40.0.tgz",
"integrity": "sha512-MD2JJ25S4tnjnhjWyalMS6K6p0h+zQV6+Ylm+aGbiS8tSn/aHLSGNzBgduj6FB4zH0ax2GRMGYi/8G1uOxhXWA=="
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz",
@ -1955,6 +1966,15 @@
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
}, },
"node_modules/barcode-detector": {
"version": "2.2.2",
"resolved": "https://registry.npmmirror.com/barcode-detector/-/barcode-detector-2.2.2.tgz",
"integrity": "sha512-JcSekql+EV93evfzF9zBr+Y6aRfkR+QFvgyzbwQ0dbymZXoAI9+WgT7H1E429f+3RKNncHz2CW98VQtaaKpmfQ==",
"dependencies": {
"@types/dom-webcodecs": "^0.1.11",
"zxing-wasm": "1.1.3"
}
},
"node_modules/base": { "node_modules/base": {
"version": "0.11.2", "version": "0.11.2",
"resolved": "https://registry.npmmirror.com/base/-/base-0.11.2.tgz", "resolved": "https://registry.npmmirror.com/base/-/base-0.11.2.tgz",
@ -5981,6 +6001,11 @@
"resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz",
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==" "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="
}, },
"node_modules/sdp": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/sdp/-/sdp-3.2.0.tgz",
"integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw=="
},
"node_modules/semver": { "node_modules/semver": {
"version": "7.6.3", "version": "7.6.3",
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.6.3.tgz", "resolved": "https://registry.npmmirror.com/semver/-/semver-7.6.3.tgz",
@ -7693,6 +7718,18 @@
"vue": "^2.x || ^3.x" "vue": "^2.x || ^3.x"
} }
}, },
"node_modules/vue-qrcode-reader": {
"version": "5.7.0",
"resolved": "https://registry.npmmirror.com/vue-qrcode-reader/-/vue-qrcode-reader-5.7.0.tgz",
"integrity": "sha512-NuLBK3madyMhM35n2libd0i5GH3Rcvq2d7qB1Qyf5FqdIdC6GaMEC8Wi8v0HFuv5V5aptOLhHVBfSczBpO3lvA==",
"dependencies": {
"barcode-detector": "2.2.2",
"webrtc-adapter": "8.2.3"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/vue-router": { "node_modules/vue-router": {
"version": "4.4.5", "version": "4.4.5",
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.4.5.tgz", "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.4.5.tgz",
@ -7774,6 +7811,18 @@
"resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==" "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="
}, },
"node_modules/webrtc-adapter": {
"version": "8.2.3",
"resolved": "https://registry.npmmirror.com/webrtc-adapter/-/webrtc-adapter-8.2.3.tgz",
"integrity": "sha512-gnmRz++suzmvxtp3ehQts6s2JtAGPuDPjA1F3a9ckNpG1kYdYuHWYpazoAnL9FS5/B21tKlhkorbdCXat0+4xQ==",
"dependencies": {
"sdp": "^3.2.0"
},
"engines": {
"node": ">=6.0.0",
"npm": ">=3.10.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
@ -7969,6 +8018,14 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/zxing-wasm": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/zxing-wasm/-/zxing-wasm-1.1.3.tgz",
"integrity": "sha512-MYm9k/5YVs4ZOTIFwlRjfFKD0crhefgbnt1+6TEpmKUDFp3E2uwqGSKwQOd2hOIsta/7Usq4hnpNRYTLoljnfA==",
"dependencies": {
"@types/emscripten": "^1.39.10"
}
} }
} }
} }

1
package.json

@ -28,6 +28,7 @@
"ts-md5": "^1.3.1", "ts-md5": "^1.3.1",
"vue": "^3.5.10", "vue": "^3.5.10",
"vue-pdf-embed": "^1.2.1", "vue-pdf-embed": "^1.2.1",
"vue-qrcode-reader": "^5.7.0",
"vue-router": "^4.4.5", "vue-router": "^4.4.5",
"vue3-pdf-embed": "^1.1.7", "vue3-pdf-embed": "^1.1.7",
"vue3-pdfjs": "^0.1.6" "vue3-pdfjs": "^0.1.6"

404
src/views/home/scanQrCode.vue

@ -1,287 +1,181 @@
<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>
<div class="scan-line"></div> <!-- <van-icon class="scanImg" @click="onClickLeft" name="arrow-left" /> -->
<!-- 新增边框装饰 --> <button @click="onClickLeft" name="arrow-left" ></button>
<div class="border-decor top-left"></div>
<div class="border-decor top-right"></div>
<div class="border-decor bottom-left"></div>
<div class="border-decor bottom-right"></div>
<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>
<div class="txt">
将二维码/条码放入框内即自动扫描
<div id="reader"></div>
</div> </div>
</div> </div>
<!--<div class="btn">
<div class="left-back">
<el-icon @click="clickBack">
<Back />
</el-icon>
</div> </div>
<div class="right-file"> </qrcode-stream>
<van-uploader
v-model="fileList"
:preview-image="false"
:after-read="dealSelectFiles"
>
<el-icon><Picture /></el-icon>
</van-uploader>
</div>
</div> -->
</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, //
qrbox: { width: 300, height: 210 }, //
aspectRatio: 1.7,// }
}, //
(decodedText, decodedResult) => { const onInit = async (promise: any) => {
window.location.href = decodedText; console.log('初始化摄像头',promise)
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 = () => {
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();
} }
});
</script>
<style lang="scss" scoped>
//
@keyframes scanLine {
0% { transform: translateY(-100%); }
100% { transform: translateY(300%); }
} }
//
@keyframes borderPulse { const onClickLeft = () => {
0% { opacity: 0.8; } history.back();
50% { opacity: 0.2; }
100% { opacity: 0.8; }
} }
</script>
.scan-line { <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; position: absolute;
top: 0;
left: 50%; left: 50%;
transform: translateX(-50%); top: 50%;
width: 90%; transform: translate(-50%, -50%);
height: 2px; overflow: hidden;
background: linear-gradient(180deg, rgba(0,255,255,0) 0%, rgba(51,204,255,1) 50%, rgba(0,255,255,0) 100%); border: 1px solid #3aa5ff;
animation: scanLine 2s infinite linear; }
z-index: 2; .qr-scanner .txt {
} width: 100%;
height: 35px;
.border-decor { line-height: 35px;
font-size: 14px;
text-align: center;
/* color: #f9f9f9; */
margin: 0 auto;
position: absolute; position: absolute;
width: 20px; top: 70%;
height: 20px; left: 0;
border: 2px solid #33ccff; }
animation: borderPulse 1.5s infinite ease-in-out; .qr-scanner .myQrcode {
text-align: center;
&.top-left { color: #3aa5ff;
top: -1px; }
left: -1px; .qr-scanner .line {
border-right: none; height: calc(100% - 2px);
border-bottom: none; 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;
} }
&.top-right { .qr-scanner .box:after,
top: -1px; .qr-scanner .box:before {
right: -1px; top: 0;
border-left: none; border-top-color: #3aa5ff;
border-bottom: none;
} }
&.bottom-left { .qr-scanner .angle:after,
bottom: -1px; .qr-scanner .angle:before {
left: -1px; bottom: 0;
border-right: none; border-bottom-color: #3aa5ff;
border-top: none;
} }
&.bottom-right { .qr-scanner .box:before,
bottom: -1px; .qr-scanner .angle:before {
right: -1px; left: 0;
border-left: none; border-left-color: #3aa5ff;
border-top: none;
} }
}
.scanCode { .qr-scanner .box:after,
height: 100vh; .qr-scanner .angle:after {
display: flex; right: 0;
flex-direction: column; border-right-color: #3aa5ff;
background: rgba(0, 0, 0);
}
//
.container {
position: relative; //
} }
#reader { @keyframes radar-beam {
position: relative; // 0% {
&::after { transform: translateY(-100%);
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(100% - 4px);
height: calc(100% - 4px);
border: 2px solid rgba(255,255,255,0.1);
}
} }
.qrcode { 100% {
position: relative; // transform: translateY(0);
} }
.btn {
flex: 1;
padding-top: 2vw;
display: flex;
padding-left: 3vw;
color: #fff;
font-size: 6vw;
align-items: flex-start;
/* border: green solid 7px; */
} }
</style> </style>
Loading…
Cancel
Save