昨天我們完成了跨國多幣別旅行開銷儀表板、多維度動態進度條系統,並正式引入 AI 模型消費語意辨別引擎,讓多國混雜的帳目在單一新台幣尺度下呈現出一目了然的財務全貌。然而,手動在計算機鍵盤上敲擊數字、確認幣別與點選分類,哪怕流程再順暢,依然需要耗費繁瑣的人工作業;當我們站在便利商店櫃檯前或採購完畢接過一大張印滿日文、英文或雙方塊條碼的實體發票時,手動輸入的低效感便會成倍放大。
為了解決實體單據的手動謄寫痛點,今天我們正式開啟全系列最硬核的篇章——AI 視覺發票掃描與多國收據辨識中心。首先登場的是計算機動作列專屬相機掃描入口、視訊串流生命週期安全調度,以及高科技霓虹雷射掃描動畫。系統徹底擺脫了傳統網頁一開啟就粗暴彈出攝影機權限請求的惱人缺陷,改為按需申請並在關閉選單時嚴格釋放底層硬體資源,杜絕手機發燙偷吃電;同時在掃描等待期間載入充滿極客氛圍的流光動畫,為即時記帳注入前所未有的儀式感。
線上體驗 Live Demo:https://410355-collab.github.io/Premium-Business-Calculator/
(強烈建議使用 iPhone Safari 或 Android 手機開啟體驗!)
GitHub 開源倉庫:https://github.com/410355-collab/Premium-Business-Calculator
歡迎提出建議與指導
(開發環境:Google Antigravity IDE + Google Gemini 協同開發)
在現代行動瀏覽器上開發影像辨識功能時,許多專案常常陷入三種嚴重的工程缺陷:
第一個問題是網頁載入時強制索取權限與錯誤處理模糊。許多工具為了圖省事,在使用者一進頁面甚至剛點開選單的瞬間,就立即調用媒體串流介面發起權限請求。這種突兀的彈窗不僅容易嚇退重視隱私的使用者,且一旦使用者手滑點擊封鎖,系統便陷入無限錯誤迴圈。若無法精細區分使用者主動拒絕、找不到相機硬體或是鏡頭正被其他應用程式霸佔,就無法給予精準的降級指引。
第二個問題是媒體串流未嚴格釋放導致的發燙與耗電。網頁在取得相機即時視訊串流後,若使用者點擊關閉按鈕退出掃描介面,一般開發者往往只是將視訊元素透過樣式隱藏。然而在瀏覽器底層,相機硬體模組依然處於全速運轉與影像擷取狀態,不僅使瀏覽器頂部常駐警示綠點,更會導致行動裝置在短短數分鐘內迅速發燙、電量雪崩式下滑。
第三個問題是模型推論期間的空白等待焦慮。當相機捕捉到照片或上傳圖檔送往解析時,從影像壓縮到雲端推論往往需要一至二秒的處理時間。若介面在此期間只呈現一個旋轉的微型圖示或完全靜止的畫面,使用者極易誤以為當機而重複點擊,甚至直接關閉視窗。要營造專業旗艦應用的質感,介面必須具備富有科幻感的動態回饋機制。
為了解決相機存取的穩定性與互動體驗,我們構建了兼顧安全性與流暢感的相機調度架構:
動作列相機入口與按需動態授權:
我們在計算機主畫面的快捷動作列中,整合了配備微光脈動藍點與液態玻璃懸浮反饋的專屬相機按鈕。系統嚴格恪守按需授權原則:當使用者僅是在日常運算或進入設定時,完全不主動碰觸攝影機介面;唯有當使用者明確點擊開啟相機拍照時,才正式向瀏覽器發起媒體約束請求,並依照視窗縱橫比例自動匹配最佳解析度。
精細錯誤診斷與硬體級生命週期釋放:
針對呼叫相機時可能發生的各類邊界狀況,系統建立了一套完整的異常捕獲矩陣。若遭遇權限拒絕、找不到設備或軌道被占用,系統會動態輸出具備建設性的操作引導,並在適當情境下自動回退至本機圖庫選擇器。更關鍵的是,我們封裝了全域停止串流函式:在使用者關閉選單、拍攝快照或切換模式的瞬間,強制遍歷所有視訊軌道呼叫停止指令,並重置視訊來源為空值,徹底切斷硬體供電。
霓虹雷射光束與非同步保底動畫:
在影像送入辨識管線的關鍵空窗期,系統在圖片上方動態疊加一層高科技掃描遮罩。遮罩由持續上下掃掠的青色雷射光束、立體幾何脈動網格與四角動態對焦框組合而成。為了防止因本地解析過快而導致動畫一閃而過的突兀跳動,管線內建了至少一秒的展示延遲保護機制,確保每一次掃描都帶給使用者如同專業終端掃描儀般的沉浸式極客體驗。
以下為動態申請視訊串流、自適應比例解析、精細錯誤轉譯與硬體資源強制釋放的關鍵實作:
// 全局 WebCam 媒體串流引用
let webcamStream = null;
// 開啟 Web 鏡頭即時視訊串流(支援電腦 WebCam 與手機後置鏡頭)
async function startWebcam() {
currentCssZoom = 1;
if (window.toggleMenu && !elements.scanMenu.classList.contains('show')) {
window.toggleMenu('ai-scan');
}
const videoEl = document.getElementById('ai-webcam-video');
const webcamBox = document.getElementById('ai-webcam-box');
if (!videoEl || !webcamBox) return;
// 開啟相機前先重置舊有狀態與上傳區域
stopWebcam();
if (elements.previewWrap) elements.previewWrap.classList.remove('scanning');
if (elements.itemsSection) elements.itemsSection.classList.remove('show');
if (elements.totalRow) elements.totalRow.style.display = 'none';
if (elements.importAllBtn) elements.importAllBtn.style.display = 'none';
if (elements.errorBox) elements.errorBox.classList.remove('show');
try {
// 依據螢幕直橫向動態推算最佳視訊約束比例
const isPortrait = window.innerHeight > window.innerWidth;
const constraints = {
video: {
facingMode: { ideal: "environment" },
aspectRatio: { ideal: isPortrait ? 0.5625 : 1.7777777778 },
width: { ideal: isPortrait ? 1080 : 1920 },
height: { ideal: isPortrait ? 1920 : 1080 }
}
};
// 按需發起相機授權申請