emblemscanner: native wasm works

This commit is contained in:
Fam Zheng 2025-09-13 21:31:05 +01:00
parent 7c7c94fa7b
commit adb87f27b5
27 changed files with 333 additions and 399 deletions

View File

@ -19,7 +19,8 @@ This is a WeChat Mini Program called "徵象" (emblem-scanner) for QR code scann
**Key Components:** **Key Components:**
- Camera system with device-specific zoom rules fetched from API - Camera system with device-specific zoom rules fetched from API
- WebAssembly-based QR processing in worker thread for performance - WebAssembly-based QR processing in worker thread for performance
- Dual camera modes: native camera (`pages/camera/`) and web view (`pages/camwebview/`) - New emblemscanner (`pages/emblemscanner/`) for unified QR scanning experience
- Legacy camera page (`pages/camera/`) kept for reference to worker integration
- Upload and verification system connecting to themblem.com API - Upload and verification system connecting to themblem.com API
**Global Data:** **Global Data:**

View File

@ -3,12 +3,9 @@
"pages/index/index", "pages/index/index",
"pages/camera/camera", "pages/camera/camera",
"pages/emblemscanner/emblemscanner", "pages/emblemscanner/emblemscanner",
"pages/productinfo/productinfo",
"pages/test_result_page/test_result_page", "pages/test_result_page/test_result_page",
"pages/debugentry/debugentry",
"pages/debuguploaded/debuguploaded",
"pages/camwebview/camwebview",
"pages/camentry/camentry", "pages/camentry/camentry",
"pages/test/test",
"pages/article/article", "pages/article/article",
"pages/nav/nav", "pages/nav/nav",
"pages/chat/chat", "pages/chat/chat",

View File

@ -274,8 +274,8 @@ Page({
// this is from the debug backend sigifying the upload has succeeded // this is from the debug backend sigifying the upload has succeeded
// we have no product info to show here because "ok" is the only returned information // we have no product info to show here because "ok" is the only returned information
// just show a temporary page // just show a temporary page
info("redirecting to debuguploaded"); info("redirecting to test result page");
wx.redirectTo({ url: '/pages/debuguploaded/debuguploaded' }) wx.redirectTo({ url: '/pages/test_result_page/test_result_page?qr_code=' + encodeURIComponent(upload_result.qr_code || 'unknown') })
} else { } else {
this.show_verifyfailed(); this.show_verifyfailed();
} }

View File

@ -1,99 +0,0 @@
// pages/camwebview/camwebview.js
import {
get_camera_rule,
get_phone_model,
} from '../../utils.js'
function make_query(zoom, ai_chat_mode) {
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 (ai_chat_mode) {
ret += "&ai_chat_mode=" + ai_chat_mode;
}
console.log(ret);
return ret;
}
Page({
/**
* 页面的初始数据
*/
data: {
emblem_camera_url: null,
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
var ai_chat_mode = options.ai_chat_mode;
get_camera_rule(null, (rule) => {
// we should have rules loaded, so this is effectively sync, before instantiating Page
var zoom = rule.zoom;
this.setData({
emblem_camera_url: "https://themblem.com/camera-5.0/?" + make_query(zoom, ai_chat_mode),
});
})
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
},
on_message(e) {
}
})

View File

@ -1 +0,0 @@
<web-view wx:if="{{ emblem_camera_url }}" src="{{ emblem_camera_url }}"></web-view>

View File

@ -1 +0,0 @@
/* pages/camwebview/camwebview.wxss */

View File

@ -1,72 +0,0 @@
// pages/debugentry/debugentry.js
import {
goto_camera,
} from '../../utils.js'
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
getApp().globalData.debug = true;
goto_camera(true);
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})

View File

@ -1,3 +0,0 @@
{
"usingComponents": {}
}

View File

@ -1 +0,0 @@
<view>Emblem debug entry</view>

View File

@ -1 +0,0 @@
/* pages/debugentry/debugentry.wxss */

