SDK开发完成

This commit is contained in:
iqudoo
2026-04-30 16:36:31 +08:00
parent 21ae621c6e
commit 0acd16e0a5
9 changed files with 189 additions and 53 deletions

File diff suppressed because one or more lines are too long

View File

@@ -180,7 +180,7 @@
<body>
<main class="page">
<section class="hero">
<h1>IScan 扫码 SDK Demo</h1>
<h1>IScan 通用扫码 SDK</h1>
<p>统一接入桥接扫码、微信 JSSDK 扫码、Web 摄像头扫码、图片识别和扫码枪输入。</p>
</section>
@@ -188,11 +188,9 @@
<div class="card">
<h2>功能说明</h2>
<ul class="feature-list">
<li>桥接环境优先调用 App 原生扫码</li>
<li>微信环境支持初始化 JSSDK 并调用 scanQRCode</li>
<li>浏览器环境使用 BarcodeDetector 识别二维码和条形码</li>
<li>摄像头不可用时可选择图片识别。</li>
<li>支持扫码枪快速输入,并与扫码监听规则统一回调。</li>
<li>支持桥接扫码、微信 JSSDK 扫码、Web 摄像头扫码、图片识别和扫码枪输入</li>
<li>浏览器环境使用 ZXing的wasm库识别二维码和条形码</li>
<li>扫码结果又监听规则统一回调</li>
</ul>
</div>
@@ -209,20 +207,21 @@
<p>点击开始后会按桥接、微信、Web 摄像头、图片识别的顺序选择可用扫码方式。</p>
<div class="actions">
<button onclick="startScan()" class="btn">开始扫码</button>
<button onclick="scanVideo()" class="btn secondary">开启视频扫码</button>
<button onclick="scanImage()" class="btn secondary">选择图片识别</button>
<button onclick="stopScan()" class="btn secondary">停止扫码</button>
</div>
</section>
<section class="grid">
<div class="card">
<h2>扫码结果</h2>
<pre id="result" class="panel result"></pre>
</div>
<div class="card">
<h2>错误信息</h2>
<pre id="error" class="panel error"></pre>
</div>
<section class="card">
<h2>扫码结果</h2>
<pre id="result" class="panel result"></pre>
</section>
<section class="card">
<h2>错误信息</h2>
<pre id="error" class="panel error"></pre>
<p style="color: #919191">错误信息可能来源于扫码结果、扫码过程、扫码初始化等。</p>
</section>
<section class="card code">
@@ -234,10 +233,11 @@
apiUrl: "https://your-domain.com/wechat/jssdk-config"
}
}).then(function () {
// 监听扫码状态
IScan.setStatusListener(function () {
console.log("status:", IScan.getStatus());
});
// 监听扫码结果
IScan.onScanListener(function (res) {
console.log("scan result:", res);
}, "scan", null, 100);
@@ -348,6 +348,11 @@ IScan.stopScan();</pre>
IScan.scanImage();
}
function scanVideo() {
hide();
IScan.scanVideo();
}
ready();
</script>
</body>

20
dist/index.d.ts vendored
View File

@@ -3,17 +3,25 @@
*/
interface ScanConfigOptions {
/**
* 网页扫码canvas样式
* 网页扫码canvas是否启用,默认启用
*/
webCanvasStyle?: string,
webCanvasEnabled?: boolean,
/**
* 网页扫码canvas样式默认position: fixed; width: 300px; height: 300px; top: 0; left: 0; z-index: 9999;
*/
webScanCanvasStyle?: string,
/**
* 网页扫码类型,默认支持二维码和条码
*/
webScanType?: ('qrCode' | 'barCode')[],
/**
* 网页扫码canvas是否启用默认启用
* 网页扫码视频是否镜像,默认自动判断:前置/PC镜像后置不镜像
*/
webCanvasEnabled?: boolean,
webScanVideoMirror?: boolean,
/**
* 网页扫码视频是否垂直镜像,默认不镜像
*/
webScanVideoMirrorVertical?: boolean,
/**
* 网页扫码成功提示音地址,默认使用内置提示音
*/
@@ -153,6 +161,10 @@ interface IScan {
* 开启扫码
*/
startScan(): void;
/**
* 开启视频扫码
*/
scanVideo(): void;
/**
* 选择图片进行识别
*/

10
dist/index.html vendored
View File

@@ -161,17 +161,18 @@
section {
margin-bottom: 16px;
}</style></head><body><main class="page"><section class="hero"><h1>IScan 扫码 SDK Demo</h1><p>统一接入桥接扫码、微信 JSSDK 扫码、Web 摄像头扫码、图片识别和扫码枪输入。</p></section><section class="grid"><div class="card"><h2>功能说明</h2><ul class="feature-list"><li>桥接环境优先调用 App 原生扫码。</li><li>微信环境支持初始化 JSSDK 并调用 scanQRCode</li><li>浏览器环境使用 BarcodeDetector 识别二维码和条形码。</li><li>摄像头不可用时可选择图片识别。</li><li>支持扫码枪快速输入,并与扫码监听规则统一回调。</li></ul></div><div class="card"><h2>当前状态</h2><p>SDK 状态:<span id="status" class="status">loading</span></p><p>运行环境:</p><pre id="output" class="panel"></pre></div></section><section class="card"><h2>操作</h2><p>点击开始后会按桥接、微信、Web 摄像头、图片识别的顺序选择可用扫码方式。</p><div class="actions"><button onclick="startScan()" class="btn">开始扫码</button> <button onclick="scanImage()" class="btn secondary">选择图片识别</button> <button onclick="stopScan()" class="btn secondary">停止扫码</button></div></section><section class="grid"><div class="card"><h2>扫码结果</h2><pre id="result" class="panel result"></pre></div><div class="card"><h2>错误信息</h2><pre id="error" class="panel error"></pre></div></section><section class="card code"><h2>接入方式</h2><pre>IScan.config({
}</style></head><body><main class="page"><section class="hero"><h1>IScan 通用扫码 SDK</h1><p>统一接入桥接扫码、微信 JSSDK 扫码、Web 摄像头扫码、图片识别和扫码枪输入。</p></section><section class="grid"><div class="card"><h2>功能说明</h2><ul class="feature-list"><li>支持桥接扫码、微信 JSSDK 扫码、Web 摄像头扫码、图片识别和扫码枪输入</li><li>浏览器环境使用 ZXing的wasm库识别二维码和条形码。</li><li>扫码结果又监听规则统一回调。</li></ul></div><div class="card"><h2>当前状态</h2><p>SDK 状态:<span id="status" class="status">loading</span></p><p>运行环境:</p><pre id="output" class="panel"></pre></div></section><section class="card"><h2>操作</h2><p>点击开始后会按桥接、微信、Web 摄像头、图片识别的顺序选择可用扫码方式。</p><div class="actions"><button onclick="startScan()" class="btn">开始扫码</button> <button onclick="scanVideo()" class="btn secondary">开启视频扫码</button> <button onclick="scanImage()" class="btn secondary">选择图片识别</button> <button onclick="stopScan()" class="btn secondary">停止扫码</button></div></section><section class="card"><h2>扫码结果</h2><pre id="result" class="panel result"></pre></section><section class="card"><h2>错误信息</h2><pre id="error" class="panel error"></pre><p style="color: #919191">错误信息可能来源于扫码结果、扫码过程、扫码初始化等。</p></section><section class="card code"><h2>接入方式</h2><pre>IScan.config({
webCanvasEnabled: true,
webScanBeepEnabled: true,
initWechatJssdk: {
apiUrl: "https://your-domain.com/wechat/jssdk-config"
}
}).then(function () {
// 监听扫码状态
IScan.setStatusListener(function () {
console.log("status:", IScan.getStatus());
});
// 监听扫码结果
IScan.onScanListener(function (res) {
console.log("scan result:", res);
}, "scan", null, 100);
@@ -277,4 +278,9 @@ IScan.stopScan();</pre></section></main><script>(function () {
IScan.scanImage();
}
function scanVideo() {
hide();
IScan.scanVideo();
}
ready();</script><script src="index.js"></script></body></html>

2
dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,7 @@
import './polyfill';
import { setConfig, getVersion } from './services/config';
import { onScanListener, offScanListener, setStatusListener, getStatus, startScan, stopScan, scanImage, clear } from './services/provider/scan';
import { onScanListener, offScanListener, setStatusListener, getStatus,
startScan, stopScan, scanVideo, scanImage, clear } from './services/provider/scan';
import { initWxJssdk } from './services/wx';
import { printDebug } from './utils/logger';
@@ -41,6 +42,7 @@ export default Object.assign({}, {
getStatus,
startScan,
stopScan,
scanVideo,
scanImage,
clear,
});

View File

@@ -258,7 +258,7 @@ export function onScanListener(listener, key, match, level) {
key,
match: match || "",
level: level || 0,
listener: listener ,
listener: listener,
cancel: () => {
const index = _scan_listener_list.indexOf(item);
if (index !== -1) {
@@ -332,12 +332,12 @@ export function startScan() {
scanPromise = __startWxScan();
} else if (isSupportWebScan()) {
console.log("startScanForWeb");
scanPromise = startScanForWeb(getConfig("webCanvasStyle"), __result);
scanPromise = startScanForWeb(getConfig("webScanCanvasStyle"), __result);
} else if (isSupportImageScan()) {
console.log("startScanForImage");
scanPromise = __startImageScan();
} else {
console.log("not support scanner");
console.log("Not support scanner");
}
return Promise.race([scanPromise, scannerPromise]);
}).finally(() => {
@@ -346,16 +346,38 @@ export function startScan() {
});
}
export function scanImage() {
if (!isSupportImageScan()) {
console.log("not support image scanner");
export function scanVideo() {
if (!isSupportWebScan()) {
console.log("Not support video scanner");
return;
}
startScanForImage().then(resp => {
if (resp && resp.result) {
__result(resp.result);
}
}).catch(err => {
console.log("scan image error", err);
Promise.resolve().then(() => {
__scanning();
return startScanForWeb(getConfig("webScanCanvasStyle"), __result).then(resp => {
if (resp && resp.result) {
__result(resp.result);
}
throw resp.error;
}).catch(err => { });
}).finally(() => {
__closed();
});
}
export function scanImage() {
if (!isSupportImageScan()) {
console.log("Not support image scanner");
return;
}
Promise.resolve().then(() => {
__scanning();
return startScanForImage().then(resp => {
if (resp && resp.result) {
__result(resp.result);
}
throw resp.error;
}).catch(err => { });
}).finally(() => {
__closed();
});
}

View File

@@ -32,15 +32,35 @@ function createEl(tagName, id, style, appendChild) {
return el;
}
function canvasDrawLine(context, width, begin, end, color) {
function transformPoint(point, width, height, mirrorHorizontal, mirrorVertical, cover) {
let x = point.x;
let y = point.y;
if (cover) {
x = cover.x + point.x * cover.scale;
y = cover.y + point.y * cover.scale;
}
return {
x: mirrorHorizontal ? width - x : x,
y: mirrorVertical ? height - y : y
};
}
function canvasDrawLine(context, width, height, begin, end, color, mirrorHorizontal, mirrorVertical, cover) {
const beginPoint = transformPoint(begin, width, height, mirrorHorizontal, mirrorVertical, cover);
const endPoint = transformPoint(end, width, height, mirrorHorizontal, mirrorVertical, cover);
context.beginPath();
context.moveTo(width - begin.x, begin.y);
context.lineTo(width - end.x, end.y);
context.moveTo(beginPoint.x, beginPoint.y);
context.lineTo(endPoint.x, endPoint.y);
context.lineWidth = 4;
context.strokeStyle = color;
context.stroke();
}
function isMobile() {
return typeof navigator !== 'undefined'
&& /Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent || "");
}
function getBarcodeFormats(scanType) {
let formats = [];
if (!scanType) {
@@ -114,11 +134,57 @@ function createBarcodeDetector(scanType) {
});
}
function drawBarcode(context, width, barcode) {
function shouldMirrorWebVideo(stream) {
const webScanVideoMirror = getConfig("webScanVideoMirror");
if (typeof webScanVideoMirror === "boolean") {
return webScanVideoMirror;
}
try {
const track = stream && stream.getVideoTracks && stream.getVideoTracks()[0];
const settings = track && track.getSettings && track.getSettings();
if (settings && settings.facingMode === "environment") {
return false;
}
if (settings && settings.facingMode === "user") {
return true;
}
} catch (e) {
}
return !isMobile();
}
function shouldMirrorWebVideoVertical() {
return getConfig("webScanVideoMirrorVertical") === true;
}
function getCanvasDisplaySize(canvasEl, fallbackWidth, fallbackHeight) {
const rect = canvasEl.getBoundingClientRect();
const width = rect.width || parseFloat(canvasEl.style.width) || fallbackWidth;
const height = rect.height || parseFloat(canvasEl.style.height) || fallbackHeight;
return {
width: Math.max(1, Math.round(width)),
height: Math.max(1, Math.round(height))
};
}
function getCoverDrawOptions(sourceWidth, sourceHeight, targetWidth, targetHeight) {
const scale = Math.max(targetWidth / sourceWidth, targetHeight / sourceHeight);
const width = sourceWidth * scale;
const height = sourceHeight * scale;
return {
scale,
width,
height,
x: (targetWidth - width) / 2,
y: (targetHeight - height) / 2
};
}
function drawBarcode(context, width, height, barcode, mirrorHorizontal, mirrorVertical, cover) {
const cornerPoints = barcode.cornerPoints;
if (cornerPoints && cornerPoints.length) {
for (let i = 0; i < cornerPoints.length; i++) {
canvasDrawLine(context, width, cornerPoints[i], cornerPoints[(i + 1) % cornerPoints.length], "#FF3B58");
canvasDrawLine(context, width, height, cornerPoints[i], cornerPoints[(i + 1) % cornerPoints.length], "#FF3B58", mirrorHorizontal, mirrorVertical, cover);
}
return;
}
@@ -131,7 +197,7 @@ function drawBarcode(context, width, barcode) {
{ x: rect.x, y: rect.y + rect.height }
];
for (let i = 0; i < points.length; i++) {
canvasDrawLine(context, width, points[i], points[(i + 1) % points.length], "#FF3B58");
canvasDrawLine(context, width, height, points[i], points[(i + 1) % points.length], "#FF3B58", mirrorHorizontal, mirrorVertical, cover);
}
}
}
@@ -261,13 +327,14 @@ export function startScanForWeb(canvasStyle, onResult) {
"display: none", false);
let canvasEnabled = getConfig("webCanvasEnabled") !== false;
let canvasDisplay = "";
let canvasBaseStyle = canvasStyle || "position: fixed; width: 300px; height: 240px; top: 0; left: 0; z-index: 9999;";
let canvasBaseStyle = canvasStyle || "position: fixed; width: 300px; height: 300px; top: 0; left: 0; z-index: 9999;";
let canvasEl = createEl("canvas",
"__webscan_canvas__",
canvasBaseStyle + " display: none;", true);
canvasDisplay = canvasEl.style.display;
canvasEl.style.cssText = canvasBaseStyle;
canvasDisplay = canvasEl.style.display;
let canvasDisplaySize = getCanvasDisplaySize(canvasEl, 300, 240);
canvasEl.style.display = "none";
let context = canvasEl.getContext("2d");
let currentUuid = scanWeb.uuid;
@@ -288,6 +355,8 @@ export function startScanForWeb(canvasStyle, onResult) {
}).then(function (options) {
const detector = options.detector;
const stream = options.stream;
const mirrorVideo = shouldMirrorWebVideo(stream);
const mirrorVideoVertical = shouldMirrorWebVideoVertical();
videoEl.srcObject = stream;
videoEl.setAttribute("playsinline", true); // iOS使用
videoEl.play();
@@ -307,10 +376,18 @@ export function startScanForWeb(canvasStyle, onResult) {
let tick = () => {
try {
if (videoEl.readyState === videoEl.HAVE_ENOUGH_DATA && !detecting) {
canvasEl.height = videoEl.videoHeight;
canvasEl.width = videoEl.videoWidth;
context.setTransform(-1, 0, 0, 1, canvasEl.width, 0);
context.drawImage(videoEl, 0, 0, canvasEl.width, canvasEl.height);
canvasEl.width = canvasDisplaySize.width;
canvasEl.height = canvasDisplaySize.height;
const cover = getCoverDrawOptions(videoEl.videoWidth, videoEl.videoHeight, canvasEl.width, canvasEl.height);
context.setTransform(
mirrorVideo ? -1 : 1,
0,
0,
mirrorVideoVertical ? -1 : 1,
mirrorVideo ? canvasEl.width : 0,
mirrorVideoVertical ? canvasEl.height : 0
);
context.drawImage(videoEl, cover.x, cover.y, cover.width, cover.height);
context.setTransform(1, 0, 0, 1, 0, 0);
if (canvasEnabled && !displayed) {
displayed = true;
@@ -323,7 +400,7 @@ export function startScanForWeb(canvasStyle, onResult) {
if (!onResult || !onResult(code.rawValue)) {
return;
}
drawBarcode(context, canvasEl.width, code);
drawBarcode(context, canvasEl.width, canvasEl.height, code, mirrorVideo, mirrorVideoVertical, cover);
playScanBeep();
scanWeb.uuid = null;
scanWeb.finish = true;

20
types/index.d.ts vendored
View File

@@ -3,17 +3,25 @@
*/
interface ScanConfigOptions {
/**
* 网页扫码canvas样式
* 网页扫码canvas是否启用,默认启用
*/
webCanvasStyle?: string,
webCanvasEnabled?: boolean,
/**
* 网页扫码canvas样式默认position: fixed; width: 300px; height: 300px; top: 0; left: 0; z-index: 9999;
*/
webScanCanvasStyle?: string,
/**
* 网页扫码类型,默认支持二维码和条码
*/
webScanType?: ('qrCode' | 'barCode')[],
/**
* 网页扫码canvas是否启用默认启用
* 网页扫码视频是否镜像,默认自动判断:前置/PC镜像后置不镜像
*/
webCanvasEnabled?: boolean,
webScanVideoMirror?: boolean,
/**
* 网页扫码视频是否垂直镜像,默认不镜像
*/
webScanVideoMirrorVertical?: boolean,
/**
* 网页扫码成功提示音地址,默认使用内置提示音
*/
@@ -153,6 +161,10 @@ interface IScan {
* 开启扫码
*/
startScan(): void;
/**
* 开启视频扫码
*/
scanVideo(): void;
/**
* 选择图片进行识别
*/