修复手机唤醒相机

This commit is contained in:
侯欢 2025-12-07 15:58:35 +08:00
parent fcbcdb7f95
commit 116ee0d54a
6 changed files with 69 additions and 4 deletions

1
.vercel/project.json Normal file
View File

@ -0,0 +1 @@
{"projectId":"prj_d4Snf7Qo4GMchiUVyezwVqJieNi6","orgId":"team_hyS3Eg5TitOtyQ1YVgGLzKUz","projectName":"trae_table_ls74","neverMindDeployCard":true}

7
.vercelignore Normal file
View File

@ -0,0 +1,7 @@
node_modules
build
dist
.git
.trae
.log
.figma

View File

@ -14,6 +14,8 @@ const closeScanner = qs('#closeScanner');
let scannerInst = null;
let videoEl = null;
let mediaStream = null;
const scannerMessage = qs('#scannerMessage');
const captureInput = qs('#captureInput');
// 无分页模式
let currentTab = 'search';
@ -81,7 +83,15 @@ input.addEventListener('keydown', e => { if (e.key === 'Enter') search(); });
async function openScanner(){
scannerModal.classList.remove('hidden');
scannerMessage.textContent = '正在请求摄像头权限...';
scannerMessage.classList.remove('hidden');
const isSecure = location.protocol === 'https:' || location.hostname === 'localhost';
try{
if(!isSecure){
scannerMessage.textContent = '非安全上下文,已切换为拍照识别方式';
captureInput.click();
return;
}
if('BarcodeDetector' in window){
const formats = ['ean_13','ean_8','code_128','code_39','upc_a','upc_e'];
const detector = new window.BarcodeDetector({ formats });
@ -94,6 +104,7 @@ async function openScanner(){
host.innerHTML = '';
host.appendChild(videoEl);
await videoEl.play();
scannerMessage.classList.add('hidden');
const loop = async()=>{
try{
const codes = await detector.detect(videoEl);
@ -110,10 +121,12 @@ async function openScanner(){
};
requestAnimationFrame(loop);
} else {
scannerModal.classList.add('hidden');
scannerMessage.textContent = '浏览器不支持实时扫码,已切换为拍照识别方式';
captureInput.click();
}
}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(mediaStream){ mediaStream.getTracks().forEach(t=>t.stop()); mediaStream=null; } }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');
}
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);
closeScanner.addEventListener('click', closeScannerFn);

View File

@ -18,7 +18,13 @@
<div class="search">
<div class="search-input">
<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>
<button id="searchBtn">查询</button>
</div>
@ -36,10 +42,12 @@
</div>
<div id="scannerModal" class="modal hidden">
<div class="modal-content">
<div id="scannerMessage" class="scanner-message hidden"></div>
<div id="scanner"></div>
<button id="closeScanner">关闭</button>
</div>
</div>
<input id="captureInput" type="file" accept="image/*" capture="environment" class="hidden" />
<script src="/static/app.js"></script>
</body>
</html>

View File

@ -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}
.search-input{position:relative;flex:1}
.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}
.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)}
@ -27,6 +30,8 @@ mark{background:#ffec99}
.loading{color:#1976d2}
.status{margin-top:8px;color:#555}
.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.hidden{display:none}
.modal-content{background:#fff;border-radius:10px;padding:12px;width:92%;max-width:480px}

1
vercel.json Normal file
View File

@ -0,0 +1 @@
{"rewrites":[{"source":"/(.*)","destination":"/index.html"}]}