simplify camera-5.1

This commit is contained in:
Fam Zheng 2025-09-15 22:20:53 +01:00
parent 5093b2b40b
commit b87693a717
2 changed files with 169 additions and 109 deletions

View File

@ -36,7 +36,8 @@ const {
// Import upload functionality for verification (self-contained)
const {
upload_image_data_urls
upload_image_data_urls,
check_auto_torch
} = require('./upload.js');
// Import precheck utilities for image processing
@ -65,8 +66,6 @@ Page({
show_tip: false,
show_modal: '',
busy: true,
should_check_auto_torch: true,
done_checking_auto_torch: false,
camera_sensitivity: 1,
qrarc_class: 'sm',
qrmarkers_class: 'hidden',
@ -92,9 +91,8 @@ Page({
use_web_view: false,
use_worker: false,
emblem_camera_url: null,
first_qr_found: false, // Track if first QR has been detected to trigger zoom-in
// State machine: loading_rules -> loading_qrtool -> init_camera -> scanning -> verifying -> result
app_state: 'loading_rules', // 'loading_rules', 'loading_qrtool', 'init_camera', 'scanning', 'webview_scanning', 'verifying', 'result'
// State machine: loading_rules -> loading_qrtool -> init_camera -> qr_detecting -> check_auto_torch -> final_scanning -> verifying -> result
app_state: 'loading_rules', // 'loading_rules', 'loading_qrtool', 'init_camera', 'qr_detecting', 'webview_scanning', 'check_auto_torch', 'final_scanning', 'verifying', 'result'
scan_mode: 'unknown', // 'camera', 'webview'
no_web_view: false, // Override web-view rule, force native camera
worker_processing: false, // Track if worker is currently processing a frame
@ -104,26 +102,15 @@ Page({
onLoad(options) {
console.log('QR Scanner module loaded', options);
// Store page options for use in onShow
this.pageOptions = options || {};
const no_web_view = options.no_web_view === '1' || options.no_web_view === 'true';
this.setData({
return_page: options.return_page || '/pages/test_result_page/test_result_page',
no_web_view: no_web_view
});
// Initialize image collection for verification
this.image_data_urls = [];
options = options || {};
const enable_debug = options.debug || options.scene == 'debug' || false;
// Step 1: Initialize system and get phone model
this.initializeSystem(enable_debug);
this.fetchRealIP();
this.fetchTenantID();
// Step 2: Load camera rules based on phone model
this.loadCameraRules();
},
fetchRealIP() {
@ -169,10 +156,6 @@ Page({
console.log(`Phone model: ${phone_model}, Use worker: ${use_worker}`);
// Set up worker if needed (like camera.js)
if (use_worker) {
this.setupWorker();
}
},
/**
@ -180,11 +163,11 @@ Page({
*/
setupWorker() {
// Create worker with local worker file
this.worker = wx.createWorker('/pages/emblemscanner/worker/index.js', {
var worker = wx.createWorker('/pages/emblemscanner/worker/index.js', {
useExperimentalWorker: true,
});
this.worker.onMessage((msg) => {
worker.onMessage((msg) => {
console.log('Worker message:', msg.type);
if (msg.type === "result") {
@ -216,14 +199,21 @@ Page({
if (result.qrcode && is_emblem_qr_pattern(result.qrcode)) {
this.addDebugMessage(`Worker QR detected: ${result.qrcode}`);
// Check auto-torch on first valid QR detection (only when in QR detecting state)
if (this.data.app_state === 'qr_detecting') {
this.startCheckingAutoTorch(result.qrcode);
// Trigger zoom-in if function is set up
if (this.on_first_qr_found && !this.data.first_qr_found) {
if (this.on_first_qr_found) {
this.on_first_qr_found();
}
if (result.ok) {
return; // Exit early, auto-torch check will return to final_scanning
}
if (this.data.app_state === 'final_scanning' && result.ok) {
// Request worker to submit image data
this.worker.postMessage({ type: "ready_to_submit" });
this.get_worker().postMessage({ type: "ready_to_submit" });
}
}
@ -260,6 +250,7 @@ Page({
});
this.addDebugMessage('Worker set up for iPhone processing');
return worker;
},
loadCameraRules(max_zoom = null) {
@ -328,10 +319,8 @@ Page({
onCameraError(e) {
console.error('Camera error', e);
this.addDebugMessage(`Camera error: ${JSON.stringify(e.detail)}`);
this.setData({
show_modal: 'verifyfailed',
hint_text: '相机初始化失败'
});
// Redirect with failure instead of showing modal
this.goToResult(null, null, false);
},
toggle_torch() {
@ -342,6 +331,7 @@ Page({
console.log('Torch toggled to:', newFlash);
},
show_scanguide() {
this.setData({
show_modal: 'scanguide',
@ -405,7 +395,6 @@ Page({
// Set up zoom-in behavior when QR is found
this.on_first_qr_found = () => {
this.setData({ first_qr_found: true });
this.addDebugMessage(`First QR found, zoom to ${zoom}x`);
this.camera_context.setZoom({ zoom: zoom });
this.setData({
@ -441,11 +430,11 @@ Page({
},
/**
* State: Init Camera -> Scanning (Camera)
* State: Init Camera -> QR Detecting (Camera)
* Also starts frame processing since all conditions are implied to be ready
*/
startCameraScanning() {
this.transitionToState('scanning', 'camera');
this.transitionToState('qr_detecting', 'camera');
this.setData({
hint_text: '查找二维码',
busy: false
@ -461,21 +450,10 @@ Page({
});
// Start the listener with worker if using worker mode
if (this.data.use_worker) {
if (!this.worker) {
this.addDebugMessage('Worker not found, cannot start listener');
this.setData({
show_modal: 'verifyfailed',
hint_text: '工作线程初始化失败'
});
return;
}
var worker = this.get_worker();
this.listener.start({
worker: this.worker
worker,
});
} else {
this.listener.start();
}
},
@ -483,8 +461,8 @@ Page({
* Camera frame callback - receives live camera frames
*/
onCameraFrame(frame) {
// Only process frames when in camera scanning state
if (this.data.app_state !== 'scanning') {
// Only process frames when in QR detecting or final scanning states
if (this.data.app_state !== 'qr_detecting' && this.data.app_state !== 'final_scanning') {
return;
}
@ -499,7 +477,7 @@ Page({
this.lastFrameTime = now;
// Use worker for iPhone, direct processing for other devices
if (this.data.use_worker && this.worker) {
if (this.data.use_worker) {
// Skip if worker is already processing a frame
if (this.data.worker_processing) {
this.setData({
@ -524,7 +502,7 @@ Page({
// Set processing flag before sending message
this.setData({ worker_processing: true });
this.worker.postMessage({
this.get_worker().postMessage({
type: 'frame',
width: frame.width,
height: frame.height,
@ -624,12 +602,19 @@ Page({
if (result.qrcode && is_emblem_qr_pattern(result.qrcode)) {
this.addDebugMessage(`QR detected: ${result.qrcode} ok: ${result.ok}: err ${result.err}`);
// Check auto-torch on first valid QR detection (only when in QR detecting state)
if (this.data.app_state === 'qr_detecting') {
this.startCheckingAutoTorch(result.qrcode);
// Trigger zoom-in if function is set up
if (!this.data.first_qr_found && this.on_first_qr_found) {
if (this.on_first_qr_found) {
this.on_first_qr_found();
}
if (result.ok) {
return; // Exit early, auto-torch check will return to final_scanning state
}
if (this.data.app_state === 'final_scanning' && result.ok) {
this.onQRCodeDetected(result.qrcode, frame, copiedFrameData); // Pass the copied frame data
}
} else {
@ -699,22 +684,28 @@ Page({
getApp().globalData.verify_resp = resp;
if (resp.serial_code) {
// Let verification animation run for a bit, then show result
// Let verification animation run for a bit, then redirect with success
const delay = 3000 - (Date.now() - begin);
setTimeout(() => {
this.goToResult(qrCode, resp.serial_code);
this.goToResult(qrCode, resp.serial_code, true);
}, delay > 0 ? delay : 0);
} else {
this.showVerifyFailed();
// Redirect with failure instead of showing modal
const delay = 3000 - (Date.now() - begin);
setTimeout(() => {
this.goToResult(qrCode, null, false);
}, delay > 0 ? delay : 0);
}
} else {
this.showVerifyFailed();
// Redirect with failure instead of showing modal
this.goToResult(qrCode, null, false);
}
};
const fail = (e) => {
this.addDebugMessage(`Upload failed: ${JSON.stringify(e)}`);
this.showVerifyFailed();
// Redirect with failure instead of showing modal
this.goToResult(qrCode, null, false);
};
// Store global data like camera.js
@ -725,15 +716,6 @@ Page({
upload_image_data_urls(dataUrls, success, fail, "emblemscanner verification");
},
/**
* Show verification failed modal
*/
showVerifyFailed() {
this.setData({
show_modal: 'verifyfailed',
hint_text: '验证失败'
});
},
/**
* State: Loading Rules -> Webview Scanning
@ -747,7 +729,40 @@ Page({
},
/**
* State: Scanning -> Verifying (only for camera mode)
* State: QR Detecting -> Check Auto-torch
*/
startCheckingAutoTorch(qrcode) {
this.transitionToState('check_auto_torch');
this.setData({
hint_text: '检查补光设置...'
});
check_auto_torch(qrcode, (auto_torch, camera_sensitivity) => {
this.addDebugMessage(`Auto-torch check: ${auto_torch}, sensitivity: ${camera_sensitivity}`);
if (auto_torch) {
this.setData({
camera_flash: 'torch'
});
this.addDebugMessage('Auto-torch enabled');
}
// Update camera sensitivity and continue scanning
this.setData({
camera_sensitivity: camera_sensitivity || this.data.camera_sensitivity
});
// Transition to final_scanning state (ready to collect frames for verification)
this.transitionToState('final_scanning');
this.setData({
hint_text: '查找二维码'
});
});
},
/**
* State: Final Scanning -> Verifying (show spinner while waiting for backend)
*/
startVerifying() {
this.transitionToState('verifying');
@ -768,12 +783,21 @@ Page({
/**
* State: Any -> Result (jump to return page)
*/
goToResult(qrCode, serialCode) {
goToResult(qrCode, serialCode, success = true) {
this.transitionToState('result');
// Pass both qr_code and serial_code parameters like camera-5.1 does
// serialCode comes from server verification response
const url = `${this.data.return_page}?qr_code=${encodeURIComponent(qrCode)}&serial_code=${encodeURIComponent(serialCode)}`;
// Always redirect to return_page, pass ok=1 for success, ok=0 for failure
// Only pass qr_code and serial_code if ok=1 (success)
let url;
if (success && qrCode) {
if (serialCode) {
url = `${this.data.return_page}?ok=1&qr_code=${encodeURIComponent(qrCode)}&serial_code=${encodeURIComponent(serialCode)}`;
} else {
url = `${this.data.return_page}?ok=1&qr_code=${encodeURIComponent(qrCode)}`;
}
} else {
url = `${this.data.return_page}?ok=0`;
}
wx.redirectTo({
url: url,
@ -806,8 +830,7 @@ Page({
ok_frames: 0,
total_processing_time: 0,
avg_processing_time_ms: 0,
last_frame_time_ms: 0,
first_qr_found: false
last_frame_time_ms: 0
});
// Reset frame timing
@ -865,12 +888,11 @@ Page({
const messageData = e.detail.data[0];
if (messageData.qr_code) {
// Web-view results go directly to result (no verification step)
this.goToResult(messageData.qr_code);
this.goToResult(messageData.qr_code, messageData.serial_code, true);
} else if (messageData.error) {
this.addDebugMessage(`Web-view error: ${messageData.error}`);
this.setData({
hint_text: '识别失败'
});
// Redirect with failure instead of showing hint
this.goToResult(null, null, false);
}
}
},
@ -882,10 +904,49 @@ Page({
return is_emblem_qr_pattern(qrCode);
},
/**
* Lifecycle - page show
*/
onShow() {
console.log('EmblemScanner page shown - initializing');
// Reset state machine to initial state
this.setData({
app_state: 'loading_rules',
scan_mode: 'unknown',
busy: true,
hint_text: '查找二维码',
show_modal: '',
worker_processing: false,
verifying_stage: 0,
// Reset frame processing statistics
frames_processed: 0,
frames_skipped: 0,
ok_frames: 0,
total_processing_time: 0,
avg_processing_time_ms: 0,
last_frame_time_ms: 0
});
// Initialize image collection for verification
this.image_data_urls = [];
const enable_debug = this.pageOptions.debug || this.pageOptions.scene == 'debug' || false;
// Step 1: Initialize system and get phone model
this.initializeSystem(enable_debug);
this.fetchRealIP();
this.fetchTenantID();
// Step 2: Load camera rules based on phone model
this.loadCameraRules();
},
/**
* Lifecycle - page hide
*/
onHide() {
console.log('EmblemScanner page hidden - cleaning up');
this.cleanupListener();
},
@ -905,6 +966,14 @@ Page({
this.listener = null;
this.addDebugMessage('Camera frame listener stopped');
}
},
get_worker() {
var gd = getApp().globalData;
if (!gd.emblemscanner_worker) {
gd.emblemscanner_worker = this.setupWorker();
}
return gd.emblemscanner_worker;
}
});

View File

@ -30,8 +30,8 @@
</camera>
</block>
<!-- STATE: SCANNING -->
<block wx:if="{{ app_state == 'scanning' }}">
<!-- STATE: QR_DETECTING, CHECK_AUTO_TORCH, or FINAL_SCANNING -->
<block wx:if="{{ app_state == 'qr_detecting' || app_state == 'check_auto_torch' || app_state == 'final_scanning' }}">
<!-- QR targeting arcs overlay -->
<view class="qrarc {{ qrarc_class }}">
<image class="topleft arc" src="./assets/arc.png"></image>
@ -145,8 +145,8 @@
</view>
</view>
<!-- Bottom action controls (for camera init and scanning) -->
<view wx:if="{{ app_state == 'init_camera' || app_state == 'scanning' }}" class="bottomfixed">
<!-- Bottom action controls (for camera init and QR detection/scanning) -->
<view wx:if="{{ app_state == 'init_camera' || app_state == 'qr_detecting' || app_state == 'check_auto_torch' || app_state == 'final_scanning' }}" class="bottomfixed">
<view class="actions">
<view class="half {{ show_tip ? 'brighter' : '' }}" bindtap="show_scanguide">
<view class="icon">
@ -183,7 +183,7 @@
</view>
</view>
<!-- Verification Spinner - copied from verifyspin component -->
<!-- Verification Spinner - shows during backend submission -->
<view wx:if="{{ show_modal == 'verifying' }}" class="verification-container">
<view class="verification-spinner {{ verifying_stage == 0 ? 'spin-and-shrink' : 'spin-only' }}">
<image class="square" src="./assets/spinner.png"></image>
@ -193,15 +193,6 @@
</view>
</view>
<!-- Verification Failed -->
<view wx:if="{{ show_modal == 'verifyfailed' }}" class="modal">
<view class="modal-content">
<text>验证失败</text>
<button bindtap="restart_camera">重新扫描</button>
<button bindtap="show_service">联系客服</button>
</view>
</view>
<!-- Scan Guide -->
<view wx:if="{{ show_modal == 'scanguide' }}" class="modal">
<view class="modal-content">