小程序二维码功能

This commit is contained in:
2026-07-12 23:59:52 +08:00
parent 1bd152073a
commit f2cdc0222d
10 changed files with 658 additions and 63 deletions
+42
View File
@@ -0,0 +1,42 @@
import './text-encoding-polyfill';
import QRCode from 'qrcode';
const QR_SIZE = 240;
const QR_OPTIONS = {
width: QR_SIZE * 2,
margin: 0,
color: { dark: '#1f1a17', light: '#ffffff' },
} as const;
/** 在 2d Canvas 上绘制二维码(小程序 / H5 通用) */
export function drawRedeemQrOnCanvas(
ctx: CanvasRenderingContext2D,
token: string,
sizePx = QR_SIZE,
) {
const qr = QRCode.create(token, { errorCorrectionLevel: 'M' });
const count = qr.modules.size;
const cell = sizePx / count;
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, sizePx, sizePx);
ctx.fillStyle = '#1f1a17';
for (let row = 0; row < count; row++) {
for (let col = 0; col < count; col++) {
if (qr.modules.get(row, col)) {
ctx.fillRect(col * cell, row * cell, cell, cell);
}
}
}
}
/** H5Data URL;失败时回退到与 h5-user 相同的外部 QR 服务 */
export async function buildRedeemQrDataUrl(token: string): Promise<string> {
try {
return await QRCode.toDataURL(token, QR_OPTIONS);
} catch {
return `https://api.qrserver.com/v1/create-qr-code/?size=${QR_SIZE * 2}x${QR_SIZE * 2}&data=${encodeURIComponent(token)}`;
}
}
export const REDEEM_QR_DISPLAY_SIZE = QR_SIZE;
@@ -0,0 +1,52 @@
/** 微信小程序基础库未内置 TextEncoderqrcode 库依赖它编码 payload */
function installTextEncodingPolyfill() {
const root = (typeof globalThis !== 'undefined'
? globalThis
: typeof global !== 'undefined'
? global
: typeof wx !== 'undefined'
? wx
: {}) as typeof globalThis & { TextEncoder?: typeof TextEncoder };
if (typeof root.TextEncoder !== 'undefined') return;
class MiniTextEncoder implements TextEncoder {
readonly encoding = 'utf-8';
encode(input?: string): Uint8Array {
const str = input ?? '';
const bytes: number[] = [];
for (let i = 0; i < str.length; i++) {
let code = str.charCodeAt(i);
if (code < 0x80) {
bytes.push(code);
} else if (code < 0x800) {
bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
} else if (code >= 0xd800 && code <= 0xdbff && i + 1 < str.length) {
const next = str.charCodeAt(i + 1);
if (next >= 0xdc00 && next <= 0xdfff) {
i += 1;
code = 0x10000 + ((code - 0xd800) << 10) + (next - 0xdc00);
bytes.push(
0xf0 | (code >> 18),
0x80 | ((code >> 12) & 0x3f),
0x80 | ((code >> 6) & 0x3f),
0x80 | (code & 0x3f),
);
} else {
bytes.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
}
} else {
bytes.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
}
}
return new Uint8Array(bytes);
}
}
root.TextEncoder = MiniTextEncoder as unknown as typeof TextEncoder;
}
installTextEncodingPolyfill();
export {};