修复手机唤醒相机
This commit is contained in:
parent
fcbcdb7f95
commit
116ee0d54a
1
.vercel/project.json
Normal file
1
.vercel/project.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"projectId":"prj_d4Snf7Qo4GMchiUVyezwVqJieNi6","orgId":"team_hyS3Eg5TitOtyQ1YVgGLzKUz","projectName":"trae_table_ls74","neverMindDeployCard":true}
|
||||||
7
.vercelignore
Normal file
7
.vercelignore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
.git
|
||||||
|
.trae
|
||||||
|
.log
|
||||||
|
.figma
|
||||||
@ -14,6 +14,8 @@ const closeScanner = qs('#closeScanner');
|
|||||||
let scannerInst = null;
|
let scannerInst = null;
|
||||||
let videoEl = null;
|
let videoEl = null;
|
||||||
let mediaStream = null;
|
let mediaStream = null;
|
||||||
|
const scannerMessage = qs('#scannerMessage');
|
||||||
|
const captureInput = qs('#captureInput');
|
||||||
// 无分页模式
|
// 无分页模式
|
||||||
|
|
||||||
let currentTab = 'search';
|
let currentTab = 'search';
|
||||||
@ -81,7 +83,15 @@ input.addEventListener('keydown', e => { if (e.key === 'Enter') search(); });
|
|||||||
|
|
||||||
async function openScanner(){
|
async function openScanner(){
|
||||||
scannerModal.classList.remove('hidden');
|
scannerModal.classList.remove('hidden');
|
||||||
|
scannerMessage.textContent = '正在请求摄像头权限...';
|
||||||
|
scannerMessage.classList.remove('hidden');
|
||||||
|
const isSecure = location.protocol === 'https:' || location.hostname === 'localhost';
|
||||||
try{
|
try{
|
||||||
|
if(!isSecure){
|
||||||
|
scannerMessage.textContent = '非安全上下文,已切换为拍照识别方式';
|
||||||
|
captureInput.click();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if('BarcodeDetector' in window){
|
if('BarcodeDetector' in window){
|
||||||
const formats = ['ean_13','ean_8','code_128','code_39','upc_a','upc_e'];
|
const formats = ['ean_13','ean_8','code_128','code_39','upc_a','upc_e'];
|
||||||
const detector = new window.BarcodeDetector({ formats });
|
const detector = new window.BarcodeDetector({ formats });
|
||||||
@ -94,6 +104,7 @@ async function openScanner(){
|
|||||||
host.innerHTML = '';
|
host.innerHTML = '';
|
||||||
host.appendChild(videoEl);
|
host.appendChild(videoEl);
|
||||||
await videoEl.play();
|
await videoEl.play();
|
||||||
|
scannerMessage.classList.add('hidden');
|
||||||
const loop = async()=>{
|
const loop = async()=>{
|
||||||
try{
|
try{
|
||||||
const codes = await detector.detect(videoEl);
|
const codes = await detector.detect(videoEl);
|
||||||
@ -110,10 +121,12 @@ async function openScanner(){
|
|||||||
};
|
};
|
||||||
requestAnimationFrame(loop);
|
requestAnimationFrame(loop);
|
||||||
} else {
|
} else {
|
||||||
scannerModal.classList.add('hidden');
|
scannerMessage.textContent = '浏览器不支持实时扫码,已切换为拍照识别方式';
|
||||||
|
captureInput.click();
|
||||||
}
|
}
|
||||||
}catch(e){
|
}catch(e){
|
||||||
scannerModal.classList.add('hidden');
|
scannerMessage.textContent = '无法启用摄像头,已切换为拍照识别方式';
|
||||||
|
captureInput.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,9 +134,39 @@ function closeScannerFn(){
|
|||||||
try{ if(scannerInst){ scannerInst.stop().then(()=>scannerInst.clear()); } }catch(_){ }
|
try{ if(scannerInst){ scannerInst.stop().then(()=>scannerInst.clear()); } }catch(_){ }
|
||||||
try{ if(mediaStream){ mediaStream.getTracks().forEach(t=>t.stop()); mediaStream=null; } }catch(_){ }
|
try{ if(mediaStream){ mediaStream.getTracks().forEach(t=>t.stop()); mediaStream=null; } }catch(_){ }
|
||||||
try{ const host = document.getElementById('scanner'); if(host){ host.innerHTML=''; } }catch(_){ }
|
try{ const host = document.getElementById('scanner'); if(host){ host.innerHTML=''; } }catch(_){ }
|
||||||
|
try{ if(scannerMessage){ scannerMessage.textContent=''; scannerMessage.classList.add('hidden'); } }catch(_){ }
|
||||||
scannerModal.classList.add('hidden');
|
scannerModal.classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
captureInput.addEventListener('change', async ()=>{
|
||||||
|
const file = captureInput.files?.[0];
|
||||||
|
if(!file){ return; }
|
||||||
|
try{
|
||||||
|
if('BarcodeDetector' in window){
|
||||||
|
const img = await createImageBitmap(file);
|
||||||
|
const detector = new window.BarcodeDetector({ formats: ['ean_13','ean_8','code_128','code_39','upc_a','upc_e'] });
|
||||||
|
const codes = await detector.detect(img);
|
||||||
|
if(codes && codes.length){
|
||||||
|
const text = codes[0].rawValue;
|
||||||
|
input.value = text;
|
||||||
|
currentQuery = text;
|
||||||
|
fetchResults();
|
||||||
|
} else {
|
||||||
|
scannerMessage.textContent = '未识别到条码,请重试或手动输入';
|
||||||
|
scannerMessage.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scannerMessage.textContent = '当前浏览器不支持条码识别,请手动输入';
|
||||||
|
scannerMessage.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}catch(_){
|
||||||
|
scannerMessage.textContent = '识别失败,请重试或手动输入';
|
||||||
|
scannerMessage.classList.remove('hidden');
|
||||||
|
} finally {
|
||||||
|
captureInput.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
scanBtn.addEventListener('click', openScanner);
|
scanBtn.addEventListener('click', openScanner);
|
||||||
closeScanner.addEventListener('click', closeScannerFn);
|
closeScanner.addEventListener('click', closeScannerFn);
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,13 @@
|
|||||||
<div class="search">
|
<div class="search">
|
||||||
<div class="search-input">
|
<div class="search-input">
|
||||||
<input id="q" type="text" placeholder="输入条码或名称,支持前缀/后缀/包含" />
|
<input id="q" type="text" placeholder="输入条码或名称,支持前缀/后缀/包含" />
|
||||||
<button id="scanBtn" class="scan-btn" aria-label="扫码">📷</button>
|
<button id="scanBtn" class="scan-btn" aria-label="扫码" title="扫码">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path d="M4 7a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3V7z" stroke="#444" stroke-width="1.5" fill="#fff"/>
|
||||||
|
<rect x="9" y="9" width="6" height="6" rx="3" stroke="#444" stroke-width="1.5" fill="#fff"/>
|
||||||
|
<path d="M7 4l2 3h6l2-3" stroke="#444" stroke-width="1.5"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button id="searchBtn">查询</button>
|
<button id="searchBtn">查询</button>
|
||||||
</div>
|
</div>
|
||||||
@ -36,10 +42,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="scannerModal" class="modal hidden">
|
<div id="scannerModal" class="modal hidden">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
<div id="scannerMessage" class="scanner-message hidden"></div>
|
||||||
<div id="scanner"></div>
|
<div id="scanner"></div>
|
||||||
<button id="closeScanner">关闭</button>
|
<button id="closeScanner">关闭</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<input id="captureInput" type="file" accept="image/*" capture="environment" class="hidden" />
|
||||||
<script src="/static/app.js"></script>
|
<script src="/static/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -11,7 +11,10 @@ h1{font-size:20px;margin:8px 0 16px}
|
|||||||
input{flex:1;padding:10px;border:1px solid #ccc;border-radius:6px;background:#fff;color:#111}
|
input{flex:1;padding:10px;border:1px solid #ccc;border-radius:6px;background:#fff;color:#111}
|
||||||
.search-input{position:relative;flex:1}
|
.search-input{position:relative;flex:1}
|
||||||
.search-input input{width:100%;padding-right:44px}
|
.search-input input{width:100%;padding-right:44px}
|
||||||
.scan-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);border:0;background:#f0f0f0;color:#111;border-radius:6px;padding:6px 10px}
|
.scan-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);border:1px solid #ddd;background:#fafafa;color:#111;border-radius:6px;padding:6px 10px;display:flex;align-items:center;justify-content:center;cursor:pointer}
|
||||||
|
.scan-btn:hover{background:#f0f0f0}
|
||||||
|
.scan-btn:active{background:#e6e6e6}
|
||||||
|
.scan-btn svg{display:block}
|
||||||
button{padding:10px 14px;border:0;border-radius:6px;background:#1976d2;color:#fff}
|
button{padding:10px 14px;border:0;border-radius:6px;background:#1976d2;color:#fff}
|
||||||
.results{display:flex;flex-direction:column;gap:12px}
|
.results{display:flex;flex-direction:column;gap:12px}
|
||||||
.card{border:1px solid #eee;border-radius:10px;padding:12px;box-shadow:0 1px 2px rgba(0,0,0,0.04)}
|
.card{border:1px solid #eee;border-radius:10px;padding:12px;box-shadow:0 1px 2px rgba(0,0,0,0.04)}
|
||||||
@ -27,6 +30,8 @@ mark{background:#ffec99}
|
|||||||
.loading{color:#1976d2}
|
.loading{color:#1976d2}
|
||||||
.status{margin-top:8px;color:#555}
|
.status{margin-top:8px;color:#555}
|
||||||
.import{margin-top:12px;display:flex;gap:8px;align-items:center}
|
.import{margin-top:12px;display:flex;gap:8px;align-items:center}
|
||||||
|
.scanner-message{margin-bottom:8px;color:#555}
|
||||||
|
.scanner-message.hidden{display:none}
|
||||||
.modal{position:fixed;inset:0;background:rgba(0,0,0,0.4);display:flex;align-items:center;justify-content:center}
|
.modal{position:fixed;inset:0;background:rgba(0,0,0,0.4);display:flex;align-items:center;justify-content:center}
|
||||||
.modal.hidden{display:none}
|
.modal.hidden{display:none}
|
||||||
.modal-content{background:#fff;border-radius:10px;padding:12px;width:92%;max-width:480px}
|
.modal-content{background:#fff;border-radius:10px;padding:12px;width:92%;max-width:480px}
|
||||||
|
|||||||
1
vercel.json
Normal file
1
vercel.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"rewrites":[{"source":"/(.*)","destination":"/index.html"}]}
|
||||||
Loading…
Reference in New Issue
Block a user