聚水潭API数据接口开发手机端网页查询商品仓位库位库存工具,支持扫描识别,预览图片
视频右边是手机端实际效果演示,含条形码识别演示
手机端产品库位查询工具
用聚水潭申请的接口写的手机端网页。 用途是给仓库的人员方便在手机查询产品的仓位、数量、核对实物。
1、支持扫描货品的商品编码进行查询,免去人工输入的繁琐。
2、支持图片放大预览,双指缩放图片大小,单指拖动图片
前端用AI写的,条形码识别要用到quagga包。后端要去聚水潭申请拿到access_token,具体看聚水潭开放平台文档,我用的是商家自研的授权。
项目用inscode开发的,用Express框架写好代码,直接部署就能在公网访问了。
服务端响应格式
{"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代码
<!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;}});// 关键修复:如果是新查询,重置当前页码为1if (isNewQuery) {currentPage = 1;}loadingElement.style.display = 'block';resultContainer.style.display = 'none';errorElement.style.display = 'none';resultList.innerHTML = '';paginationContainer.innerHTML = '';pageInfoElement.textContent = '';// 构建API请求URLlet 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';// 检查浏览器是否支持getUserMediaif (!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();});};// 初始化QuaggainitQuagga();}).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>