const qs = (s) => document.querySelector(s); const results = qs('#results'); const input = qs('#q'); const searchBtn = qs('#searchBtn'); const fileInput = qs('#file'); const importBtn = qs('#importBtn'); const importStatus = qs('#importStatus'); const tabs = document.querySelectorAll('.tab'); const searchSection = qs('#searchSection'); const importSection = qs('#importSection'); const scanBtn = qs('#scanBtn'); const scannerModal = qs('#scannerModal'); const closeScanner = qs('#closeScanner'); let scannerInst = null; let videoEl = null; let mediaStream = null; // 无分页模式 let currentTab = 'search'; let limit = 10000; let currentQuery = ''; function setTab(tab){ currentTab = tab; tabs.forEach(t=>{ const active = t.dataset.tab===tab; t.classList.toggle('active', active); t.setAttribute('aria-selected', String(active)); }); if(tab==='search'){ searchSection.classList.remove('hidden'); importSection.classList.add('hidden'); } else { importSection.classList.remove('hidden'); searchSection.classList.add('hidden'); } } tabs.forEach(t=>t.addEventListener('click',()=>setTab(t.dataset.tab))); setTab('search'); async function fetchResults(){ results.innerHTML = '
加载中...
'; try{ const r = await fetch(`/products?q=${encodeURIComponent(currentQuery)}&limit=${limit}`); const d = await r.json(); render(d); }catch(e){ results.innerHTML = '
请求失败
'; } } function search(){ currentQuery = input.value.trim(); fetchResults(); } function render(items) { if (!items || items.length === 0) { results.innerHTML = '
无结果
'; return; } const term = currentQuery; const esc = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const re = term ? new RegExp(esc, 'ig') : null; results.innerHTML = items.map(x => { const name = x.name ?? ''; const purchase = x.purchase_price ?? ''; const sale = x.sale_price ?? ''; const barcode = x.barcode ?? ''; const nameH = re ? name.replace(re, m=>`${m}`) : name; const barcodeH = re ? barcode.replace(re, m=>`${m}`) : barcode; return `
${nameH}
卖价:${sale}
进价:${purchase}
条码:${barcodeH}
`; }).join(''); // 无分页模式 } searchBtn.addEventListener('click', search); input.addEventListener('keydown', e => { if (e.key === 'Enter') search(); }); async function openScanner(){ scannerModal.classList.remove('hidden'); try{ if('BarcodeDetector' in window){ const formats = ['ean_13','ean_8','code_128','code_39','upc_a','upc_e']; const detector = new window.BarcodeDetector({ formats }); mediaStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }); videoEl = document.createElement('video'); videoEl.autoplay = true; videoEl.playsInline = true; videoEl.srcObject = mediaStream; const host = document.getElementById('scanner'); host.innerHTML = ''; host.appendChild(videoEl); await videoEl.play(); const loop = async()=>{ try{ const codes = await detector.detect(videoEl); if(codes && codes.length){ const text = codes[0].rawValue; input.value = text; currentQuery = text; fetchResults(); closeScannerFn(); return; } }catch(_){ } if(!scannerModal.classList.contains('hidden')) requestAnimationFrame(loop); }; requestAnimationFrame(loop); } else { scannerModal.classList.add('hidden'); } }catch(e){ scannerModal.classList.add('hidden'); } } function closeScannerFn(){ try{ if(scannerInst){ scannerInst.stop().then(()=>scannerInst.clear()); } }catch(_){ } try{ if(mediaStream){ mediaStream.getTracks().forEach(t=>t.stop()); mediaStream=null; } }catch(_){ } try{ const host = document.getElementById('scanner'); if(host){ host.innerHTML=''; } }catch(_){ } scannerModal.classList.add('hidden'); } scanBtn.addEventListener('click', openScanner); closeScanner.addEventListener('click', closeScannerFn); // 无分页控件 importBtn.addEventListener('click', async () => { const f = fileInput.files?.[0]; if (!f) return; const fd = new FormData(); fd.append('file', f); importStatus.textContent = '导入中...'; try { const r = await fetch('/import', { method: 'POST', body: fd }); const d = await r.json(); importStatus.textContent = `插入: ${d.inserted ?? 0},更新: ${d.updated ?? 0},跳过: ${d.skipped ?? 0}`; } catch (e) { importStatus.textContent = '导入失败'; } });