聚水潭API数据接口开发手机端网页查询商品仓位库位库存工具,支持扫描识别,预览图片

视频右边是手机端实际效果演示,含条形码识别演示

手机端产品库位查询工具

用聚水潭申请的接口写的手机端网页。 用途是给仓库的人员方便在手机查询产品的仓位、数量、核对实物。

1、支持扫描货品的商品编码进行查询,免去人工输入的繁琐。

2、支持图片放大预览,双指缩放图片大小,单指拖动图片

前端用AI写的,条形码识别要用到quagga包。后端要去聚水潭申请拿到access_token,具体看聚水潭开放平台文档,我用的是商家自研的授权。

项目用inscode开发的,用Express框架写好代码,直接部署就能在公网访问了。

服务端响应格式

html 复制代码
{
    "code": 0,
    "msg": "",
    "count": 1,
    "data": [
        {
            "pic": "图片url",
            "i_id": "BGD25070201",
            "labels": "客户定制",
            "sku_id": "BGD25070201-客订巴塔那护发精油60ml-LL",
            "name": "客订巴塔那护发精油60ml",
            "properties_value": "60ml",
            "sale_price": 1.63,
            "supplier_name": "A-东莞厂",
            "l": 3.8,
            "w": 3.8,
            "h": 11.9,
            "volume": 171.84,
            "unit": "瓶",
            "modified": "2025-08-05 09:28:23.963",
            "creator_name": "王进",
            "created": "2025-07-02 17:26:48.830",
            "is_series_number": false,
            "c_id": 389468118,
            "supplier_id": 16892712,
            "co_id": 12020245,
            "owner_co_id": 0,
            "sku_type": "normal",
            "_primary_key": "BGD25070201-客订巴塔那护发精油60ml-LL",
            "qty": 0,
            "order_lock": 0,
            "purchase_qty": 0,
            "bin": "D-1-25",
            "supplier_count": 1,
            "sale_price_formula_id": 0
        }
    ]
}

