themblem/scanner/pages/emblemscanner/emblemscanner.js
2025-10-29 21:27:29 +00:00

391 lines
9.4 KiB
JavaScript

// QR Scanner Module - Self-contained QR scanning page
// Adapted from existing camera implementation
// Import utility functions from library
const {
get_system_info,
get_phone_model,
get_camera_rule,
make_query,
fetch_real_ip,
get_tenant_id,
is_emblem_qr_pattern
} = require('./libemblemscanner.js');
Page({
/**
* Page initial data
*/
data: {
hint_text: '查找二维码',
enable_debug: false,
camera_flash: 'off',
phone_model: 'unknown',
zoom: -1,
max_zoom: 1,
show_tip: false,
show_modal: '',
busy: true,
should_check_auto_torch: true,
done_checking_auto_torch: false,
camera_sensitivity: 1,
frame_uploaded: 0,
frame_upload_time_cost: 0,
qrarc_class: 'sm',
qrmarkers_class: 'hidden',
frame_upload_interval_ms: 2000,
return_page: '', // Page to navigate to after successful scan
server_url: 'https://themblem.com', // Default server URL
real_ip: '', // User's real IP address
tenant_id: '', // Tenant identifier
debug_msgs: [],
debug_image_data_url: '',
rule_zoom: -1,
camera_rule: null,
use_web_view: false,
emblem_camera_url: null,
// State machine: loading -> scanning -> verifying -> result
app_state: 'loading', // 'loading', 'scanning_camera', 'scanning_webview', 'verifying', 'result'
scan_mode: 'unknown', // 'camera', 'webview'
force_camera: false // Override web-view rule, force native camera
},
/**
* Lifecycle function--Called when page load
*/
onLoad(options) {
console.log('QR Scanner module loaded', options);
// Store query parameters
const force_camera = options.force_camera === '1' || options.force_camera === 'true';
this.setData({
return_page: options.return_page || '',
force_camera: force_camera
});
// Initialize image data storage
this.image_data_urls = [];
// Handle debug mode locally
options = options || {};
const enable_debug = options.debug || options.scene == 'debug' || false;
// Get system information
this.initializeSystem(enable_debug);
// Fetch IP address and tenant info, then load camera rules
this.fetchRealIP();
this.fetchTenantID();
this.loadCameraRules();
},
/**
* Fetch real IP address
*/
fetchRealIP() {
fetch_real_ip((err, ip) => {
this.setData({ real_ip: ip });
});
},
/**
* Fetch tenant ID (can be customized based on app logic)
*/
fetchTenantID() {
const tenant_id = get_tenant_id();
this.setData({ tenant_id });
},
/**
* Initialize system information and device detection
*/
initializeSystem(enable_debug) {
const systemInfo = get_system_info();
const phone_model = systemInfo.model;
this.setData({
enable_debug,
phone_model,
window_width: systemInfo.windowWidth,
window_height: systemInfo.windowHeight
});
console.log(`Phone model: ${phone_model}, Using native camera mode`);
},
/**
* Load camera rules from API
*/
loadCameraRules() {
get_camera_rule(null, (rule) => {
console.log('Camera rule loaded:', rule);
// Check for force_camera override
const should_use_webview = rule.web_view && !this.data.force_camera;
let emblem_camera_url = null;
// Set up web-view URL if needed
if (should_use_webview) {
emblem_camera_url = "https://themblem.com/camera-5.1/?" + make_query(rule.zoom, this.data.return_page, this.data.real_ip, this.data.tenant_id);
this.addDebugMessage(`Using web-view camera: ${emblem_camera_url}`);
} else {
if (this.data.force_camera && rule.web_view) {
this.addDebugMessage('Forcing native camera (override web-view rule)');
} else {
this.addDebugMessage('Using native WeChat camera');
}
}
this.setData({
camera_rule: rule,
zoom: rule.zoom,
rule_zoom: rule.zoom,
camera_sensitivity: rule.sensitivity || 1,
use_web_view: should_use_webview,
emblem_camera_url: emblem_camera_url
});
// Add rule info to debug messages
this.addDebugMessage(`Camera rule: zoom=${rule.zoom}, web_view=${rule.web_view}${this.data.force_camera ? ' (FORCED_CAMERA)' : ''}`);
// Transition to appropriate scanning state
if (should_use_webview) {
this.startWebviewScanning();
} else {
this.startCameraScanning();
}
});
},
/**
* Camera ready callback
*/
onCameraReady(e) {
console.log('Camera ready', e);
this.camera_context = wx.createCameraContext();
this.addDebugMessage('Camera initialized in scanCode mode');
// State transition is handled in loadCameraRules
},
/**
* Camera error callback
*/
onCameraError(e) {
console.error('Camera error', e);
this.addDebugMessage(`Camera error: ${JSON.stringify(e.detail)}`);
this.setData({
show_modal: 'verifyfailed',
hint_text: '相机初始化失败'
});
},
/**
* Toggle torch/flash
*/
toggle_torch() {
const newFlash = this.data.camera_flash === 'torch' ? 'off' : 'torch';
this.setData({
camera_flash: newFlash
});
console.log('Torch toggled to:', newFlash);
},
/**
* Show scan guide
*/
show_scanguide() {
this.setData({
show_modal: 'scanguide',
show_tip: false
});
},
/**
* Show service modal
*/
show_service() {
this.setData({
show_modal: 'service'
});
},
/**
* Close modal and restart camera (legacy method name for WXML compatibility)
*/
restart_camera() {
this.restartScanning();
},
/**
* Close any modal
*/
close_modal() {
this.setData({
show_modal: ''
});
},
/**
* Debug tap handler
*/
debug_tap() {
const count = (this.debug_tap_count || 0) + 1;
this.debug_tap_count = count;
if (count >= 5) {
this.setData({
enable_debug: !this.data.enable_debug
});
this.debug_tap_count = 0;
}
// Clear count after 3 seconds
setTimeout(() => {
this.debug_tap_count = 0;
}, 3000);
},
/**
* State Machine: Transition to new state
*/
transitionToState(newState, mode = null) {
const oldState = this.data.app_state;
this.addDebugMessage(`State: ${oldState} -> ${newState}${mode ? ` (${mode})` : ''}`);
const stateData = { app_state: newState };
if (mode) stateData.scan_mode = mode;
this.setData(stateData);
},
/**
* State: Loading -> Scanning (Camera)
*/
startCameraScanning() {
this.transitionToState('scanning_camera', 'camera');
this.setData({
hint_text: '查找二维码',
busy: false
});
},
/**
* State: Loading -> Scanning (Web-view)
*/
startWebviewScanning() {
this.transitionToState('scanning_webview', 'webview');
this.setData({
hint_text: '查找二维码',
busy: false
});
},
/**
* State: Scanning -> Verifying (only for camera mode)
*/
startVerifying() {
this.transitionToState('verifying');
this.setData({
hint_text: '识别成功',
show_modal: 'verifying'
});
},
/**
* State: Any -> Result (jump to return page)
*/
goToResult(qrCode) {
this.transitionToState('result');
if (this.data.return_page) {
wx.navigateTo({
url: `${this.data.return_page}?qr_code=${encodeURIComponent(qrCode)}`,
success: () => {
this.addDebugMessage(`Navigated to: ${this.data.return_page}`);
},
fail: (err) => {
this.addDebugMessage(`Navigation failed: ${err.errMsg}`);
this.restartScanning();
}
});
} else {
this.addDebugMessage('No return page specified');
this.restartScanning();
}
},
/**
* State: Any -> Loading (restart)
*/
restartScanning() {
this.transitionToState('loading');
this.setData({
show_modal: '',
hint_text: '初始化相机...',
busy: true,
qr_position: null,
qr_sharpness: 0,
qr_size: 0
});
// Reload camera rules to restart the flow
this.loadCameraRules();
},
/**
* Add debug message to debug overlay
*/
addDebugMessage(message) {
if (!this.data.enable_debug) return;
const timestamp = new Date().toLocaleTimeString();
const debugMsg = `${timestamp}: ${message}`;
this.setData({
debug_msgs: [...this.data.debug_msgs, debugMsg].slice(-10) // Keep last 10 messages
});
},
/**
* Log function for debugging
*/
log(...args) {
console.log(...args);
this.addDebugMessage(args.join(' '));
},
/**
* Handle messages from web-view camera
*/
on_webview_message(e) {
console.log('Web-view message received:', e);
this.addDebugMessage(`Web-view message: ${JSON.stringify(e.detail)}`);
// Handle QR code results from web-view
if (e.detail && e.detail.data && e.detail.data.length > 0) {
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);
} else if (messageData.error) {
this.addDebugMessage(`Web-view error: ${messageData.error}`);
this.setData({
hint_text: '识别失败'
});
}
}
},
/**
* Check if QR code matches Emblem pattern
*/
isEmblemQRPattern(qrCode) {
return is_emblem_qr_pattern(qrCode);
},
});