【问题描述】: 需要通过手机浏览器,调用摄像头并扫描物料的二维码记录信息并录入数据库
【解决思路】: 引用 @zxing

公网或者内网ip使用需要配置ssl证书,否则无法调用摄像头获取视频流~


散热器扫描
散热器扫描
配件扫描
配件扫描

废话不多说直接上代码 = =



<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />

    <meta name="yhf" content="ZXing for JS">
    <title>物料扫码识别</title>
    <link rel="stylesheet" rel="preload" as="style" onload="this.rel='stylesheet';this.onload=null"
        href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
    <link rel="stylesheet" rel="preload" as="style" onload="this.rel='stylesheet';this.onload=null"
        href="https://unpkg.com/normalize.css@8.0.0/normalize.css">
    <link rel="stylesheet" rel="preload" as="style" onload="this.rel='stylesheet';this.onload=null"
        href="https://unpkg.com/milligram@1.3.0/dist/milligram.min.css">
    <!-- 引入 layui.css -->
    <link rel="stylesheet" href="//unpkg.com/layui@2.6.8/dist/css/layui.css">

    <style>
        #show {
            position: fixed;
            width: 100%;
            height: 100vh;
            align-items: center;
            justify-content: center;
            background-color: #fff;
            text-align: center;
            top: -2000px;
            left: 0;
        }

        li {
            display: flex;
            padding: 5px 10px;
            align-items: center;
            justify-content: space-between;
        }

        .btns {
            padding: 20px 0;
            position: fixed;
            bottom: 0;
            display: flex;
            justify-content: space-around;
            box-shadow: 0px 0px 5px 0px #efefef;
            width: 100%;
            background-color: #fff;
        }

        #cancel {
            padding: 20px 0;
            position: fixed;
            bottom: 0;
            display: none;
            justify-content: space-around;
            box-shadow: 0px 0px 5px 0px #efefef;
            width: 100%;
            background-color: #fff;
            z-index: 9999;
        }

        #sourceSelectPanel {
            position: fixed;
            left: 3000px;
        }

        .layui-input {
            display: inline-block;
            width: 40px !important;
        }

        video {
            width: 100%;
            height: 100%;
            margin-top: -100px;
        }

        .layui-table-cell {
            padding: 0px 5px;
        }
        .layui-table-view{
            margin-bottom: 100px;
        }
    </style>
</head>

