二维码扫描优化,但是开票页面必须在微信浏览器中打开。
该项目暂停。
This commit is contained in:
+244
@@ -0,0 +1,244 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>QR 诊断测试</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.js"></script>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body { font-family: system-ui, sans-serif; margin: 0; padding: 16px; background: #f5f5f5; }
|
||||
h2 { margin: 0 0 8px; }
|
||||
.card { background: #fff; border-radius: 8px; padding: 16px; margin-bottom: 12px; box-shadow: 0 1px 3px rgba(0,0,0,.1); }
|
||||
input[type=file] { margin: 4px 0 12px; }
|
||||
img, canvas { max-width: 100%; border: 1px solid #ccc; border-radius: 6px; margin: 6px 0; }
|
||||
button { padding: 8px 14px; border: none; border-radius: 6px; background: #1976d2; color: #fff; font-size: 14px; cursor: pointer; margin-right: 8px; }
|
||||
.result { margin-top: 10px; padding: 10px; border-radius: 6px; font-size: 13px; white-space: pre-wrap; word-break: break-all; }
|
||||
.ok { background: #e8f5e9; color: #2e7d32; }
|
||||
.fail { background: #ffebee; color: #c62828; }
|
||||
.info { background: #e3f2fd; color: #1565c0; }
|
||||
.row { display: flex; flex-wrap: wrap; gap: 12px; }
|
||||
.col { flex: 1; min-width: 280px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>jsQR 二维码诊断工具</h2>
|
||||
|
||||
<div class="card">
|
||||
<input type="file" id="file" accept="image/*" />
|
||||
<button onclick="testAll()">全面诊断</button>
|
||||
<button onclick="testFull()">原始尺寸识别</button>
|
||||
<button onclick="testHalf()">50%缩放识别</button>
|
||||
<button onclick="testQuarter()">25%缩放识别</button>
|
||||
<button onclick="testCenter()">中心裁剪识别</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h3>原图</h3>
|
||||
<img id="preview" style="display:none" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>当前处理结果</h3>
|
||||
<canvas id="resultCanvas" style="display:none"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="diag" style="display:none">
|
||||
<h3>诊断日志</h3>
|
||||
<div id="log"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const fileInput = document.getElementById('file');
|
||||
const preview = document.getElementById('preview');
|
||||
const resultCanvas = document.getElementById('resultCanvas');
|
||||
const logDiv = document.getElementById('log');
|
||||
const diagDiv = document.getElementById('diag');
|
||||
|
||||
let cachedImageData = null;
|
||||
|
||||
/** 获取图片的 ImageData 缓存。@returns {Promise<ImageData>} */
|
||||
async function getImageData() {
|
||||
if (cachedImageData) return cachedImageData;
|
||||
const file = fileInput.files[0];
|
||||
if (!file) throw new Error('请先选择图片');
|
||||
const url = URL.createObjectURL(file);
|
||||
preview.src = url;
|
||||
preview.style.display = 'block';
|
||||
const img = await loadImage(url);
|
||||
const c = document.createElement('canvas');
|
||||
c.width = img.width; c.height = img.height;
|
||||
const ctx = c.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0);
|
||||
cachedImageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
URL.revokeObjectURL(url);
|
||||
return cachedImageData;
|
||||
}
|
||||
|
||||
/** 加载图片。@param {string} url @returns {Promise<HTMLImageElement>} */
|
||||
function loadImage(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = () => reject(new Error('图片加载失败'));
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试 JSQR 识别并返回结果。
|
||||
* @param {ImageData} imageData 图片数据。
|
||||
* @param {string} label 测试标签。
|
||||
* @returns {{ok:boolean, data?:string, msg:string}}
|
||||
*/
|
||||
function tryDecode(imageData, label) {
|
||||
const start = performance.now();
|
||||
const code = jsQR(imageData.data, imageData.width, imageData.height, {
|
||||
inversionAttempts: 'attemptBoth',
|
||||
});
|
||||
const ms = (performance.now() - start).toFixed(0);
|
||||
if (code) {
|
||||
return { ok: true, data: code.data, msg: `${label}: 成功 (${ms}ms) → ${code.data}` };
|
||||
}
|
||||
return { ok: false, msg: `${label}: 失败 (${ms}ms) — ${imageData.width}x${imageData.height}` };
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩放 ImageData 到指定倍数。
|
||||
* @param {ImageData} src 源数据。
|
||||
* @param {number} scale 缩放倍率 (0.5=一半, 0.25=四分之一)。
|
||||
* @returns {ImageData}
|
||||
*/
|
||||
function scaleImageData(src, scale) {
|
||||
const w = Math.floor(src.width * scale);
|
||||
const h = Math.floor(src.height * scale);
|
||||
const c = document.createElement('canvas');
|
||||
c.width = w; c.height = h;
|
||||
const ctx = c.getContext('2d');
|
||||
const temp = document.createElement('canvas');
|
||||
temp.width = src.width; temp.height = src.height;
|
||||
temp.getContext('2d').putImageData(src, 0, 0);
|
||||
ctx.drawImage(temp, 0, 0, w, h);
|
||||
return ctx.getImageData(0, 0, w, h);
|
||||
}
|
||||
|
||||
/**
|
||||
* 裁剪图片中心区域(可能包含二维码的核心部分)。
|
||||
* @param {ImageData} src 源数据。
|
||||
* @returns {ImageData}
|
||||
*/
|
||||
function cropCenter(src) {
|
||||
const cw = Math.floor(src.width * 0.6);
|
||||
const ch = Math.floor(src.height * 0.6);
|
||||
const ox = Math.floor((src.width - cw) / 2);
|
||||
const oy = Math.floor((src.height - ch) / 2);
|
||||
const c = document.createElement('canvas');
|
||||
c.width = cw; c.height = ch;
|
||||
const ctx = c.getContext('2d');
|
||||
const temp = document.createElement('canvas');
|
||||
temp.width = src.width; temp.height = src.height;
|
||||
temp.getContext('2d').putImageData(src, 0, 0);
|
||||
ctx.drawImage(temp, ox, oy, cw, ch, 0, 0, cw, ch);
|
||||
return ctx.getImageData(0, 0, cw, ch);
|
||||
}
|
||||
|
||||
/** 显示处理后的图片到 canvas。@param {ImageData} data */
|
||||
function showResult(data) {
|
||||
resultCanvas.width = data.width;
|
||||
resultCanvas.height = data.height;
|
||||
resultCanvas.getContext('2d').putImageData(data, 0, 0);
|
||||
resultCanvas.style.display = 'block';
|
||||
}
|
||||
|
||||
/** 添加日志。@param {string} msg @param {string} cls */
|
||||
function addLog(msg, cls) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'result ' + cls;
|
||||
div.textContent = msg;
|
||||
logDiv.appendChild(div);
|
||||
}
|
||||
|
||||
/** 原始尺寸识别。@returns {Promise<void>} */
|
||||
async function testFull() {
|
||||
cachedImageData = null;
|
||||
diagDiv.style.display = 'block';
|
||||
logDiv.innerHTML = '';
|
||||
try {
|
||||
const data = await getImageData();
|
||||
addLog(`图片原始尺寸: ${data.width}x${data.height}`, 'info');
|
||||
showResult(data);
|
||||
const r = tryDecode(data, '原始尺寸');
|
||||
addLog(r.msg, r.ok ? 'ok' : 'fail');
|
||||
} catch (e) { addLog('错误: ' + e.message, 'fail'); }
|
||||
}
|
||||
|
||||
/** 50% 缩放识别。@returns {Promise<void>} */
|
||||
async function testHalf() {
|
||||
try {
|
||||
const data = await getImageData();
|
||||
const half = scaleImageData(data, 0.5);
|
||||
showResult(half);
|
||||
addLog(`缩放至 50%: ${half.width}x${half.height}`, 'info');
|
||||
const r = tryDecode(half, '50%缩放');
|
||||
addLog(r.msg, r.ok ? 'ok' : 'fail');
|
||||
} catch (e) { addLog('错误: ' + e.message, 'fail'); }
|
||||
}
|
||||
|
||||
/** 25% 缩放识别。@returns {Promise<void>} */
|
||||
async function testQuarter() {
|
||||
try {
|
||||
const data = await getImageData();
|
||||
const quarter = scaleImageData(data, 0.25);
|
||||
showResult(quarter);
|
||||
addLog(`缩放至 25%: ${quarter.width}x${quarter.height}`, 'info');
|
||||
const r = tryDecode(quarter, '25%缩放');
|
||||
addLog(r.msg, r.ok ? 'ok' : 'fail');
|
||||
} catch (e) { addLog('错误: ' + e.message, 'fail'); }
|
||||
}
|
||||
|
||||
/** 中心裁剪识别。@returns {Promise<void>} */
|
||||
async function testCenter() {
|
||||
try {
|
||||
const data = await getImageData();
|
||||
const crop = cropCenter(data);
|
||||
showResult(crop);
|
||||
addLog(`中心裁剪: ${crop.width}x${crop.height}`, 'info');
|
||||
const r = tryDecode(crop, '中心裁剪');
|
||||
addLog(r.msg, r.ok ? 'ok' : 'fail');
|
||||
} catch (e) { addLog('错误: ' + e.message, 'fail'); }
|
||||
}
|
||||
|
||||
/** 全面诊断:依次尝试多种策略。@returns {Promise<void>} */
|
||||
async function testAll() {
|
||||
diagDiv.style.display = 'block';
|
||||
logDiv.innerHTML = '';
|
||||
try {
|
||||
const data = await getImageData();
|
||||
addLog(`图片原始尺寸: ${data.width}x${data.height} (${(data.width * data.height / 1000000).toFixed(1)}MP)`, 'info');
|
||||
|
||||
const tests = [
|
||||
{ fn: () => tryDecode(data, '原始尺寸'), show: () => showResult(data) },
|
||||
{ fn: () => tryDecode(scaleImageData(data, 0.5), '50%缩放'), show: () => showResult(scaleImageData(data, 0.5)) },
|
||||
{ fn: () => tryDecode(scaleImageData(data, 0.25), '25%缩放'), show: () => showResult(scaleImageData(data, 0.25)) },
|
||||
{ fn: () => tryDecode(scaleImageData(data, 0.125), '12.5%缩放'), show: () => showResult(scaleImageData(data, 0.125)) },
|
||||
{ fn: () => tryDecode(cropCenter(data), '中心60%裁剪'), show: () => showResult(cropCenter(data)) },
|
||||
{ fn: () => tryDecode(scaleImageData(cropCenter(data), 0.5), '中心裁剪+50%缩放'), show: () => showResult(scaleImageData(cropCenter(data), 0.5)) },
|
||||
];
|
||||
|
||||
for (const t of tests) {
|
||||
t.show();
|
||||
const r = t.fn();
|
||||
addLog(r.msg, r.ok ? 'ok' : 'fail');
|
||||
if (r.ok) {
|
||||
addLog('🎉 识别成功!内容: ' + r.data, 'ok');
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
addLog('错误: ' + e.message, 'fail');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user