Vue implements barcode scanning and QR code H5 compatible Apple IOS

1. Attention

vue-qrcode-reader uses camera parsing. For security reasons, the browser can only access the camera under the https protocol or localhost, so代码需要部署所在的服务器需要https

2. Specific implementation ①

  • Install plugin
npm install --save vue-qrcode-reader
  • Create component
<template>
	<div>
		<qrcode-stream v-if="show" :camera="camera" :torch="torchActive" @decode="onDecode" @init="onInit">
			<div>
				<div class="qr-scanner">
					<div class="box">
						<div class="line"></div>
						<div class="angle"></div>
					</div>
				</div>
			</div>
		</qrcode-stream>
	</div>
</template>

<script>
import {
    
     QrcodeStream } from 'vue-qrcode-reader';
export default {
    
    
    components: {
    
    
        QrcodeStream
    },
    data() {
    
    
        return {
    
    
            torchActive: false,
            camera: 'auto',
        };
    },
    props: {
    
    
        show: {
    
    
            type: Boolean,
            default: false
        }
    },
    methods: {
    
    
        async onDecode(scanResult) {
    
    
			// TODO 逻辑处理或直接返回扫码结果
            this.$emit('getScanResult', scanResult)
        },
        async onInit(promise) {
    
    
            const {
    
    
                capabilities
            } = await promise;

            const TORCH_IS_SUPPORTED = !!capabilities.torch;
            try {
    
    
                await promise;
            } catch (error) {
    
    
                if (error.name === 'NotAllowedError') {
    
    
                    this.$toast.fail('ERROR');
                } else if (error.name === 'NotFoundError') {
    
    
                    this.$toast.fail('这个设备上没有摄像头');
                } else if (error.name === 'NotSupportedError') {
    
    
                    this.$toast.fail('所需的安全上下文(HTTPS、本地主机)');
                } else if (error.name === 'NotReadableError') {
    
    
                    this.$toast.fail('相机被占用');
                } else if (error.name === 'OverconstrainedError') {
    
    
                    this.$toast.fail('安装摄像头不合适');
                } else if (error.name === 'StreamApiNotSupportedError') {
    
    
                    this.$toast.fail('此浏览器不支持流API');
                }
            }
        },
    },
};
</script>

<style scoped>
.qr-scanner {
    
    
    background-image: linear-gradient(0deg,
            transparent 24%,
            rgba(32, 255, 77, 0.1) 25%,
            rgba(32, 255, 77, 0.1) 26%,
            transparent 27%,
            transparent 74%,
            rgba(32, 255, 77, 0.1) 75%,
            rgba(32, 255, 77, 0.1) 76%,
            transparent 77%,
            transparent),
        linear-gradient(90deg,
            transparent 24%,
            rgba(32, 255, 77, 0.1) 25%,
            rgba(32, 255, 77, 0.1) 26%,
            transparent 27%,
            transparent 74%,
            rgba(32, 255, 77, 0.1) 75%,
            rgba(32, 255, 77, 0.1) 76%,
            transparent 77%,
            transparent);
    background-size: 3rem 3rem;
    background-position: -1rem -1rem;
    width: 100%;
    height: 29vh;
    position: relative;
    background-color: #1110;
}

.qr-scanner .box {
    
    
    width: 213px;
    height: 213px;
    position: absolute;
    left: 50%;
    top: 100%;
    transform: translate(-50%, -50%);
    overflow: hidden;
    border: 0.1rem solid rgba(0, 255, 51, 0.2);
}

.qr-scanner .txt {
    
    
    width: 100%;
    height: 35px;
    line-height: 35px;
    font-size: 14px;
    text-align: center;
    margin: 0 auto;
    position: absolute;
    top: 70%;
    left: 0;
}

.qr-scanner .myQrcode {
    
    
    text-align: center;
    color: #00ae10;
}

.qr-scanner .line {
    
    
    height: calc(100% - 2px);
    width: 100%;
    background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #00ff33 211%);
    border-bottom: 3px solid #00ff33;
    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;

    border: 0.2rem solid transparent;
}

.qr-scanner .box:after,
.qr-scanner .box:before {
    
    
    top: 0;
    border-top-color: #00ff33;
}

.qr-scanner .angle:after,
.qr-scanner .angle:before {
    
    
    bottom: 0;
    border-bottom-color: #00ff33;
}

