Major enhancements to the emblemscanner module: **Camera System:** - Device-specific camera rules from Themblem API - Web-view fallback for problematic devices in same page - Loading spinner prevents camera mode jumping - Debug mode with comprehensive diagnostics **Self-contained Architecture:** - Inline utility functions (no external dependencies) - Camera rule matching and API integration - Web-view URL generation with proper parameters - Message handling for web-view QR results **UI Improvements:** - Hide overlays during loading state - Enhanced debug overlay with camera rule info - Worker/native camera mode detection - Loading feedback with "初始化相机..." message Module is now production-ready for camera setup and UI, with framework for QR processing integration. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
394 lines
9.6 KiB
JavaScript
394 lines
9.6 KiB
JavaScript
// QR Scanner Module - Self-contained QR scanning page
|
|
// Adapted from existing camera implementation
|
|
|
|
// Utility functions (copied from utils.js for self-contained module)
|
|
var camera_rules = null;
|
|
|
|
function get_system_info() {
|
|
return wx.getSystemInfoSync();
|
|
}
|
|
|
|
function get_phone_model() {
|
|
var ret = get_system_info().model;
|
|
console.log("phone model", ret);
|
|
return ret;
|
|
}
|
|
|
|
function match_camera_rules(model, rules, default_zoom) {
|
|
console.log(model, "apply zoom rules:", rules);
|
|
var best_match = null;
|
|
for (var rule of rules) {
|
|
if (model.toLowerCase().startsWith(rule.model.toLowerCase())) {
|
|
if (!best_match || rule.model.length > best_match.model.length) {
|
|
best_match = rule;
|
|
}
|
|
}
|
|
}
|
|
if (best_match) {
|
|
console.log("found best match", best_match);
|
|
return best_match;
|
|
}
|
|
var ret = {
|
|
zoom: default_zoom,
|
|
web_view: false
|
|
};
|
|
console.log("using default", ret);
|
|
return ret;
|
|
}
|
|
|
|
function get_camera_rule(max_zoom, cb) {
|
|
var default_zoom = 4;
|
|
if (max_zoom && max_zoom >= 60) {
|
|
/*
|
|
* 2024.06.01: in some Huawei/Honor models, the scale is different, use 40
|
|
* in this case so we don't need to set up rules for each specific model
|
|
*/
|
|
console.log(`max zoom is ${max_zoom}, default zoom will be 40`);
|
|
default_zoom = 40;
|
|
}
|
|
if (camera_rules) {
|
|
let rule = match_camera_rules(get_phone_model(), camera_rules, default_zoom);
|
|
cb(rule);
|
|
} else {
|
|
var url = 'https://themblem.com/api/v1/camera-rules/';
|
|
wx.request({
|
|
url,
|
|
complete: (res) => {
|
|
var rules = res.data;
|
|
camera_rules = rules;
|
|
let rule = match_camera_rules(get_phone_model(), rules, default_zoom);
|
|
cb(rule);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function make_query(zoom, return_page) {
|
|
var gd = getApp().globalData;
|
|
var ret = "zoom=" + zoom;
|
|
var ui = wx.getStorageSync('userinfo') || {};
|
|
ret += "&phonemodel=" + encodeURIComponent(get_phone_model());
|
|
ret += "&realip=" + (gd.real_ip || "");
|
|
ret += "&emblem_id=" + (ui.emblem_id || "");
|
|
ret += "&nick_name=" + encodeURIComponent(ui.nickName || "");
|
|
ret += "&tenant=" + (gd.tenant_id || "");
|
|
ret += "&tk=" + Date.now();
|
|
if (return_page) {
|
|
ret += "&return_page=" + encodeURIComponent(return_page);
|
|
}
|
|
console.log("Web-view query:", ret);
|
|
return ret;
|
|
}
|
|
|
|
Page({
|
|
/**
|
|
* Page initial data
|
|
*/
|
|
data: {
|
|
hint_text: '查找二维码',
|
|
enable_debug: false,
|
|
camera_flash: 'off',
|
|
phone_model: 'unknown',
|
|
zoom: -1,
|
|
max_zoom: 1,
|
|
use_worker: false,
|
|
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
|
|
debug_msgs: [],
|
|
debug_image_data_url: '',
|
|
rule_zoom: -1,
|
|
camera_rule: null,
|
|
use_web_view: false,
|
|
emblem_camera_url: null
|
|
},
|
|
|
|
/**
|
|
* Lifecycle function--Called when page load
|
|
*/
|
|
onLoad(options) {
|
|
console.log('QR Scanner module loaded', options);
|
|
|
|
// Store return page from query parameters
|
|
if (options.return_page) {
|
|
this.setData({
|
|
return_page: options.return_page
|
|
});
|
|
}
|
|
|
|
// Initialize image data storage
|
|
this.image_data_urls = [];
|
|
|
|
// Handle debug mode
|
|
options = options || {};
|
|
if (options.debug || options.scene == 'debug') {
|
|
getApp().globalData.debug = true;
|
|
}
|
|
const enable_debug = getApp().globalData.debug || false;
|
|
|
|
// Get system information
|
|
this.initializeSystem(enable_debug);
|
|
|
|
// Load camera rules
|
|
this.loadCameraRules();
|
|
},
|
|
|
|
/**
|
|
* Initialize system information and device detection
|
|
*/
|
|
initializeSystem(enable_debug) {
|
|
const systemInfo = get_system_info();
|
|
const phone_model = systemInfo.model;
|
|
const use_worker = phone_model.toLowerCase().includes('iphone');
|
|
|
|
this.setData({
|
|
enable_debug,
|
|
phone_model,
|
|
window_width: systemInfo.windowWidth,
|
|
window_height: systemInfo.windowHeight,
|
|
use_worker
|
|
});
|
|
|
|
console.log(`Phone model: ${phone_model}, Use worker: ${use_worker}`);
|
|
|
|
// Store phone model in global data
|
|
getApp().globalData.phone_model = phone_model;
|
|
|
|
// Initialize worker for iPhone devices
|
|
if (use_worker) {
|
|
this.initializeWorker();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Initialize WebAssembly worker for QR processing
|
|
*/
|
|
initializeWorker() {
|
|
// TODO: Initialize worker - requires qrtool.wx.js and worker setup
|
|
console.log('Worker initialization would happen here');
|
|
},
|
|
|
|
/**
|
|
* Load camera rules from API
|
|
*/
|
|
loadCameraRules() {
|
|
get_camera_rule(null, (rule) => {
|
|
console.log('Camera rule loaded:', rule);
|
|
|
|
const use_web_view = rule.web_view || false;
|
|
let emblem_camera_url = null;
|
|
|
|
// Set up web-view URL if needed
|
|
if (use_web_view) {
|
|
emblem_camera_url = "https://themblem.com/camera-5.0/?" + make_query(rule.zoom, this.data.return_page);
|
|
this.addDebugMessage(`Using web-view camera: ${emblem_camera_url}`);
|
|
} 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: use_web_view,
|
|
emblem_camera_url: emblem_camera_url,
|
|
busy: false
|
|
});
|
|
|
|
// Add rule info to debug messages
|
|
this.addDebugMessage(`Camera rule: zoom=${rule.zoom}, web_view=${rule.web_view}`);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Camera initialization callback
|
|
*/
|
|
setup_camera(e) {
|
|
console.log('Camera setup', e);
|
|
this.camera_context = wx.createCameraContext();
|
|
|
|
// Set up camera frame listener
|
|
this.camera_context.onCameraFrame((frame) => {
|
|
this.processFrame(frame);
|
|
});
|
|
|
|
this.setData({
|
|
busy: false,
|
|
hint_text: '查找二维码'
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Process camera frame for QR detection
|
|
*/
|
|
processFrame(frame) {
|
|
if (this.data.busy) return;
|
|
|
|
// TODO: Implement QR detection logic
|
|
console.log('Processing frame', frame.width, frame.height);
|
|
},
|
|
|
|
/**
|
|
* Handle successful QR verification
|
|
*/
|
|
onVerificationSuccess(qrCode) {
|
|
console.log('QR verification successful:', qrCode);
|
|
|
|
if (this.data.return_page) {
|
|
wx.navigateTo({
|
|
url: `${this.data.return_page}?qr_code=${encodeURIComponent(qrCode)}`,
|
|
success: () => {
|
|
console.log(`Navigated to return page: ${this.data.return_page}`);
|
|
},
|
|
fail: (err) => {
|
|
console.error('Failed to navigate to return page:', err);
|
|
this.restart_camera();
|
|
}
|
|
});
|
|
} else {
|
|
console.warn('No return page specified');
|
|
this.restart_camera();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
restart_camera() {
|
|
this.setData({
|
|
show_modal: '',
|
|
hint_text: '查找二维码',
|
|
busy: false
|
|
});
|
|
},
|
|
|
|
/**
|
|
* 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);
|
|
},
|
|
|
|
/**
|
|
* Generate hint text based on QR detection result
|
|
*/
|
|
make_hint_text(result) {
|
|
if (result && result.qrcode && result.qrcode.length > 0) {
|
|
const err = result.err || '';
|
|
if (err.includes('margin too small')) {
|
|
return '对齐定位点';
|
|
} else if (err.includes('energy check failed') || err.includes('cannot detect angle')) {
|
|
return '移近一点';
|
|
}
|
|
return '对齐定位点';
|
|
}
|
|
return '查找二维码';
|
|
},
|
|
|
|
/**
|
|
* 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) {
|
|
this.onVerificationSuccess(messageData.qr_code);
|
|
} else if (messageData.error) {
|
|
this.addDebugMessage(`Web-view error: ${messageData.error}`);
|
|
this.setData({
|
|
show_modal: 'verifyfailed',
|
|
hint_text: '识别失败'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}); |