|
@@ -0,0 +1,195 @@
|
|
|
+<script setup>
|
|
|
+import {Html5Qrcode, Html5QrcodeScanner, Html5QrcodeScanType, Html5QrcodeSupportedFormats} from "html5-qrcode";
|
|
|
+
|
|
|
+const emit = defineEmits('oSuccess', 'onError')
|
|
|
+const slots = useSlots();
|
|
|
+// 判断是否有 default 插槽
|
|
|
+const hasDefaultSlot = computed(() => !!slots.default);
|
|
|
+
|
|
|
+let isShow = ref(false)
|
|
|
+let results = ref(null)
|
|
|
+
|
|
|
+let scanInstance = null
|
|
|
+const openQrcode = async () => {
|
|
|
+ Html5Qrcode.getCameras()
|
|
|
+ .then(devices => {
|
|
|
+ if (devices && devices.length) {// 当前环境下能识别出摄像头,并且摄像头的数据可能不止一个
|
|
|
+ if (!scanInstance) {
|
|
|
+ // reader 放置扫码功能的元素ID
|
|
|
+ scanInstance = new Html5Qrcode('qr-reader', {
|
|
|
+ formatsToSupport: [
|
|
|
+ Html5QrcodeSupportedFormats.QR_CODE,
|
|
|
+ ],
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ isShow.value = true
|
|
|
+ scanInstance.start(
|
|
|
+ {facingMode: "environment"},
|
|
|
+ {
|
|
|
+ focusMode: 'continuous',
|
|
|
+ fps: 1, // 可选,每n秒帧扫描一次
|
|
|
+ qrbox: { // 扫描的UI框
|
|
|
+ width: 250,
|
|
|
+ height: 250
|
|
|
+ },
|
|
|
+ videoConstraints: {
|
|
|
+ // width: 375,
|
|
|
+ // height: (window.visualViewport.height - 50),
|
|
|
+ aspectRatio: window.visualViewport.height / window.visualViewport.width,
|
|
|
+ facingMode: "environment",
|
|
|
+ }
|
|
|
+ },
|
|
|
+ (decodedText, decodedResult) => {
|
|
|
+ showToast('识别成功')
|
|
|
+ // 扫描结果
|
|
|
+ results.value = {
|
|
|
+ decodedText,
|
|
|
+ decodedResult
|
|
|
+ }
|
|
|
+ isShow.value = false
|
|
|
+ scanInstance.stop()
|
|
|
+ emit('oSuccess', {
|
|
|
+ decodedText,
|
|
|
+ decodedResult
|
|
|
+ })
|
|
|
+ },
|
|
|
+ (errorMessage, error) => {
|
|
|
+ emit('onError', {
|
|
|
+ errorMessage,
|
|
|
+ error
|
|
|
+ })
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ isShow.value = false
|
|
|
+ // 错误信息处理仅供参考,具体情况看输出!!!
|
|
|
+ let errorStr = ''
|
|
|
+ if (typeof err === "string") {
|
|
|
+ errorStr = err
|
|
|
+ } else {
|
|
|
+ if (err.name === "NotAllowedError")
|
|
|
+ errorStr = "您需要授予相机访问权限"
|
|
|
+ if (err.name === "NotFoundError") {
|
|
|
+ errorStr = "未检测到摄像头"
|
|
|
+ }
|
|
|
+ if (err.name === "NotSupportedError")
|
|
|
+ errorStr = "摄像头访问只支持在安全的上下文中,如https或localhost"
|
|
|
+ if (err.name === "NotReadableError") errorStr = "摄像头被占用"
|
|
|
+ if (err.name === "OverconstrainedError")
|
|
|
+ errorStr = "安装摄像头不合适"
|
|
|
+ if (err.name === "StreamApiNotSupportedError")
|
|
|
+ errorStr = "此浏览器不支持流API"
|
|
|
+ }
|
|
|
+ showToast(errorStr)
|
|
|
+ })
|
|
|
+}
|
|
|
+const closeQrcode = () => {
|
|
|
+ isShow.value = false
|
|
|
+ if (scanInstance) scanInstance.stop()
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+let fileList = ref([])
|
|
|
+const uploadImg = () => {
|
|
|
+ try {
|
|
|
+ window.qrcode.callback = (result) => {
|
|
|
+ showToast(result);
|
|
|
+ };
|
|
|
+ let file = state.fileList[0].file;
|
|
|
+ let reader = new FileReader();
|
|
|
+ reader.onload = (function () {
|
|
|
+ return function (e) {
|
|
|
+ window.qrcode.decode(e.target.result);
|
|
|
+ };
|
|
|
+ })(file);
|
|
|
+ reader.readAsDataURL(file);
|
|
|
+ } catch (error) {
|
|
|
+ console.log(error);
|
|
|
+ showToast({
|
|
|
+ message: error+"识别失败!",
|
|
|
+ duration: 2000,
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const selectFile = ()=> {
|
|
|
+ let odeScanner = new Html5QrcodeScanner(
|
|
|
+ "qr-code-file", { fps: 1 , qrbox: { // 扫描的UI框
|
|
|
+ width: 250,
|
|
|
+ height: 250
|
|
|
+ },});
|
|
|
+ odeScanner.render((e) => {
|
|
|
+ console.log(e)
|
|
|
+ });
|
|
|
+ console.log(odeScanner, 'odeScanner')
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ selectFile()
|
|
|
+})
|
|
|
+onUnmounted(() => {
|
|
|
+ if (scanInstance) scanInstance.stop()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <template v-if="hasDefaultSlot">
|
|
|
+ <slot></slot>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <van-button @click="openQrcode" size="mini">扫一扫</van-button>
|
|
|
+ </template>
|
|
|
+ <div v-if="false" class="h-200">
|
|
|
+ {{ results }}
|
|
|
+ </div>
|
|
|
+ <div class="overlay" v-show="isShow">
|
|
|
+ <div class="absolute w-full z-30 flex flex-row items-center justify-center p-12">
|
|
|
+ <div class="absolute left-12 font-bold" @click="closeQrcode">
|
|
|
+ <span class="iconfont icon-left text-white"></span>
|
|
|
+ </div>
|
|
|
+ <div class="text-base text-[#fff] font-bold">
|
|
|
+ 扫描二维码
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="relative qr-reader z-20" id="qr-reader"></div>
|
|
|
+ <p class="absolute w-full p-12 text-center bottom-100 z-30 text-sm text-[#fff]">请将二维码对准扫码框中心</p>
|
|
|
+ <div id="qr-code-file" class="absolute w-54 h-54 grid place-items-center bottom-40 z-30 rounded-full bg-[#000]/80 text-white left-1/2 -translate-x-1/2">
|
|
|
+ <img src="~/assets/img/scan/pic.png" height="32" width="32" alt=""/>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div id="qr-code-file" class="w-54 h-54 grid place-items-center bottom-40 z-30 rounded-full bg-[#000]/80 text-white">
|
|
|
+ <img src="~/assets/img/scan/pic.png" height="32" width="32" alt=""/>
|
|
|
+ </div>
|
|
|
+ <van-uploader
|
|
|
+ v-model="fileList"
|
|
|
+ :preview-image="false"
|
|
|
+ :after-read="uploadImg"
|
|
|
+ accept="image/*"
|
|
|
+ :multiple="false"
|
|
|
+ :max-count="1"
|
|
|
+ >
|
|
|
+ <van-icon name="photo-o"
|
|
|
+ /></van-uploader>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.overlay {
|
|
|
+ position: fixed;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ z-index: 999999;
|
|
|
+ width: 100%;
|
|
|
+ height: calc(100% - 50px);
|
|
|
+ background: rgba(0, 0, 0, 0.7);
|
|
|
+
|
|
|
+ .scan-instruction {
|
|
|
+
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|