<script setup>
|
import { onMounted, ref } from "vue";
|
import { QrcodeStream, setZXingModuleOverrides } from "vue-qrcode-reader";
|
import { ElMessage } from "element-plus";
|
|
const error = ref("");
|
const cameraIsReady = ref(false);
|
const isSupportTorch = ref(false); // 是否支持闪光灯
|
const torch = ref(false); // 闪光灯状态
|
// 相机配置选项: 'user'|'environment' (默认:environment)
|
const selectedConstraints = ref({ facingMode: "environment" });
|
|
// 检测到二维码后绘制画布类型
|
function 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);
|
}
|
}
|
|
async function onCameraReady(capabilities) {
|
// NOTE: on iOS we can't invoke `enumerateDevices` before the user has given
|
// camera access permission. `QrcodeStream` internally takes care of
|
// requesting the permissions. The `camera-on` event should guarantee that this
|
// has happened..
|
console.log('camera ready', '=============');
|
|
try {
|
isSupportTorch.value = !!capabilities.torch;
|
cameraIsReady.value = true;
|
error.value = "";
|
} catch (error) {
|
onError(error);
|
cameraIsReady.value = false;
|
}
|
}
|
// 错误提示
|
function onError(err) {
|
error.value = `[${err.name}]: `;
|
if (err.name === "NotAllowedError") {
|
error.value += "you need to grant camera access permission";
|
} else if (err.name === "NotFoundError") {
|
error.value += "no camera on this device";
|
} else if (err.name === "NotSupportedError") {
|
error.value += "secure context required (HTTPS, localhost)";
|
} else if (err.name === "NotReadableError") {
|
error.value += "is the camera already in use?";
|
} else if (err.name === "OverconstrainedError") {
|
error.value += "installed cameras are not suitable";
|
} else if (err.name === "StreamApiNotSupportedError") {
|
error.value += "Stream API is not supported in this browser";
|
} else if (err.name === "InsecureContextError") {
|
error.value +=
|
"Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.";
|
} else {
|
error.value += err.message;
|
}
|
console.log('error', error.value, '=============');
|
|
ElMessage.warning(error.value);
|
}
|
// 用户摄像头的流后
|
function onDetect(detectedCodes) {
|
console.log('detectedCodes', detectedCodes, '=============');
|
|
if (detectedCodes.length > 0) {
|
onDecode(detectedCodes[0]?.rawValue);
|
}
|
}
|
|
const emit = defineEmits(["on-success"]);
|
|
// 解码(交给父组件处理:进行网络请求)
|
function onDecode(text) {
|
console.log('text', text, '=============');
|
|
emit("on-success", text);
|
}
|
// 文件转成base64
|
const processFile = async (file) => {
|
let reader = new FileReader();
|
reader.readAsDataURL(file);
|
reader.onload = (e) => {
|
let base64String = e.target?.result ||'';
|
// 此处可对该base64进行获取赋值传入后端
|
onDecode(base64String)
|
};
|
};
|
</script>
|
|
<template>
|
<div class="qrcode-scanner">
|
<qrcode-stream
|
class="qrcode-wrap"
|
:torch="torch"
|
v-memo="[torch]"
|
:constraints="selectedConstraints"
|
:track="paintBoundingBox"
|
@error="onError"
|
@detect="onDetect"
|
@camera-on="onCameraReady"
|
>
|
<div v-if="isSupportTorch" class="torch-wrap">
|
<div class="torch" @click="() => (torch = !torch)">
|
<div class="flash-light" v-if="torch">
|
灯灭了
|
</div>
|
<div class="flash-light" v-else>
|
灯开了
|
</div>
|
{{ torch ? "关闭闪光灯" : "打开闪光灯" }}
|
</div>
|
</div>
|
</qrcode-stream>
|
</div>
|
</template>
|
|
<style scoped lang="less">
|
.qrcode-wrap {
|
position: fixed !important;
|
top: 0;
|
right: 0;
|
bottom: 0;
|
left: 0;
|
width: 100vw;
|
height: 100vh;
|
z-index: 1 !important;
|
background: rgba(0, 0, 0, 0.5);
|
}
|
.torch-wrap {
|
width: 18.75rem;
|
height: 12.5rem;
|
position: fixed !important;
|
left: 50%;
|
top: 50%;
|
transform: translate(-50%, -30%);
|
z-index: 20;
|
}
|
|
.torch {
|
position: fixed;
|
bottom: -6.25rem;
|
left: 50%;
|
transform: translateX(-50%);
|
z-index: 20;
|
color: #fff;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
}
|
.photo-wrap {
|
position: fixed;
|
bottom: 2.875rem;
|
left: 2.875rem;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
gap: 6px;
|
}
|
|
.photo {
|
height: 3.125rem;
|
width: 3.125rem;
|
background-color: rgba(250, 250, 250, 0.8);
|
border-radius: 50%;
|
display: grid;
|
place-items: center;
|
cursor: pointer;
|
}
|
</style>
|