10 changed files with 1394 additions and 209 deletions
@ -1,205 +1,193 @@ |
|||
<template> |
|||
<div class="scanCode"> |
|||
<div class="container"> |
|||
<div class="qrcode"> |
|||
|
|||
|
|||
<div class="btn"> |
|||
<div class="left-back" style="float: left;"> |
|||
|
|||
<el-icon @click="clickBack" style="border: white solid 0px;"> |
|||
<Back /> |
|||
</el-icon> |
|||
<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> |
|||
<!--中间的扫码框--> |
|||
<div class="box"> |
|||
<div class="line"></div> |
|||
<div class="angle"></div> |
|||
</div> |
|||
<div class="txt"> |
|||
将二维码/条码放入框内,即自动扫描 |
|||
</div> |
|||
</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 id="reader"></div> |
|||
</div> |
|||
</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; |
|||
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) |
|||
router.back(); |
|||
} |
|||
const getCameras = () => { |
|||
Html5Qrcode.getCameras() |
|||
.then((devices) => { |
|||
if (devices && devices.length) { |
|||
state.html5QrCode = new Html5Qrcode("reader"); |
|||
start(); |
|||
} |
|||
}) |
|||
.catch((err) => { |
|||
alert("摄像头无访问权限!"); |
|||
}); |
|||
}; |
|||
const stop = () => { |
|||
state.html5QrCode |
|||
.stop() |
|||
.then((ignore) => { |
|||
console.log("停止扫码", ignore); |
|||
}) |
|||
.catch((err) => { |
|||
console.log(err); |
|||
alert("停止扫码失败"); |
|||
}); |
|||
}; |
|||
const dealSelectFiles = () => { |
|||
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("图片识别失败!"); |
|||
} |
|||
}; |
|||
onMounted(() => { |
|||
getCameras(); |
|||
}); |
|||
onUnmounted(() => { |
|||
//扫描设备是否在运行 |
|||
if (state.html5QrCode&&state.html5QrCode.isScanning) { |
|||
stop(); |
|||
</template> |
|||
<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 { |
|||
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'; |
|||
} |
|||
}); |
|||
|
|||
|
|||
|
|||
</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; */ |
|||
} |
|||
|
|||
.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> |
|||
|
|||
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> |
|||
@ -0,0 +1,184 @@ |
|||
<template> |
|||
<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> |
|||
<!--中间的扫码框--> |
|||
<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 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 { |
|||
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(); |
|||
} |
|||
</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> |
|||
|
|||
@ -0,0 +1,396 @@ |
|||
<template> |
|||
|
|||
<div class="page-scan"> |
|||
<!-- 扫码区域 --> |
|||
<!-- <div style="z-index: 1000; position: relative;"> --> |
|||
<el-icon @click="onClickLeft" class= "close-icon" style="width: 30px;height: 30px;"><!-- style="margin:15px;width: 20px;height: 20px;color: white;" --> |
|||
<Close style="width: 30px;height: 30px;" /><!-- style="width: 20px;height: 20px;" --> |
|||
</el-icon> |
|||
<!-- </div> --> |
|||
<div class="QrCode"> |
|||
|
|||
<video ref="video" height="100%" width="100%" id="video" autoplay></video> |
|||
|
|||
</div> |
|||
<!-- 扫码样式一 --> |
|||
<div class="Qr_scanner"> |
|||
<div class="box"> |
|||
<div class="line_row"> |
|||
<div class="line"></div> |
|||
</div> |
|||
<div class="angle"></div> |
|||
|
|||
</div> |
|||
|
|||
</div> |
|||
<div class="txt"> |
|||
将二维码/条码放入框内,即自动扫描 |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
// WebRTC适配器 只需要引入就ok |
|||
import 'webrtc-adapter' |
|||
import { BrowserMultiFormatReader } from '@zxing/library' |
|||
import request from '@/utils/axios/index' |
|||
import wx from 'weixin-js-sdk'; |
|||
|
|||
export default { |
|||
name: 'scanCodePage', |
|||
data() { |
|||
return { |
|||
codeReader: null, |
|||
isIOS: false |
|||
} |
|||
}, |
|||
mounted() { |
|||
const ua = navigator.userAgent |
|||
//isIOS.value = /iPhone|iPad|iPod/i.test(ua) |
|||
this.isIOS = /iPhone|iPad|iPod/i.test(ua) && !window.MSStream; |
|||
this.codeReader = new BrowserMultiFormatReader() |
|||
//alert(navigator.userAgent) |
|||
if (/wxwork\//.test(navigator.userAgent)) { |
|||
//alert('企业微信环境') |
|||
this.initWxConfig(); |
|||
} else { |
|||
// 正常使用WebRTC |
|||
this.openScan() |
|||
//this.initWxConfig(); |
|||
|
|||
} |
|||
|
|||
|
|||
|
|||
}, |
|||
beforeUnmount() { |
|||
this.codeReader && this.codeReader.reset() |
|||
}, |
|||
methods: { |
|||
|
|||
|
|||
getQyWxSignature() { |
|||
let url = window.location.href.split("#")[0]; |
|||
var req = {}; |
|||
req.url = url; |
|||
return request({ |
|||
url: "/javasys/lowCode/QrCode/QyWxSignature", |
|||
method: "get", |
|||
data: req, |
|||
}); |
|||
}, |
|||
|
|||
|
|||
// 初始化企业微信配置 |
|||
async initWxConfig() { |
|||
|
|||
this.getQyWxSignature().then(({ data }) => { |
|||
//alert(data.corpid) |
|||
wx.config({ |
|||
beta: true,// 必须这么写,否则wx.invoke调用形式的jsapi会有问题 |
|||
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 |
|||
appId: data.corpid, // 必填,企业微信的corpID |
|||
timestamp: data.timestamp, // 必填,生成签名的时间戳 |
|||
nonceStr: data.noncestr, // 必填,生成签名的随机串 |
|||
signature: data.jsapi_ticket_enterprises,// 必填,签名,见 附录-JS-SDK使用权限签名算法 |
|||
jsApiList: ['scanQRCode'] // 必填,需要使用的JS接口列表,凡是要调用的接口都需要传进来 |
|||
}); |
|||
this.startScan() |
|||
}); |
|||
}, |
|||
|
|||
// 触发扫码 |
|||
startScan() { |
|||
wx.ready(() => { |
|||
wx.scanQRCode({ |
|||
needResult: 1, // 返回扫码结果 |
|||
scanType: ['qrCode','barCode'], // 扫码类型:二维码 条码 |
|||
success: (res) => { |
|||
const result = res.resultStr; |
|||
|
|||
this.handleScanResult(result); |
|||
}, |
|||
fail: (err) => { |
|||
alert(err.errMsg) |
|||
|
|||
} |
|||
|
|||
|
|||
}); |
|||
}); |
|||
}, |
|||
onClickLeft(){ |
|||
this.codeReader && this.codeReader.reset() |
|||
this.codeReader = null |
|||
this.$emit('update-qrScaning', '1') // 触发父组件事件 emits('update-scanResult', dataList.value[0].rawValue) // 触发父组件事件 |
|||
}, |
|||
|
|||
// 处理扫码结果 |
|||
handleScanResult(code) { |
|||
/* this.$axios.post('/api/verify-code', { code }) |
|||
.then(res => { |
|||
// 登录成功后的逻辑(如跳转页面) |
|||
}); */ |
|||
this.$emit('update-scanResult', code) // 触发父组件事件 |
|||
}, |
|||
|
|||
|
|||
|
|||
async openScan() { |
|||
this.codeReader |
|||
.listVideoInputDevices() |
|||
.then((videoInputDevices) => { |
|||
// 默认获取第一个摄像头设备id |
|||
let firstDeviceId = videoInputDevices[0].deviceId |
|||
|
|||
//安卓手机获取不到后置摄像头标志 |
|||
let noBackAndroidFlag = false |
|||
// 获取第一个摄像头设备的名称 |
|||
|
|||
//console.log(videoInputDevices) |
|||
// 筛选后置主摄像头 |
|||
//alert(videoInputDevices.length) |
|||
videoInputDevices.some(element => { |
|||
if (videoInputDevices.length > 1) {//超过一个摄像头 |
|||
//alert(element.label) |
|||
if (element.label.startsWith("Video device")) {//判断是否是企业微信环境 |
|||
//alert(element.label) |
|||
if (this.isIOS) {//是苹果 |
|||
firstDeviceId = element.deviceId |
|||
return true; |
|||
} else {//是安卓 |
|||
if (videoInputDevices.length == 2) { |
|||
noBackAndroidFlag = true |
|||
//if(element.label.startsWith("Video device 2")){ |
|||
|
|||
//firstDeviceId = element.deviceId |
|||
//return true |
|||
//} |
|||
} else if (videoInputDevices.length >= 3) { |
|||
if (element.label.startsWith("Video device 3")) { |
|||
firstDeviceId = element.deviceId |
|||
return true |
|||
} |
|||
} |
|||
} |
|||
} else { |
|||
if (element.label.includes("back")) { |
|||
firstDeviceId = element.deviceId |
|||
return true; |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
} else {//只有一个摄像头 |
|||
return true |
|||
} |
|||
|
|||
|
|||
}); |
|||
|
|||
this.codeReader && this.codeReader.reset() // 重置 |
|||
this.decodeFromInputVideoFunc(firstDeviceId) |
|||
}) |
|||
.catch((err) => { |
|||
console.error(err) |
|||
}) |
|||
}, |
|||
decodeFromInputVideoFunc(firstDeviceId) { |
|||
this.codeReader.decodeFromInputVideoDeviceContinuously( |
|||
firstDeviceId, |
|||
'video', |
|||
(result, err) => { |
|||
if (result) { |
|||
console.log('扫描结果', result) |
|||
if (result.text) { |
|||
// this.clickIndexLeft(result.text) |
|||
//alert(result.text) |
|||
this.$emit('update-scanResult', result.text) |
|||
} |
|||
} |
|||
if (err && !err) { |
|||
console.error(err) |
|||
} |
|||
} |
|||
) |
|||
}, |
|||
/* // 停止扫描,并返回上一页 |
|||
clickIndexLeft(result) { |
|||
this.codeReader && this.codeReader.reset() |
|||
this.codeReader = null |
|||
// this.$route.params.result = result |
|||
// this.$router.back() |
|||
} */ |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scope> |
|||
.QrCode { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
position: relative; |
|||
|
|||
#video { |
|||
width: 100%; |
|||
height: 100%; |
|||
object-fit: cover; |
|||
} |
|||
} |
|||
|
|||
.Qr_scanner { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
z-index: 9; |
|||
width: 100%; |
|||
height: 100%; |
|||
background-color: rgba(0, 0, 0, 0.3); |
|||
} |
|||
|
|||
.Qr_scanner .box { |
|||
width: 65vw; |
|||
height: 65vw; |
|||
max-height: 75vh; |
|||
max-width: 75vh; |
|||
position: relative; |
|||
left: 50%; |
|||
top: 45%; |
|||
transform: translate(-50%, -50%); |
|||
border: 1px solid rgb(43, 113, 254); |
|||
|
|||
.line_row { |
|||
width: 100%; |
|||
overflow: hidden; |
|||
background-image: linear-gradient(0deg, |
|||
transparent 24%, |
|||
rgba(136, 176, 255, 0.1) 25%, |
|||
rgba(136, 176, 255, 0.1) 26%, |
|||
transparent 27%, |
|||
transparent 74%, |
|||
rgba(136, 176, 255, 0.1) 75%, |
|||
rgba(136, 176, 255, 0.1) 76%, |
|||
transparent 77%, |
|||
transparent), |
|||
linear-gradient(90deg, |
|||
transparent 24%, |
|||
rgba(136, 176, 255, 0.1) 25%, |
|||
rgba(136, 176, 255, 0.1) 26%, |
|||
transparent 27%, |
|||
transparent 74%, |
|||
rgba(136, 176, 255, 0.1) 75%, |
|||
rgba(136, 176, 255, 0.1) 76%, |
|||
transparent 77%, |
|||
transparent); |
|||
background-size: 3rem 3rem; |
|||
background-position: -1rem -1rem; |
|||
animation: Heightchange 2s infinite; |
|||
animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99); |
|||
animation-delay: 1.4s; |
|||
border-bottom: 1px solid rgba(136, 176, 255, 0.1); |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: flex-end; |
|||
} |
|||
} |
|||
|
|||
.Qr_scanner .line { |
|||
width: 100%; |
|||
height: 3px; |
|||
background: #2b71fe; |
|||
// opacity: 0.58; |
|||
filter: blur(4px); |
|||
} |
|||
|
|||
.Qr_scanner .box:after, |
|||
.Qr_scanner .box:before, |
|||
.Qr_scanner .angle:after, |
|||
.Qr_scanner .angle:before { |
|||
content: ''; |
|||
display: block; |
|||
position: absolute; |
|||
width: 78px; |
|||
height: 78px; |
|||
border: 0.15rem solid transparent; |
|||
} |
|||
|
|||
.Qr_scanner .box:after, |
|||
.Qr_scanner .box:before { |
|||
top: -7px; |
|||
border-top-color: #2b71fe; |
|||
} |
|||
|
|||
.Qr_scanner .angle:after, |
|||
.Qr_scanner .angle:before { |
|||
bottom: -7px; |
|||
border-bottom-color: #2b71fe; |
|||
} |
|||
|
|||
.Qr_scanner .box:before, |
|||
.Qr_scanner .angle:before { |
|||
left: -7px; |
|||
border-left-color: #2b71fe; |
|||
} |
|||
|
|||
.Qr_scanner .box:after, |
|||
.Qr_scanner .angle:after { |
|||
right: -7px; |
|||
border-right-color: #2b71fe; |
|||
} |
|||
|
|||
@keyframes radar-beam { |
|||
0% { |
|||
transform: translateY(-100%); |
|||
} |
|||
|
|||
100% { |
|||
transform: translateY(0); |
|||
} |
|||
} |
|||
|
|||
@keyframes Heightchange { |
|||
0% { |
|||
height: 0; |
|||
} |
|||
|
|||
100% { |
|||
height: 100%; |
|||
} |
|||
} |
|||
|
|||
.txt { |
|||
width: 100%; |
|||
height: 35px; |
|||
line-height: 35px; |
|||
font-size: 14px; |
|||
text-align: center; |
|||
/* color: #f9f9f9; */ |
|||
margin: 0 auto; |
|||
position: absolute; |
|||
top: 63%; |
|||
left: 0; |
|||
color: white; |
|||
z-index: 1001; |
|||
} |
|||
|
|||
.close-icon { |
|||
position: absolute; |
|||
top: 15px; |
|||
left: 15px; |
|||
width: 20px; |
|||
height: 20px; |
|||
color: white; |
|||
z-index: 1001; |
|||
cursor: pointer; |
|||
background: rgba(0, 0, 0, 0.3); /* 可选背景 */ |
|||
border-radius: 50%; /* 圆形背景 */ |
|||
padding: 5px; |
|||
/* 点击区域扩大 */ |
|||
} |
|||
.Qr_scanner { |
|||
z-index: 999; /* 低于图标容器 */ |
|||
/* 其他原有样式保持不变 */ |
|||
} |
|||
</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