html代码

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="referrer" content="no-referrer">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>查询页面</title>
    <script src="quagga/dist/quagga.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        }
        
        body {
            background-color: #f5f5f5;
            color: #333;
            padding: 8px;
            overflow-x: hidden;
        }
        
        .container {
            max-width: 600px;
            margin: 0 auto;
        }
        
        .header {
            text-align: center;
            margin-bottom: 20px;
        }
        
        .search-form {
            background-color: #fff;
            border-radius: 8px;
            padding: 15px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        
        .form-group {
            margin-bottom: 15px;
            position: relative;
        }
        
        .form-group label {
            display: block;
            margin-bottom: 5px;
            font-size: 14px;
            color: #666;
        }
        
        .form-control {
            width: 100%;
            padding: 12px 40px 12px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 16px;
        }
        
        .scan-icon {
            position: absolute;
            right: 10px;
            top: 67%;
            transform: translateY(-50%);
            background-color: transparent;
            color: white;
            border: none;
            /* border-radius: 50%; */
            width: 30px;
            height: 30px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            font-size: 30px;
        }
        
        .btn {
            width: 100%;
            padding: 12px;
            background-color: #4285f4;
            color: white;
            border: none;
            border-radius: 4px;
            font-size: 16px;
            cursor: pointer;
        }
        
        .btn:hover {
            background-color: #3367d6;
        }
        
        .query-type-group {
            display: flex;
            justify-content: space-between;
            margin-bottom: 15px;
        }
        
        .query-type-option {
            display: flex;
            align-items: center;
        }
        
        .query-type-option input {
            margin-right: 5px;
        }
        
        .result-container {
            margin-top: 20px;
            background-color: #fff;
            border-radius: 8px;
            padding: 15px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            display: none;
        }
        
        .loading {
            text-align: center;
            padding: 20px;
            display: none;
        }
        
        .error {
            color: #d93025;
            padding: 10px;
            background-color: #fce8e6;
            border-radius: 4px;
            margin-top: 10px;
            display: none;
        }
        
        .result-list {
            list-style: none;
        }
        
        .result-item {
            display: flex;
            padding: 15px 0;
            border-bottom: 1px solid #eee;
            gap: 15px;
        }
        
        .result-item:last-child {
            border-bottom: none;
        }
        
        .result-image {
            width: 80px;
            height: 80px;
            flex-shrink: 0;
            background-color: #f0f0f0;
            border-radius: 4px;
            overflow: hidden;
            cursor: pointer;
        }
        
        .result-image img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            transition: transform 0.3s ease;
        }
        
        .result-content {
            flex: 1;
        }
        
        .result-content h3 {
            font-size: 16px;
            margin-bottom: 5px;
            color: #4285f4;
        }
        
        .result-content p {
            font-size: 14px;
            color: #666;
            margin-bottom: 5px;
        }
        
        .pagination {
            display: flex;
            justify-content: center;
            margin-top: 20px;
            flex-wrap: wrap;
        }
        
        .pagination button {
            margin: 2px;
            padding: 5px 10px;
            background-color: #f0f0f0;
            border: 1px solid #ddd;
            border-radius: 4px;
            cursor: pointer;
        }
        
        .pagination button.active {
            background-color: #4285f4;
            color: white;
            border-color: #4285f4;
        }
        
        .pagination button:hover:not(.active) {
            background-color: #e0e0e0;
        }
        
        .pagination button:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }
        
        .page-info {
            text-align: center;
            margin-top: 10px;
            font-size: 14px;
            color: #666;
        }
        
        .scanner-modal {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.8);
            display: none;
            align-items: center;
            justify-content: center;
            z-index: 1000;
        }
        
        .scanner-container {
            width: 100%;
            max-width: 100%;
            height: 70vh;
            position: relative;
            background-color: #000;
            overflow: hidden;
        }
        
        .scanner-video {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }
        
        .scanner-overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: transparent;
            border: 2px solid rgba(0, 255, 0, 0.5);
            box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.7);
            pointer-events: none;
        }
        
        .scanner-guide {
            position: absolute;
            top: 10%;
            left: 10%;
            width: 80%;
            height: 2px;
            background: rgba(0, 255, 0, 0.5);
            animation: scan 2s infinite linear;
        }
        
        @keyframes scan {
            0% { top: 10%; }
            100% { top: 90%; }
        }
        
        .scanner-cancel {
            position: absolute;
            top: 20px;
            right: 20px;
            background-color: rgba(0, 0, 0, 0.5);
            color: white;
            border: none;
            border-radius: 50%;
            width: 40px;
            height: 40px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            font-size: 18px;
            z-index: 1001;
        }
        
        .scanner-result {
            position: absolute;
            bottom: 20px;
            left: 0;
            width: 100%;
            text-align: center;
            color: white;
            background-color: rgba(0, 0, 0, 0.5);
            padding: 15px;
            font-size: 16px;
            display: none;
            z-index: 1001;
        }
        
        .image-preview-modal {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.9);
            display: none;
            align-items: center;
            justify-content: center;
            z-index: 2000;
            opacity: 0;
            transition: opacity 0.3s ease;
        }
        
        .image-preview-modal.active {
            opacity: 1;
        }
        
        .image-preview-container {
            position: relative;
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            touch-action: none;
        }
        
        .image-preview-img {
            max-width: 100%;
            max-height: 100%;
            transform: scale(1);
            transition: transform 0.3s ease;
            will-change: transform;
        }
        
        .image-preview-close {
            position: absolute;
            top: 20px;
            right: 20px;
            background-color: rgba(0, 0, 0, 0.5);
            color: white;
            border: none;
            border-radius: 50%;
            width: 40px;
            height: 40px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            font-size: 18px;
            z-index: 2001;
        }
    </style>