.qr-scanner .box:before,
.qr-scanner .angle:before {
    
    
    left: 0;
    border-left-color: #00ff33;
}

.qr-scanner .box:after,
.qr-scanner .angle:after {
    
    
    right: 0;
    border-right-color: #00ff33;
}

@keyframes radar-beam {
    
    
    0% {
    
    
        transform: translateY(-100%);
    }

    100% {
    
    
        transform: translateY(0);
    }
}
</style>

3. Component usage

  • Import components
import qrcode from '@/components/qrcode-scan/qrcode.vue'
  • Register component
components: {
    
    
    'vue-qrcode': qrcode
},
  • The original code scanning pop-up window style is not very beautiful and difficult to control. Use vant pop-up window to control the style. DefinitionshowScanDefault false
<!-- 扫码弹窗 -->
<van-dialog class="dailog-sm" v-model="showScan" :showConfirmButton="false" :close-on-click-overlay="true">
    <vue-qrcode :show="showScan" @getScanResult="getScanResult"></vue-qrcode>
    <div class="btns-sm">
    <van-button size="large" type="info" @click="showScan = false">取消</van-button>
    </div>
</van-dialog>
  • getScanResult
// 扫码结果逻辑处理
async getScanResult (scanResult) {
    
    
	console.log(scanResult);
}
  • Pop-up window takes up H5 page style adjustment
.dailog-sm{
    
    
    left: 0;
    top: 0;
    bottom: 0;
    right: 0;
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
    border-radius: 0;
    width: auto;
    .qrcode-stream-wrapper{
    
    
        height: 92vh;
        video{
    
    
            height: 92vh;
        }
    }
    .van-dialog__content{
    
    
        height: 92%;
        .dialog{
    
    
            height: 100%;
        }
        .qr-scanner{
    
    
            height: 100vh;
            .box{
    
    
                top: 40%;
                width: 60vw;
                height: 60vw;
            }
        }
    }
    .btns-sm{
    
    
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        padding: 0;
        background: #000;
        height: 8vh;
        text-align: center;
        .van-button{
    
    
            display: inline-block;
            height: 8vh;
            background: none;
            color: #fff;
            border: none;
            font-size: 0.4rem;
        }
    }
}

4. Effect display

  • Under https, no pop-ups or style adjustments are added.

Insert image description here

  • Under https, add a pop-up window without style adjustment
    Insert image description here
  • http, add pop-up window, add style adjustment
    Insert image description here

5. Finally

As you can see, after the style adjustment, it fills up the screen. The camera cannot be enabled under non-https, and a white screen is displayed.




As required, add条形码scan support and苹果手机IOSsupport

Note: In addition to localhost testing, other environments also require https environment.In addition, support for IOS is as follows, click to go to the official website
Insert image description here

Specific implementation②

  • Install plugin
npm install @zxing/library --save
  • Create component
<template>
    <div class="page-scan" id="app" v-if="show">
        <div class="scan-box">
            <video ref="video" id="video" class="scan-video" autoplay></video>
            <div class="scan-img">
                <div class="scan-frame">
                    <span class="left-t" style="left: 20%; top: 10%;"></span>
                    <span class="right-t" style="right: 20%; top: 10%;"></span>
                    <span class="left-b" style="left: 20%; bottom: 10%;"></span>
                    <span class="right-b" style="right: 20%; bottom: 10%;"></span>
                    <span class="cross-line" style="margin-top: 8%;"></span>
                </div>
            </div>
            <div class="scan-tip"> {
    
    {
    
    scanTextData.tipMsg}} </div>
        </div>
    </div>
</template>