View File

@ -1,67 +0,0 @@
// pages/debuguploaded/debuguploaded.js
import {
goto_camera,
} from '../../utils.js'
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
goto_camera() {
goto_camera(true);
},
})

View File

@ -1,3 +0,0 @@
{
"usingComponents": {}
}

View File

@ -1,7 +0,0 @@
<view class="container">
<view class="alert">
已上传数据到调试后端
</view>
<button class="primary" bindtap="goto_camera">返回到采集页面</button>
</view>

View File

@ -1,13 +0,0 @@
view.container {
padding-top: 100rpx;
background: #fff;
}
view.alert {
margin: 0 1rem 1rem;
text-align: center;
font-size: 1.2rem;
padding: 1rem;
border-radius: 1rem;
background-color: #eee;
}

View File

@ -34,6 +34,16 @@ const {
is_emblem_qr_pattern is_emblem_qr_pattern
} = require('./libemblemscanner.js'); } = require('./libemblemscanner.js');
// Import upload functionality for verification (self-contained)
const {
upload_image_data_urls
} = require('./upload.js');
// Import precheck utilities for image processing
const {
data_url_from_frame
} = require('../../precheck.js');
// Import QR processing module // Import QR processing module
const { const {
load_qrtool, load_qrtool,
@ -72,7 +82,6 @@ Page({
debug_image_data_url: '', debug_image_data_url: '',
debug_last_result: null, debug_last_result: null,
qrtool_ready: false, qrtool_ready: false,
frame_processing_started: false,
// Frame processing statistics // Frame processing statistics
frames_processed: 0, frames_processed: 0,
frames_skipped: 0, frames_skipped: 0,
@ -83,8 +92,8 @@ Page({
camera_rule: null, camera_rule: null,
use_web_view: false, use_web_view: false,
emblem_camera_url: null, emblem_camera_url: null,
// State machine: initializing -> loading -> scanning -> verifying -> result // State machine: loading_rules -> loading_qrtool -> init_camera -> scanning -> verifying -> result
app_state: 'initializing', // 'initializing', 'loading', 'scanning_camera', 'scanning_webview', 'verifying', 'result' app_state: 'loading_rules', // 'loading_rules', 'loading_qrtool', 'init_camera', 'scanning', 'webview_scanning', 'verifying', 'result'
scan_mode: 'unknown', // 'camera', 'webview' scan_mode: 'unknown', // 'camera', 'webview'
no_web_view: false // Override web-view rule, force native camera no_web_view: false // Override web-view rule, force native camera
}, },
@ -99,20 +108,23 @@ Page({
no_web_view: no_web_view no_web_view: no_web_view
}); });
// Initialize image collection for verification
this.image_data_urls = []; this.image_data_urls = [];
options = options || {}; options = options || {};
const enable_debug = options.debug || options.scene == 'debug' || false; const enable_debug = options.debug || options.scene == 'debug' || false;
// Step 1: Initialize system and get phone model
this.initializeSystem(enable_debug); this.initializeSystem(enable_debug);
this.initializeQRTool();
this.fetchRealIP(); this.fetchRealIP();
this.fetchTenantID(); this.fetchTenantID();
// Step 2: Load camera rules based on phone model
this.loadCameraRules(); this.loadCameraRules();
}, },
fetchRealIP() { fetchRealIP() {
fetch_real_ip((err, ip) => { fetch_real_ip((_err, ip) => {
this.setData({ real_ip: ip }); this.setData({ real_ip: ip });
}); });
}, },
@ -129,8 +141,9 @@ Page({
if (is_qrtool_ready()) { if (is_qrtool_ready()) {
this.setData({ qrtool_ready: true }); this.setData({ qrtool_ready: true });
this.addDebugMessage('QRTool WASM loaded and ready'); this.addDebugMessage('QRTool WASM loaded and ready');
this.transitionToState('loading');
this.startFrameProcessingMaybe(); // Step 4: QRTool loaded, now initialize camera
this.startCameraInit();
} else { } else {
setTimeout(checkReady, 100); setTimeout(checkReady, 100);
} }
@ -152,8 +165,8 @@ Page({
console.log(`Phone model: ${phone_model}, Using native camera mode`); console.log(`Phone model: ${phone_model}, Using native camera mode`);
}, },
loadCameraRules() { loadCameraRules(max_zoom = null) {
get_camera_rule(null, (rule) => { get_camera_rule(max_zoom, (rule) => {
console.log('Camera rule loaded:', rule); console.log('Camera rule loaded:', rule);
const should_use_webview = rule.web_view && !this.data.no_web_view; const should_use_webview = rule.web_view && !this.data.no_web_view;
@ -178,18 +191,41 @@ Page({
this.addDebugMessage(`Camera rule: zoom=${rule.zoom}, web_view=${rule.web_view}${this.data.no_web_view ? ' (NO_WEB_VIEW)' : ''}`); this.addDebugMessage(`Camera rule: zoom=${rule.zoom}, web_view=${rule.web_view}${this.data.no_web_view ? ' (NO_WEB_VIEW)' : ''}`);
if (should_use_webview) { if (should_use_webview) {
// Step 3a: Go directly to webview scanning (no QRTool needed)
this.startWebviewScanning(); this.startWebviewScanning();
} else { } else {
this.startCameraScanning(); // Step 3b: Load QRTool for camera mode
this.addDebugMessage('Starting QRTool initialization');
this.transitionToState('loading_qrtool');
this.initializeQRTool();
} }
}); });
}, },
onCameraReady(e) { onCameraReady(e) {
console.log('Camera ready', e); console.log('Camera ready', e);
// Get max zoom from camera initialization
let max_zoom = null;
if (e && e.detail && e.detail.maxZoom) {
max_zoom = e.detail.maxZoom;
this.setData({
max_zoom: max_zoom
});
this.addDebugMessage(`Camera max zoom: ${max_zoom}`);
}
this.camera_context = wx.createCameraContext(); this.camera_context = wx.createCameraContext();
this.addDebugMessage('Camera initialized for WASM processing'); this.addDebugMessage('Camera context created');
this.startFrameProcessingMaybe();
// Step 5: Set up initial zoom and start scanning
if (this.data.camera_rule) {
this.setupCameraZoom(this.data.camera_rule);
this.addDebugMessage('Initial zoom set up');
}
// Step 6: Transition to scanning (which also starts frame processing)
this.startCameraScanning();
}, },
onCameraError(e) { onCameraError(e) {
@ -249,6 +285,39 @@ Page({
}, },
/**
* Set up camera zoom functionality similar to camera.js
*/
setupCameraZoom(rule) {
if (!this.camera_context) {
this.addDebugMessage('Cannot setup zoom: camera context not ready');
return;
}
console.log('setupCameraZoom called:', rule);
const zoom = rule.zoom;
const initial_zoom = 2;
this.setData({
zoom: initial_zoom,
rule_zoom: zoom
});
this.addDebugMessage(`Camera set initial zoom to ${initial_zoom}x, will zoom in to ${zoom}x when QR is found`);
this.camera_context.setZoom({ zoom: initial_zoom });
// Set up zoom-in behavior when QR is found
this.on_qr_found = () => {
this.addDebugMessage(`QR found, zoom to ${zoom}x`);
this.camera_context.setZoom({ zoom: zoom });
this.setData({
zoom: zoom,
qrmarkers_class: '',
qrarc_class: 'lg'
});
};
},
/** /**
* State Machine: Transition to new state * State Machine: Transition to new state
*/ */
@ -263,33 +332,28 @@ Page({
}, },
/** /**
* State: Loading -> Scanning (Camera) * State: Loading -> Init Camera (prepare camera context)
*/ */
startCameraScanning() { startCameraInit() {
this.transitionToState('scanning_camera', 'camera'); this.transitionToState('init_camera', 'camera');
this.setData({ this.setData({
hint_text: '查找二维码', hint_text: '初始化相机...',
busy: false busy: true
}); });
}, },
/** /**
* Start frame processing if both QRTool and camera are ready * State: Init Camera -> Scanning (Camera)
* Also starts frame processing since all conditions are implied to be ready
*/ */
startFrameProcessingMaybe() { startCameraScanning() {
// Already started, nothing to do this.transitionToState('scanning', 'camera');
if (this.data.frame_processing_started) { this.setData({
return; hint_text: '查找二维码',
} busy: false
});
// Check if both are ready
if (!this.data.qrtool_ready || !this.camera_context) {
const qrStatus = this.data.qrtool_ready ? 'ready' : 'not ready';
const cameraStatus = this.camera_context ? 'ready' : 'not ready';
this.addDebugMessage(`Cannot start frame processing - QRTool: ${qrStatus}, Camera: ${cameraStatus}`);
return;
}
// Start frame processing - all conditions are implied to be ready at this point
this.addDebugMessage('Starting camera frame listener'); this.addDebugMessage('Starting camera frame listener');
this.lastFrameTime = 0; this.lastFrameTime = 0;
@ -300,23 +364,21 @@ Page({
// Start the listener // Start the listener
this.listener.start(); this.listener.start();
// Mark as started
this.setData({ frame_processing_started: true });
}, },
/** /**
* Camera frame callback - receives live camera frames * Camera frame callback - receives live camera frames
*/ */
onCameraFrame(frame) { onCameraFrame(frame) {
// Only process frames when in camera scanning state and QRTool is ready // Only process frames when in camera scanning state and QRTool is ready
if (this.data.app_state !== 'scanning_camera' || !this.data.qrtool_ready) { if (this.data.app_state !== 'scanning') {
return; return;
} }
// Throttle frame processing to avoid overwhelming the system // Throttle frame processing to avoid overwhelming the system
const now = Date.now(); const now = Date.now();
if (this.lastFrameTime && (now - this.lastFrameTime) < 200) { if (this.lastFrameTime && (now - this.lastFrameTime) < 100) {
this.setData({ this.setData({
frames_skipped: this.data.frames_skipped + 1 frames_skipped: this.data.frames_skipped + 1
}); });
@ -349,7 +411,7 @@ Page({
}); });
if (result) { if (result) {
this.handleQRResult(result); this.handleQRResult(result, frame);
} }
} catch (error) { } catch (error) {
this.addDebugMessage(`Frame processing error: ${error.message}`); this.addDebugMessage(`Frame processing error: ${error.message}`);
@ -374,7 +436,7 @@ Page({
/** /**
* Handle QR processing result * Handle QR processing result
*/ */
handleQRResult(result) { handleQRResult(result, frame) {
// Update debug info if available // Update debug info if available
if (result.debug_data_url) { if (result.debug_data_url) {
this.setData({ this.setData({
@ -385,10 +447,21 @@ Page({
// Generate hint text // Generate hint text
const hint = make_hint_text(result); const hint = make_hint_text(result);
// Check if we have a valid QR code // Check if we have a valid QR code that's ready for upload (like camera.js)
if (result.qrcode && result.valid_pattern && result.ok) { // don't require ok as we only care about the view has a valid qrcode in it
this.addDebugMessage(`QR detected: ${result.qrcode}`); // zooming in so that it's more likely to be clear enough for upload
this.onQRCodeDetected(result.qrcode); if (!this.data.qr_found && result.qrcode && result.valid_pattern && is_emblem_qr_pattern(result.qrcode)) {
this.addDebugMessage(`QR detected and ready: ${result.qrcode}`);
// Trigger zoom-in if function is set up
if (this.on_qr_found) {
this.on_qr_found();
}
this.onQRCodeDetected(result.qrcode, frame);
this.setData({
qr_found: true
});
} else { } else {
// Update hint for user guidance // Update hint for user guidance
this.setData({ hint_text: hint }); this.setData({ hint_text: hint });
@ -400,23 +473,88 @@ Page({
}, },
/** /**
* Handle successful QR code detection * Handle successful QR code detection - collect images from "ok" frames and verify
*/ */
onQRCodeDetected(qrCode) { onQRCodeDetected(qrCode, frameData) {
// Start verification process // Convert frame data to data URL for upload (only called for "ok" frames)
this.startVerifying(); if (frameData) {
const dataUrl = data_url_from_frame(frameData.width, frameData.height, frameData.data);
this.image_data_urls.push(dataUrl);
this.addDebugMessage(`Collected ${this.image_data_urls.length}/3 good images`);
}
// Simulate verification delay, then go to result // Need 3 "ok" frames before verification (like camera.js)
setTimeout(() => { if (this.image_data_urls.length >= 3) {
this.goToResult(qrCode); this.addDebugMessage('3 good images collected, starting verification');
}, 2000); this.startVerifying();
this.submitImageForVerification(this.image_data_urls, qrCode);
this.image_data_urls = []; // Reset for next scan
}
// If less than 3 images, continue scanning to collect more
}, },
/** /**
* State: Loading -> Scanning (Web-view) * Submit images for verification like camera.js
*/
submitImageForVerification(dataUrls, qrCode) {
this.addDebugMessage('Submitting images for verification');
const begin = Date.now();
const success = (res) => {
this.addDebugMessage(`Upload success, code: ${res.statusCode}`);
if (res.statusCode === 200) {
let resp;
if (typeof res.data === "string") {
resp = JSON.parse(res.data);
} else {
resp = res.data;
}
// Store verification response
getApp().globalData.verify_resp = resp;
if (resp.serial_code) {
// Let verification animation run for a bit, then show result
const delay = 3000 - (Date.now() - begin);
setTimeout(() => {
this.goToResult(qrCode, resp.serial_code);
}, delay > 0 ? delay : 0);
} else {
this.showVerifyFailed();
}
} else {
this.showVerifyFailed();
}
};
const fail = (e) => {
this.addDebugMessage(`Upload failed: ${JSON.stringify(e)}`);
this.showVerifyFailed();
};
// Store global data like camera.js
const gd = getApp().globalData;
gd.image_data_urls = dataUrls;
gd.qr_code = qrCode;
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
*/ */
startWebviewScanning() { startWebviewScanning() {
this.transitionToState('scanning_webview', 'webview'); this.transitionToState('webview_scanning', 'webview');
this.setData({ this.setData({
hint_text: '查找二维码', hint_text: '查找二维码',
busy: false busy: false
@ -437,12 +575,16 @@ Page({
/** /**
* State: Any -> Result (jump to return page) * State: Any -> Result (jump to return page)
*/ */
goToResult(qrCode) { goToResult(qrCode, serialCode) {
this.transitionToState('result'); this.transitionToState('result');
if (this.data.return_page) { if (this.data.return_page) {
wx.navigateTo({ // Pass both qr_code and serial_code parameters like camera-5.1 does
url: `${this.data.return_page}?qr_code=${encodeURIComponent(qrCode)}`, // serialCode comes from server verification response
const url = `${this.data.return_page}?qr_code=${encodeURIComponent(qrCode)}&serial_code=${encodeURIComponent(serialCode)}`;
wx.redirectTo({
url: url,
success: () => { success: () => {
this.addDebugMessage(`Navigated to: ${this.data.return_page}`); this.addDebugMessage(`Navigated to: ${this.data.return_page}`);
}, },
@ -475,8 +617,7 @@ Page({
frames_skipped: 0, frames_skipped: 0,
total_processing_time: 0, total_processing_time: 0,
avg_processing_time_ms: 0, avg_processing_time_ms: 0,
last_frame_time_ms: 0, last_frame_time_ms: 0
frame_processing_started: false
}); });
// Reset frame timing // Reset frame timing

View File

@ -1,18 +1,36 @@
<view class="wrapper"> <view class="wrapper">
<!-- STATE: INITIALIZING --> <!-- STATE: LOADING_RULES -->
<view wx:if="{{ app_state == 'initializing' }}" class="loading-spinner"> <view wx:if="{{ app_state == 'loading_rules' }}" class="loading-spinner">
<view class="spinner"></view>
<text>加载相机规则...</text>
</view>
<!-- STATE: LOADING_QRTOOL -->
<view wx:if="{{ app_state == 'loading_qrtool' }}" class="loading-spinner">
<view class="spinner"></view> <view class="spinner"></view>
<text>初始化QR工具...</text> <text>初始化QR工具...</text>
</view> </view>
<!-- STATE: LOADING --> <!-- STATE: INIT_CAMERA -->
<view wx:if="{{ app_state == 'loading' }}" class="loading-spinner"> <block wx:if="{{ app_state == 'init_camera' }}">
<view class="spinner"></view> <!-- Show camera without overlays during initialization -->
<text>初始化相机...</text> <view class="osd">
</view> <view class="upper" bindtap="debug_tap">
{{ hint_text }}
</view>
</view>
<!-- STATE: SCANNING_CAMERA --> <!-- WeChat native camera -->
<block wx:if="{{ app_state == 'scanning_camera' }}"> <camera class="camera"
flash="{{ camera_flash }}"
frame-size="medium"
bindinitdone="onCameraReady"
binderror="onCameraError">
</camera>
</block>
<!-- STATE: SCANNING -->
<block wx:if="{{ app_state == 'scanning' }}">
<!-- QR targeting arcs overlay --> <!-- QR targeting arcs overlay -->
<view class="qrarc {{ qrarc_class }}"> <view class="qrarc {{ qrarc_class }}">
<image class="topleft arc" src="./assets/arc.png"></image> <image class="topleft arc" src="./assets/arc.png"></image>
@ -42,8 +60,8 @@
</camera> </camera>
</block> </block>
<!-- STATE: SCANNING_WEBVIEW --> <!-- STATE: WEBVIEW_SCANNING -->
<block wx:if="{{ app_state == 'scanning_webview' }}"> <block wx:if="{{ app_state == 'webview_scanning' }}">
<!-- Web-view camera (no overlays) --> <!-- Web-view camera (no overlays) -->
<web-view src="{{ emblem_camera_url }}" <web-view src="{{ emblem_camera_url }}"
bindmessage="on_webview_message"> bindmessage="on_webview_message">
@ -144,8 +162,8 @@
</view> </view>
</view> </view>
<!-- Bottom action controls (only for camera scanning) --> <!-- Bottom action controls (for camera init and scanning) -->
<view wx:if="{{ app_state == 'scanning_camera' }}" class="bottomfixed"> <view wx:if="{{ app_state == 'init_camera' || app_state == 'scanning' }}" class="bottomfixed">
<view class="actions"> <view class="actions">
<view class="half {{ show_tip ? 'brighter' : '' }}" bindtap="show_scanguide"> <view class="half {{ show_tip ? 'brighter' : '' }}" bindtap="show_scanguide">
<view class="icon"> <view class="icon">
@ -194,7 +212,7 @@
<view wx:if="{{ show_modal == 'verifyfailed' }}" class="modal"> <view wx:if="{{ show_modal == 'verifyfailed' }}" class="modal">
<view class="modal-content"> <view class="modal-content">
<text>验证失败</text> <text>验证失败</text>
<button bindtap="restart_camera">重新扫描</button> <button bindtap="restartScanning">重新扫描</button>
<button bindtap="show_service">联系客服</button> <button bindtap="show_service">联系客服</button>
</view> </view>
</view> </view>
@ -206,7 +224,7 @@
<view>1. 将QR码置于框内</view> <view>1. 将QR码置于框内</view>
<view>2. 保持稳定</view> <view>2. 保持稳定</view>
<view>3. 确保光线充足</view> <view>3. 确保光线充足</view>
<button bindtap="restart_camera">开始扫描</button> <button bindtap="close_modal">开始扫描</button>
<button bindtap="show_service">联系客服</button> <button bindtap="show_service">联系客服</button>
</view> </view>
</view> </view>

View File

@ -10,7 +10,6 @@ var qrtool_ready = false;
function load_qrtool() { function load_qrtool() {
var m = require('./qrtool.wx.js'); var m = require('./qrtool.wx.js');
m.onRuntimeInitialized = () => { m.onRuntimeInitialized = () => {
console.log("QRTool runtime initialized");
qrtool_ready = true; qrtool_ready = true;
qrtool = m; qrtool = m;
} }
@ -28,39 +27,17 @@ function is_qrtool_ready() {
*/ */
function process_frame(width, height, image_data, camera_sensitivity, enable_debug = false) { function process_frame(width, height, image_data, camera_sensitivity, enable_debug = false) {
if (!qrtool_ready) { if (!qrtool_ready) {
console.log("qrtool not ready");
return null; return null;
} }
console.log('process_frame called:', {
width,
height,
data_type: typeof image_data,
data_constructor: image_data ? image_data.constructor.name : 'undefined',
data_length: image_data ? image_data.length : 'undefined',
data_byteLength: image_data ? image_data.byteLength : 'undefined',
bytes_per_element: image_data ? image_data.BYTES_PER_ELEMENT : 'undefined',
camera_sensitivity,
enable_debug
});
try { try {
// Copy frame data to avoid TOCTOU // Copy frame data to avoid TOCTOU
var uca1 = new Uint8ClampedArray(image_data); var uca1 = new Uint8ClampedArray(image_data);
var uca = new Uint8ClampedArray(uca1); var uca = new Uint8ClampedArray(uca1);
console.log('Frame data copied:', {
uca_length: uca.length,
uca_bytes_per_element: uca.BYTES_PER_ELEMENT
});
var buf = qrtool._malloc(uca.length * uca.BYTES_PER_ELEMENT); var buf = qrtool._malloc(uca.length * uca.BYTES_PER_ELEMENT);
qrtool.HEAPU8.set(uca, buf); qrtool.HEAPU8.set(uca, buf);
console.log('Buffer allocated:', {
buffer_size: uca.length * uca.BYTES_PER_ELEMENT
});
var dot_area_buf = 0; var dot_area_buf = 0;
var debug_data_url = null; var debug_data_url = null;
@ -75,8 +52,6 @@ function process_frame(width, height, image_data, camera_sensitivity, enable_deb
[buf, width, height, dot_area_buf, camera_sensitivity || 1] [buf, width, height, dot_area_buf, camera_sensitivity || 1]
); );
console.log('qrtool_angle result:', result_str);
if (enable_debug && dot_area_buf) { if (enable_debug && dot_area_buf) {
const dot_area_size = 32; const dot_area_size = 32;
const da_len = dot_area_size * dot_area_size * uca.BYTES_PER_ELEMENT * 4; const da_len = dot_area_size * dot_area_size * uca.BYTES_PER_ELEMENT * 4;

View File

@ -0,0 +1,71 @@
function upload_image_data_urls(image_data_urls, success, fail, log) {
do_upload(image_data_urls, success, fail, log);
}
function check_auto_torch(qrcode, cb) {
var gd = getApp().globalData;
var url = gd.server_url + '/api/v1/check-auto-torch/?qrcode=' + encodeURIComponent(qrcode);
console.log("check_auto_torch:", url);
const fail = (e) => {
console.log("failed to check auto torch", e);
cb(false, null);
};
const success = (res) => {
if (res.statusCode == 200) {
var resp;
console.log(res.data);
if (typeof res.data == "string") {
resp = JSON.parse(res.data);
} else {
resp = res.data;
}
cb(resp.enable_auto_torch, resp.camera_sensitivity);
} else {
console.log("failed to check auto torch", res.data);
cb(false, null);
}
};
wx.request({
url,
method: "GET",
success,
fail,
});
}
function do_upload(image_data_urls, success, fail, log) {
var ui = wx.getStorageSync('userinfo');
var gd = getApp().globalData;
var fd = {
emblem_id: ui.emblem_id,
nick_name: ui.nickName,
realip: gd.real_ip,
qrcode: gd.qr_code,
angle: 0,
phonemodel: gd.phone_model,
image_data_urls,
use_roi_verify: 1,
log,
};
var ci = gd.caller_info;
if (ci && ci.token) {
fd.token = ci.token;
}
var url = gd.server_url + '/api/v1/qr-verify/';
console.log("wx.request", url, fd.qrcode, fd.angle, fd.phonemodel, fd.realip);
wx.request({
url,
method: "POST",
header: {
"Content-Type": "application/json",
},
data: JSON.stringify(fd),
success,
fail,
});
}
module.exports = {
upload_image_data_urls,
check_auto_torch,
};

View File

@ -128,7 +128,9 @@ Page({
goto_camera: function () { goto_camera: function () {
this.getUserProfile(() => { this.getUserProfile(() => {
goto_camera(false); wx.navigateTo({
url: '/pages/emblemscanner/emblemscanner?return_page=/pages/test_result_page/test_result_page'
});
}); });
}, },

View File

@ -1,37 +1,44 @@
// pages/productinfo/productinfo.js
// pages/test/test.js
Page({ Page({
/** /**
* Page initial data * Page initial data
*/ */
data: { data: {
url: null,
}, },
/** /**
* Lifecycle function--Called when page load * Lifecycle function--Called when page load
*/ */
onLoad(options) { onLoad(options) {
var base_url = getApp().globalData.server_url + '/api/product-info/';
var url = base_url + options.serial_code + '/';
console.log(url);
this.setData({
url: url,
})
}, },
/** /**
* Lifecycle function--Called when page is initially rendered * Lifecycle function--Called when page is initially rendered
*/ */
onReady() { onReady() {
}, },
/** /**
* Lifecycle function--Called when page show * Lifecycle function--Called when page show
*/ */
onShow() { onShow() {
}, },
/** /**
* Lifecycle function--Called when page hide * Lifecycle function--Called when page hide
*/ */
onHide() { onHide() {
}, },
/** /**

View File

@ -0,0 +1 @@
<web-view wx:if="{{ url }}" src="{{url}}"></web-view>

View File

@ -0,0 +1 @@
/* pages/productinfo/productinfo.wxss */

View File

@ -1,8 +0,0 @@
{
"usingComponents": {
"verifyspin": "/components/verifyspin/verifyspin",
"verifyfailed": "/components/verifyfailed/verifyfailed",
"scanguide": "/components/scanguide/scanguide",
"servicemodal": "/components/servicemodal/servicemodal"
}
}

View File

@ -1,2 +0,0 @@
<text>a</text>
<text>b</text>

View File

@ -77,18 +77,16 @@ function get_camera_rule(max_zoom, cb) {
function goto_camera(redirect, opts) { function goto_camera(redirect, opts) {
var query = opts || ""; var query = opts || "";
get_camera_rule(null, (rule) => { // Route to emblemscanner with return page for product info
console.log("camera rule:", rule); var url = "/pages/emblemscanner/emblemscanner?return_page=/pages/productinfo/productinfo";
var url = "/pages/camera/camera?" + query; if (query) {
if (rule.web_view) { url += "&" + query;
url = "/pages/camwebview/camwebview?" + query; }
} if (redirect) {
if (redirect) { wx.redirectTo({ url });
wx.redirectTo({ url }); } else {
} else { wx.navigateTo({ url });
wx.navigateTo({ url }); }
}
});
} }
function make_new_session_id() { function make_new_session_id() {