Search-Goods/static/app.js

146 lines
4.8 KiB
JavaScript

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 = '<div class="loading">加载中...</div>';
try{
const r = await fetch(`/products?q=${encodeURIComponent(currentQuery)}&limit=${limit}`);
const d = await r.json();
render(d);
}catch(e){
results.innerHTML = '<div class="empty">请求失败</div>';
}
}
function search(){
currentQuery = input.value.trim();
fetchResults();
}
function render(items) {
if (!items || items.length === 0) {
results.innerHTML = '<div class="empty">无结果</div>';
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=>`<mark>${m}</mark>`) : name;
const barcodeH = re ? barcode.replace(re, m=>`<mark>${m}</mark>`) : barcode;
return `
<div class="card">
<div class="name">${nameH}</div>
<div class="prices">
<div class="price-sale">卖价:${sale}</div>
<div class="price-purchase">进价:${purchase}</div>
</div>
<div class="barcode">条码:${barcodeH}</div>
</div>`;
}).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 = '导入失败';
}
});