<script>
import {
    
    
    BrowserMultiFormatReader
} from '@zxing/library'
let scanTextData = {
    
    
    codeReader: null,
    tipMsg: '识别条形码/二维码'
}
export default {
    
    
    name: 'scanCodePage',
    data () {
    
    
        return {
    
    
            scanTextData: {
    
    
                codeReader: null,
                tipMsg: '识别条形码/二维码'
            },
            hasBind: false
        }
    },
    props: {
    
    
        show: {
    
    
            type: Boolean,
            default: false
        }
    },
    mounted () {
    
    
        scanTextData.codeReader = new BrowserMultiFormatReader()
        this.openScan()
    },
    watch: {
    
    
        'show' (val) {
    
    
            if (!val) {
    
    
                // 关闭摄像头
                let thisVideo = document.getElementById('video')
                thisVideo.srcObject.getTracks()[0].stop()
                scanTextData.codeReader.reset()
            } else {
    
    
                if (scanTextData.codeReader === null) {
    
    
                    scanTextData.codeReader = new BrowserMultiFormatReader()
                }
                this.openScan()
            }
        }
    },
    methods: {
    
    
        async openScan () {
    
    
            scanTextData.codeReader.getVideoInputDevices().then((videoInputDevices) => {
    
    
                // 默认获取第一个摄像头设备id
                let firstDeviceId = videoInputDevices[0].deviceId
                // 获取第一个摄像头设备的名称
                const videoInputDeviceslablestr = JSON.stringify(videoInputDevices[0].label)
                if (videoInputDevices.length > 1) {
    
    
                    // 判断是否后置摄像头
                    if (videoInputDeviceslablestr.indexOf('back') > -1) {
    
    
                        firstDeviceId = videoInputDevices[0].deviceId
                    } else {
    
    
                        firstDeviceId = videoInputDevices[1].deviceId
                    }
                }
                this.decodeFromInputVideoFunc(firstDeviceId)
            }).catch(err => {
    
    
                console.error(err)
            })
        },
        decodeFromInputVideoFunc (firstDeviceId) {
    
    
            scanTextData.codeReader.reset()
            scanTextData.codeReader.decodeFromInputVideoDeviceContinuously(firstDeviceId, 'video', (result, err) => {
    
    
                if (result && result.text) {
    
    
                    this.handleResult(result.text)
                }
                if (err && !(err)) {
    
    
                    this.$toast.fail(err)
                }
            })
        },
        async handleResult (scanResult) {
    
    
            // TODO 逻辑处理或直接返回扫码结果
            this.$emit('getScanResult', scanResult)
        }
    }
}
</script>

<style lang="less" scoped>
.scan-box {
    
    
    position: fixed;
    top: 0;
    left: 0;
    height: 100%;
    width: 100vw;
}

.scan-video {
    
    
    height: 100vh;
    width: 100vw;
    object-fit: cover;
}

.scan-img {
    
    
    width: 500px;
    height: 500px;
    position: fixed;
    top: 40%;
    left: 50%;
    margin-top: -200px;
    margin-left: -250px;
    z-index: 6;

    .scan-frame {
    
    
        width: 100%;
        height: 100%;
        position: relative;

        .left-t,
        .right-t,
        .left-b,
        .right-b {
    
    
            position: absolute;
            width: 80px;
            height: 80px;
        }

        .left-t {
    
    
            top: 0;
            left: 0;
            border-top: 2px solid #17B1B7;
            border-left: 2px solid #17B1B7;
        }

        .right-t {
    
    
            top: 0;
            right: 0;
            border-top: 2px solid #17B1B7;
            border-right: 2px solid #17B1B7;
        }

        .left-b {
    
    
            bottom: 0;
            left: 0;
            border-bottom: 2px solid #17B1B7;
            border-left: 2px solid #17B1B7;
        }

        .right-b {
    
    
            bottom: 0;
            right: 0;
            border-bottom: 2px solid #17B1B7;
            border-right: 2px solid #17B1B7;
        }

        .cross-line {
    
    
            width: 300px;
            height: 10px;
            background: linear-gradient(to right, rgba(255, 255, 255, 0), #5DDDD3, rgba(255, 255, 255, 0));
            position: absolute;
            top: 0;
            left: 20%;
            animation: identifier_p 5s infinite;
        }

        @keyframes identifier_p {
    
    
            0% {
    
    
                top: 0%;
            }

            50% {
    
    
                top: 82%;
            }

            100% {
    
    
                top: 0;
            }
        }
    }
}

.scan-tip {
    
    
    width: 100vw;
    text-align: center;
    margin-bottom: 5vh;
    color: white;
    font-size: 5vw;
    position: absolute;
    bottom: 50px;
    left: 0;
    color: #fff;
}

.page-scan {
    
    
    overflow-y: hidden;
}
</style>

Guess you like

Origin blog.csdn.net/weixin_46099269/article/details/125800391