小程序二维码功能
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** H5:Data 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 @@
|
||||
/** 微信小程序基础库未内置 TextEncoder,qrcode 库依赖它编码 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 {};
|
||||
Reference in New Issue
Block a user