</head>
<body>
    <div class="container">
        <!-- <div class="header">
            <h1>信息查询</h1>
        </div> -->
        
        <div class="search-form">
            <div class="query-type-group">
                <div class="query-type-option">
                    <input type="radio" id="style-code" name="query-type" value="iid" checked>
                    <label for="style-code">款式编码</label>
                </div>
                <div class="query-type-option">
                    <input type="radio" id="product-code" name="query-type" value="skuId">
                    <label for="product-code">商品编码</label>
                </div>
                <div class="query-type-option">
                    <input type="radio" id="product-name" name="query-type" value="name">
                    <label for="product-name">商品名称</label>
                </div>
            </div>
            
            <div class="form-group">
                <label for="query">请输入查询内容</label>
                <input type="text" id="query" class="form-control" placeholder="例如:商品编码、或商品名称等">
                <button class="scan-icon" id="scan-btn"><svg class="icon" style="width: 1.0498046875em;height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1075 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14811"><path d="M668.45257166 923.42857133v-55.247238h221.037714v-221.062095H944.76190466V923.42857133zM115.80952366 923.42857133V647.11923833h55.271619v221.062095h221.062095V923.42857133z m731.428572-97.523809H213.33333366V582.09523833h633.904762v243.809524zM115.80952366 536.60038133v-55.271619h828.952381v55.271619zM847.23809566 435.80952333H213.33333366V192.00000033h633.904762v243.809523z m42.300952-65.024V149.72342833h-221.062095V94.47619033H944.76190466v276.309333z m-773.705143 0V94.47619033h276.333714v55.247238H171.10552366v221.062095z" fill="#333333" p-id="14812"></path></svg></button>
            </div>
            
            <button id="search-btn" class="btn">查询</button>
        </div>
        
        <div class="loading" id="loading">
            <p>正在查询中,请稍候...</p>
        </div>
        
        <div class="error" id="error">
            查询失败,请检查输入或稍后重试
        </div>
        
        <div class="result-container" id="result">
            <!-- <h2>查询结果</h2> -->
            <ul class="result-list" id="result-list"></ul>
            <div class="pagination" id="pagination"></div>
            <div class="page-info" id="page-info"></div>
        </div>
    </div>
    
    <div class="scanner-modal" id="scanner-modal">
        <div class="scanner-container">
            <video class="scanner-video" id="scanner-video" playsinline></video>
            <div class="scanner-overlay"></div>
            <div class="scanner-guide"></div>
            <button class="scanner-cancel" id="scanner-cancel">✕</button>
            <div class="scanner-result" id="scanner-result"></div>
        </div>
    </div>
    
    <div class="image-preview-modal" id="image-preview-modal">
        <button class="image-preview-close" id="image-preview-close">✕</button>
        <div class="image-preview-container">
            <img class="image-preview-img" id="image-preview-img" src="" alt="预览图片">
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const searchBtn = document.getElementById('search-btn');
            const queryInput = document.getElementById('query');
            const resultContainer = document.getElementById('result');
            const resultList = document.getElementById('result-list');
            const loadingElement = document.getElementById('loading');
            const errorElement = document.getElementById('error');
            const scanBtn = document.getElementById('scan-btn');
            const scannerModal = document.getElementById('scanner-modal');
            const scannerVideo = document.getElementById('scanner-video');
            const scannerCancel = document.getElementById('scanner-cancel');
            const scannerResult = document.getElementById('scanner-result');
            const paginationContainer = document.getElementById('pagination');
            const pageInfoElement = document.getElementById('page-info');
            
            const imagePreviewModal = document.getElementById('image-preview-modal');
            const imagePreviewImg = document.getElementById('image-preview-img');
            const imagePreviewClose = document.getElementById('image-preview-close');
            
            const queryTypeRadios = document.querySelectorAll('input[name="query-type"]');
            
            // 分页相关变量
            let currentPage = 1;
            let totalPages = 1;
            const itemsPerPage = 90; // 每页90条记录
            
            // 图片预览相关变量
            let currentScale = 1;
            let currentTranslateX = 0;
            let currentTranslateY = 0;
            let isDragging = false;
            let startX, startY;
            let initialTranslateX, initialTranslateY;
            let initialDistance = 0;
            
            // 条形码扫描相关变量
            let scannerRunning = false;
            let mediaStream = null;
            
            // 点击扫描图标打开扫描模态框
            scanBtn.addEventListener('click', function() {
                startScanner();
            });
            
            // 关闭扫描模态框
            scannerCancel.addEventListener('click', function() {
                stopScanner();
                scannerModal.style.display = 'none';
            });
            
            // 点击查询按钮执行查询
            searchBtn.addEventListener('click', function() {
                performSearch(true); // 传递true表示是新查询
            });
            
            // 执行查询的函数
            function performSearch(isNewQuery = true) {
                const query = queryInput.value.trim();
                
                let selectedQueryType = 'iid';
                queryTypeRadios.forEach(radio => {
                    if (radio.checked) {
                        selectedQueryType = radio.value;
                    }
                });
                
                // 关键修复:如果是新查询,重置当前页码为1
                if (isNewQuery) {
                    currentPage = 1;
                }
                
                loadingElement.style.display = 'block';
                resultContainer.style.display = 'none';
                errorElement.style.display = 'none';
                resultList.innerHTML = '';
                paginationContainer.innerHTML = '';
                pageInfoElement.textContent = '';
                
                // 构建API请求URL
                let apiUrl;
                if (query) {
                    apiUrl = `${window.origin}/api/GetPageListV2?page=${currentPage}&limit=${itemsPerPage}&${selectedQueryType}=${encodeURIComponent(query)}`;
                } else {
                    apiUrl = `${window.origin}/api/GetPageListV2?page=${currentPage}&limit=${itemsPerPage}`;
                }

                // 模拟API请求
                fetch(apiUrl)
                    .then(response => {
                        if (!response.ok) {
                            throw new Error('网络响应不正常');
                        }
                        return response.json();
                    })
                    .then(data => {
                        loadingElement.style.display = 'none';
                        
                        if (data.code === 0 && data.data && data.data.length > 0) {
                            // 计算总页数
                            totalPages = Math.ceil(data.count / itemsPerPage);
                            
                            // 显示分页信息
                            updatePageInfo(data.count);
                            
                            // 渲染分页控件
                            renderPagination();
                            
                            // 显示当前页的数据
                            displayPageResults(data.data);
                            
                            resultContainer.style.display = 'block';
                        } else {
                            errorElement.textContent = data.msg || '没有找到匹配的记录';
                            errorElement.style.display = 'block';
                        }
                    })
                    .catch(error => {
                        loadingElement.style.display = 'none';
                        errorElement.textContent = '查询失败: ' + error.message;
                        errorElement.style.display = 'block';
                        console.error('查询错误:', error);
                    });
            }
            
            // 显示当前页的数据
            function displayPageResults(results) {
                resultList.innerHTML = '';
                
                if (results.length > 0) {
                    results.forEach(function(item) {
                        const li = document.createElement('li');
                        li.className = 'result-item';
                        li.innerHTML = `
                            <div class="result-image">
                                <img src="${item.pic || item.pic_big}" alt="${item.name || ''}">
                            </div>
                            <div class="result-content">
                                <h3>${item.sku_id}</h3>
                                <p><strong>库位:</strong> ${item.bin || ''}</p>
                                <p><strong>库存:</strong> ${item.qty || 0} --- <strong>订单占有:</strong> ${item.order_lock || 0}</p>
                                <p><strong>销售属性:</strong> ${item.properties_value || '未填写'}</p>
                                <p><strong>备注:</strong> ${item.remark || ''}</p>
                            </div>
                        `;
                        resultList.appendChild(li);
                    });
                }
            }
            
            // 更新分页信息
            function updatePageInfo(totalItems) {
                const startItem = (currentPage - 1) * itemsPerPage + 1;
                const endItem = Math.min(currentPage * itemsPerPage, totalItems);
                pageInfoElement.textContent = `显示 ${startItem}-${endItem} 条,共 ${totalItems} 条`;
            }
            
            // 渲染分页控件
            function renderPagination() {
                paginationContainer.innerHTML = '';
                
                // 上一页按钮
                const prevButton = document.createElement('button');
                prevButton.textContent = '上一页';
                prevButton.disabled = currentPage === 1;
                prevButton.addEventListener('click', function() {
                    if (currentPage > 1) {
                        currentPage--;
                        performSearch(false); // 传递false表示是翻页操作
                    }
                });
                paginationContainer.appendChild(prevButton);
                
                // 页码按钮
                const maxVisiblePages = 5;
                let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
                let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
                
                if (endPage - startPage + 1 < maxVisiblePages) {
                    startPage = Math.max(1, endPage - maxVisiblePages + 1);
                }
                
                if (startPage > 1) {
                    const firstPageButton = document.createElement('button');
                    firstPageButton.textContent = '1';
                    firstPageButton.addEventListener('click', function() {
                        currentPage = 1;
                        performSearch(false);
                    });
                    paginationContainer.appendChild(firstPageButton);
                    
                    if (startPage > 2) {
                        const ellipsis = document.createElement('span');
                        ellipsis.textContent = '...';
                        paginationContainer.appendChild(ellipsis);
                    }
                }
                
                for (let i = startPage; i <= endPage; i++) {
                    const pageButton = document.createElement('button');
                    pageButton.textContent = i;
                    if (i === currentPage) {
                        pageButton.classList.add('active');
                    }
                    pageButton.addEventListener('click', (function(page) {
                        return function() {
                            currentPage = page;
                            performSearch(false);
                        };
                    })(i));
                    paginationContainer.appendChild(pageButton);
                }
                
                if (endPage < totalPages) {
                    if (endPage < totalPages - 1) {
                        const ellipsis = document.createElement('span');
                        ellipsis.textContent = '...';
                        paginationContainer.appendChild(ellipsis);
                    }
                    
                    const lastPageButton = document.createElement('button');
                    lastPageButton.textContent = totalPages;
                    lastPageButton.addEventListener('click', function() {
                        currentPage = totalPages;
                        performSearch(false);
                    });
                    paginationContainer.appendChild(lastPageButton);
                }
                
                // 下一页按钮
                const nextButton = document.createElement('button');
                nextButton.textContent = '下一页';
                nextButton.disabled = currentPage === totalPages;
                nextButton.addEventListener('click', function() {
                    if (currentPage < totalPages) {
                        currentPage++;
                        performSearch(false);
                    }
                });
                paginationContainer.appendChild(nextButton);
            }
            
            // 打开图片预览
            function openImagePreview(src) {
                imagePreviewImg.src = src;
                imagePreviewModal.style.display = 'flex';
                setTimeout(() => {
                    imagePreviewModal.classList.add('active');
                }, 10);
                
                resetImageTransform();
            }
            
            // 关闭图片预览
            function closeImagePreview() {
                imagePreviewModal.classList.remove('active');
                setTimeout(() => {
                    imagePreviewModal.style.display = 'none';
                }, 300);
            }
            
            // 重置图片变换
            function resetImageTransform() {
                currentScale = 1;
                currentTranslateX = 0;
                currentTranslateY = 0;
                updateImageTransform();
            }
            
            // 更新图片变换
            function updateImageTransform() {
                imagePreviewImg.style.transform = `scale(${currentScale}) translate(${currentTranslateX}px, ${currentTranslateY}px)`;
            }
            
            // 触摸事件处理
            imagePreviewImg.addEventListener('touchstart', function(e) {
                if (e.touches.length === 1) {
                    // 单指触摸 - 准备拖动
                    isDragging = true;
                    startX = e.touches[0].clientX;
                    startY = e.touches[0].clientY;
                    initialTranslateX = currentTranslateX;
                    initialTranslateY = currentTranslateY;
                } else if (e.touches.length === 2) {
                    // 双指触摸 - 准备缩放
                    isDragging = false;
                    
                    // 计算两指之间的距离
                    const touch1 = e.touches[0];
                    const touch2 = e.touches[1];
                    const distance = Math.hypot(
                        touch2.clientX - touch1.clientX,
                        touch2.clientY - touch1.clientY
                    );
                    
                    // 存储初始距离用于计算缩放比例
                    e.target.dataset.initialDistance = distance;
                }
            }, { passive: false });
            
            imagePreviewImg.addEventListener('touchmove', function(e) {
                if (isDragging && e.touches.length === 1) {
                    // 单指拖动
                    const deltaX = e.touches[0].clientX - startX;
                    const deltaY = e.touches[0].clientY - startY;
                    
                    currentTranslateX = initialTranslateX + deltaX;
                    currentTranslateY = initialTranslateY + deltaY;
                    
                    updateImageTransform();
                } else if (e.touches.length === 2) {
                    // 双指缩放
                    const touch1 = e.touches[0];
                    const touch2 = e.touches[1];
                    const currentDistance = Math.hypot(
                        touch2.clientX - touch1.clientX,
                        touch2.clientY - touch1.clientY
                    );
                    
                    // 获取初始距离
                    if (e.target.dataset.initialDistance) {
                        const initialDistance = parseFloat(e.target.dataset.initialDistance);
                        
                        // 计算缩放比例
                        if (initialDistance > 0) {
                            const scale = currentScale * (currentDistance / initialDistance);
                            
                            // 限制缩放范围
                            currentScale = Math.max(0.5, Math.min(3, scale));
                            
                            updateImageTransform();
                        }
                    }
                }
            }, { passive: false });
            
            imagePreviewImg.addEventListener('touchend', function() {
                isDragging = false;
            }, { passive: false });
            
            // 关闭按钮点击事件
            imagePreviewClose.addEventListener('click', closeImagePreview);
            
            // 点击模态框背景关闭预览
            imagePreviewModal.addEventListener('click', function(e) {
                if (e.target === imagePreviewModal) {
                    closeImagePreview();
                }
            });
            
            // 使用事件委托处理图片点击事件
            document.addEventListener('click', function(e) {
                // 检查点击的是否是结果图片
                if (e.target.closest('.result-image img')) {
                    const img = e.target.closest('.result-image img');
                    openImagePreview(img.src);
                }
            });
            
            // 开始扫描
            function startScanner() {
                if (scannerRunning) return;
                
                scannerRunning = true;
                scannerResult.style.display = 'none';
                scannerModal.style.display = 'flex';
                
                // 检查浏览器是否支持getUserMedia
                if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
                    showScannerError("您的浏览器不支持摄像头访问");
                    return;
                }
                
                // 请求摄像头权限
                navigator.mediaDevices.getUserMedia({
                    video: {
                        facingMode: 'environment',
                        width: { ideal: 1280 },
                        height: { ideal: 720 }
                    },
                    audio: false
                }).then(function(stream) {
                    mediaStream = stream;
                    scannerVideo.srcObject = stream;
                    
                    // 确保视频播放
                    scannerVideo.onloadedmetadata = function() {
                        scannerVideo.play().catch(e => {
                            showScannerError("无法播放视频流: " + e.message);
                            stopScanner();
                        });
                    };
                    
                    // 初始化Quagga
                    initQuagga();
                }).catch(function(err) {
                    showScannerError("无法访问摄像头: " + (err.message || "用户拒绝授权"));
                    stopScanner();
                });
            }
            
            function initQuagga() {
                Quagga.init({
                    inputStream: {
                        name: "Live",
                        type: "LiveStream",
                        target: scannerVideo,
                        constraints: {
                            width: { min: 640 },
                            height: { min: 480 },
                            facingMode: "environment",
                            aspectRatio: { min: 1, max: 2 }
                        },
                    },
                    decoder: {
                        readers: [
                            "ean_reader",
                            "ean_8_reader",
                            "code_128_reader",
                            "code_39_reader",
                            "code_39_vin_reader",
                            "codabar_reader",
                            "upc_reader",
                            "upc_e_reader"
                        ],
                        debug: {
                            drawBoundingBox: false,
                            showFrequency: false,
                            drawScanline: false,
                            showPattern: false
                        }
                    },
                    locator: {
                        patchSize: "medium",
                        halfSample: true
                    },
                    numOfWorkers: 4,
                    frequency: 10,
                    debug: false
                }, function(err) {
                    if (err) {
                        showScannerError("初始化扫描器失败: " + (err.message || "未知错误"));
                        stopScanner();
                        return;
                    }
                    
                    Quagga.start();
                    
                    // 监听扫描结果
                    Quagga.onDetected(function(result) {
                        if (result.codeResult) {
                            const code = result.codeResult.code;
                            scannerResult.textContent = "扫描结果: " + code;
                            scannerResult.dataset.barcode = code;
                            scannerResult.style.display = 'block';
                            
                            // 停止扫描
                            stopScanner();
                            scannerModal.style.display = 'none';
                            
                            // 自动填充输入框并执行查询
                            queryInput.value = code;
                            performSearch();
                        }
                    });
                });
            }
            
            function showScannerError(message) {
                scannerResult.textContent = message;
                scannerResult.style.display = 'block';
                scannerRunning = false;
            }
            
            // 停止扫描
            function stopScanner() {
                if (!scannerRunning) return;
                
                try {
                    if (Quagga) {
                        Quagga.stop();
                    }
                    
                    if (mediaStream) {
                        mediaStream.getTracks().forEach(track => track.stop());
                        mediaStream = null;
                    }
                    
                    scannerVideo.srcObject = null;
                } catch (e) {
                    console.error("停止扫描时出错:", e);
                }
                
                scannerRunning = false;
            }
        });
    </script>
</body>
</html>