<body>

    <main class="wrapper" style="padding-top:2em">

        <section class="container" id="demo-content">

            <div class="layui-card">
                <div class="layui-card-header">
                    <h3>基本信息</h1>
                </div>
                <div class="layui-card-body" id="baseInfo">
                    <label>料号:</label>
                    <label>名称:</label>
                    <label>批次号:</label>
                </div>
            </div>

            <ul id="list">

            </ul>
            <table class="layui-hide" id="test"></table>
            <div id="show">
                <!-- </div> -->
                <video id="video" width="300" height="200"></video>
            </div>

            <div id="sourceSelectPanel" style="display:none">
                <label for="sourceSelect">Change video source:</label>
                <select id="sourceSelect" style="max-width:400px">
                </select>
            </div>

            <!-- <div style="display: table">
                <label for="decoding-style"> Decoding Style:</label>
                <select id="decoding-style" size="1">
                    <option value="continuously">Decode continuously</option>
                    <option value="once">Decode once</option>
                </select>
            </div>

            <label>Result:</label>
            <pre><code id="result"></code></pre> -->


        </section>

        <div class="btns" id="btns">
            <button type="button" onClick="start(true)" class="layui-btn layui-btn-sm" id="startButton">散热器扫描</button>
            <button type="button" onClick="start(false)" class="layui-btn layui-btn-sm">配件扫描</button>
            <button type="button" onclick="submit()" class="layui-btn layui-btn-sm">提交</button>
        </div>
        <div id="cancel">
            <button type="button" class="layui-btn layui-btn-sm" id="resetButton">取消扫描</button>
        </div>

    </main>

    <script type="text/html" id="barDemo">
        <a class="layui-btn layui-btn-xs" lay-event="del">减</a>
        <!-- <input id={{d.value}} type="number" name={{d.value}} autocomplete='off' value={{d.count}} lay-event='tabInput' class="layui-input"> -->
        <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="add">加</a>
      </script>
    <!-- 引入 layui.js -->
    <script src="https://unpkg.com/layui@2.8.0-rc.2/dist/layui.js"></script>
    <script type="text/javascript" src="https://unpkg.com/@zxing/library@latest"></script>
    <script type="text/javascript">
        //列表数据
        let list = [
          
        ]
        //基本信息
        let baseInfo = {}
        //实例化BrowserQRCodeReader 
        const codeReader = new ZXing.BrowserQRCodeReader()
        //扫码类型
        let type = true
        //初始化 开始扫码事件
        let start = null;

        //单次识别 回调结果 二维码信息
        function decodeOnce(codeReader, selectedDeviceId) {
            codeReader.decodeFromInputVideoDevice(selectedDeviceId, 'video').then((result) => {
                console.log('Found QR code!', result.text)
                //根据返回的result 编写 业务逻辑
                if (type) {
                    if (result.text.includes('Supplier Code')) {
                        renderBaseInfo(result.text)
                        closeCamera()
                    }
                } else {
                    if (result.text.includes('code')) {
                        handleList(result.text)
                        closeCamera()
                    }
                }
            }).catch((err) => {
                console.error(err)
                document.getElementById('result').textContent = err
            })
        }

        //持续识别 回调结果 二维码信息
        function decodeContinuously(codeReader, selectedDeviceId) {
            codeReader.decodeFromInputVideoDeviceContinuously(selectedDeviceId, 'video', (result, err) => {
                if (result) {
                    console.log('Found QR code!', result.text)
                    //根据返回的result 编写 业务逻辑
                    if (type) {
                        if (result.text.includes('Supplier Code')) {
                            renderBaseInfo(result.text)
                            closeCamera()
                        }
                    } else {
                        if (result.text.includes('code')) {
                            handleList(result.text)
                            closeCamera()
                        }
                    }
                }
                if (err) {
                    // As long as this error belongs into one of the following categories
                    // the code reader is going to continue as excepted. Any other error
                    // will stop the decoding loop.
                    //
                    // Excepted Exceptions:
                    //
                    //  - NotFoundException
                    //  - ChecksumException
                    //  - FormatException

                    if (err instanceof ZXing.NotFoundException) {
                        console.log('No QR code found.')
                    }

                    if (err instanceof ZXing.ChecksumException) {
                        console.log('A code was found, but it\'s read value was not valid.')
                    }

                    if (err instanceof ZXing.FormatException) {
                        console.log('A code was found, but it was in a invalid format.')
                    }
                }
            })
        }


        //关闭摄像头 并移除摄像头显示
        function closeCamera() {
            codeReader.reset()
            document.getElementById('show').style.top = '-2000px'
            document.getElementById('cancel').style = 'none'
        }


        window.addEventListener('load', function () {
            //初始化 列表demo数据
            renderList()

            let selectedDeviceId;

            console.log('ZXing code reader initialized')

            //获取视频流 异步
            codeReader.getVideoInputDevices()
                .then((videoInputDevices) => {
                    //选择摄像头设备来源
                    const sourceSelect = document.getElementById('sourceSelect')
                    selectedDeviceId = videoInputDevices[0].deviceId
                    if (videoInputDevices.length >= 1) {
                        videoInputDevices.forEach((element) => {
                            const sourceOption = document.createElement('option')
                            sourceOption.text = element.label
                            sourceOption.value = element.deviceId
                            sourceSelect.appendChild(sourceOption)
                        })

                        sourceSelect.onchange = () => {
                            selectedDeviceId = sourceSelect.value;
                        };

                        const sourceSelectPanel = document.getElementById('sourceSelectPanel')
                        sourceSelectPanel.style.display = 'block'
                    }

                    // 开始扫描二维码
                    start = function (data) {
                        type = data
                        document.getElementById('show').style.top = '0'
                        document.getElementById('cancel').style.display = 'flex'

                        //这里其实是配置项 被强制去除了  默认是连续识别二维码,上述html中decoding-style 可以取消注释获取 识别类型方式
                        const decodingStyle = null;

                        if (decodingStyle == "once") {
                            decodeOnce(codeReader, selectedDeviceId);
                        } else {
                            decodeContinuously(codeReader, selectedDeviceId);
                        }

                        console.log(`Started decode from camera with id ${selectedDeviceId}`)
                    }

                    //取消扫描界面并关闭摄像头
                    document.getElementById('resetButton').addEventListener('click', () => {
                        closeCamera()
                    })

                })
                .catch((err) => {
                    console.error(err)
                })
        })


        //根据list 渲染列表
        function renderList() {
            console.log(list);
            //layUI 的列表 可以使用其他ui组件 原理相同
            layui.use('table', function () {
                var table = layui.table;
                table.render({
                    elem: '#test'
                    , data: list
                    , cellMinWidth: 80,
                    limit: 1000
                    , cols: [[
                        { field: 'value', title: '料号', width: 100 },
                        { field: 'name', title: '名称' },
                        { field: 'count', title: '数量', with: 50, edit: 'number' },
                        { field: 'options', title: '操作', toolbar: '#barDemo' }
                    ]]
                });

                //监听行工具事件
                table.on('tool(test)', function (obj) {
                    let data = obj.data;
                    let index = obj.tr.attr("data-index")
                    //console.log(obj)
                    if (obj.event === 'add') {
                        console.log(data);
                        list[index].count++
                    } else if (obj.event === 'del') {
                        console.log(data);
                        if (list[index].count === 1) {
                            list.splice(index, 1)
                        } else {
                            list[index].count--
                        }
                    }
                    renderList()

                });

                table.on('edit(test)', function (obj) {
                    var value = obj.value //得到修改后的值
                        , data = obj.data //得到所在行所有键值
                        , field = obj.field; //得到字段
                    let index = obj.tr.attr("data-index")
                    console.log(value, data, field);

                    list[index].count = value
                    renderList()

                });
            });
        }


        //根据baseInfo渲染基本信息
        function renderBaseInfo(result) {
            baseInfo = String2Json(result);
            document.getElementById('baseInfo').innerHTML = `
                <label>料号: ${baseInfo['Type']}</label>
                <label>名称: ${baseInfo['Name']}</label>
                <label>批次号: ${baseInfo['Batch No']}</label>
            `
        }

        function submit() {
            console.log(baseInfo, list);
        }


        // Name:Radiator,Type:1002915397,Batch No:221130004,Supplier Code:804C 字符串解析为JSon
        function String2Json(string) {
            let arr = string.split(',')
            let obj = {}
            arr.map(item => {
                let KeyValueList = item.split(":")
                obj[KeyValueList[0]] = KeyValueList[1]
            })
            return obj
        }

        // "code:16608052020,name:侧卸油缸~LGC50D-730000~装配图" 处理数据
        function handleList(result) {
            let item = String2Json(result)
            let isExit = false
            if (list && list.length > 0) {
                for (let i = 0; i < list.length; i++) {
                    if (list[i].name === item['name']) {
                        list[i].count++
                        isExit = true
                    }
                }
            }
            let obj = {}
            obj['name'] = item.name
            obj['value'] = item.code
            obj['count'] = 1
            if (!isExit) {
                list.push(obj)
            }

            console.log(list);
            renderList()
        }



    </script>

</body>

</html>
Last modification:March 5, 2023
如果觉得我的文章对你有用,请随意赞赏