wangrong
7 小时以前 db685b04e52efd75213ef7540fdc0bed7dccfd9e
cool/utils/qrcode.ts
@@ -1,39 +1,173 @@
import QRCode from "qrcode";
const QRCODE_KEY = "recycleQrcodeBase64";
function u32(buf: Uint8Array, offset: number, value: number) {
   buf[offset] = (value >>> 24) & 255;
   buf[offset + 1] = (value >>> 16) & 255;
   buf[offset + 2] = (value >>> 8) & 255;
   buf[offset + 3] = value & 255;
}
export function generateQrcode(text: string): Promise<string> {
   return new Promise((resolve) => {
      QRCode.toDataURL(
         text,
         {
            width: 300,
            margin: 2,
         },
         (err, url) => {
            if (err) {
               console.error("❌ 二维码生成失败", err);
               resolve("");
               return;
            }
            resolve(url);
         },
      );
function crc32(data: Uint8Array) {
   let c = 0xffffffff;
   for (let i = 0; i < data.length; i++) {
      c ^= data[i];
      for (let k = 0; k < 8; k++) {
         c = (c >>> 1) ^ (c & 1 ? 0xedb88320 : 0);
      }
   }
   return (c ^ 0xffffffff) >>> 0;
}
function adler32(data: Uint8Array) {
   let a = 1;
   let b = 0;
   for (let i = 0; i < data.length; i++) {
      a = (a + data[i]) % 65521;
      b = (b + a) % 65521;
   }
   return ((b << 16) | a) >>> 0;
}
function chunk(type: string, data: Uint8Array) {
   const out = new Uint8Array(12 + data.length);
   u32(out, 0, data.length);
   out[4] = type.charCodeAt(0);
   out[5] = type.charCodeAt(1);
   out[6] = type.charCodeAt(2);
   out[7] = type.charCodeAt(3);
   out.set(data, 8);
   u32(out, 8 + data.length, crc32(out.subarray(4, 8 + data.length)));
   return out;
}
function concat(parts: Uint8Array[]) {
   const len = parts.reduce((sum, part) => sum + part.length, 0);
   const out = new Uint8Array(len);
   let offset = 0;
   for (const part of parts) {
      out.set(part, offset);
      offset += part.length;
   }
   return out;
}
function zlibStore(raw: Uint8Array) {
   const blocks: Uint8Array[] = [];
   let offset = 0;
   while (offset < raw.length) {
      const len = Math.min(65535, raw.length - offset);
      const last = offset + len >= raw.length;
      const block = new Uint8Array(5 + len);
      block[0] = last ? 1 : 0;
      block[1] = len & 255;
      block[2] = (len >>> 8) & 255;
      const nlen = (~len) & 0xffff;
      block[3] = nlen & 255;
      block[4] = (nlen >>> 8) & 255;
      block.set(raw.subarray(offset, offset + len), 5);
      blocks.push(block);
      offset += len;
   }
   const bodyLen = blocks.reduce((sum, block) => sum + block.length, 0);
   const out = new Uint8Array(2 + bodyLen + 4);
   out[0] = 0x78;
   out[1] = 0x01;
   let cursor = 2;
   for (const block of blocks) {
      out.set(block, cursor);
      cursor += block.length;
   }
   u32(out, cursor, adler32(raw));
   return out;
}
function bytesToBase64(bytes: Uint8Array) {
   const api = typeof uni === "undefined" ? undefined : uni;
   if (api && typeof api.arrayBufferToBase64 === "function") {
      const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
      return api.arrayBufferToBase64(buffer as ArrayBuffer);
   }
   const table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
   let out = "";
   for (let i = 0; i < bytes.length; i += 3) {
      const a = bytes[i];
      const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
      const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
      const n = (a << 16) | (b << 8) | c;
      out += table[(n >> 18) & 63];
      out += table[(n >> 12) & 63];
      out += i + 1 < bytes.length ? table[(n >> 6) & 63] : "=";
      out += i + 2 < bytes.length ? table[n & 63] : "=";
   }
   return out;
}
/** 不依赖浏览器 canvas,直接画出可在小程序 image 中显示的 PNG。 */
export function createQrPng(text: string) {
   const qr = QRCode.create(text, { errorCorrectionLevel: "M" });
   const modules = qr.modules;
   const count = modules.size;
   const margin = 4;
   const scale = 8;
   const size = (count + margin * 2) * scale;
   const raw = new Uint8Array(size * (size + 1));
   let cursor = 0;
   for (let y = 0; y < size; y++) {
      raw[cursor++] = 0;
      const row = Math.floor(y / scale) - margin;
      for (let x = 0; x < size; x++) {
         const col = Math.floor(x / scale) - margin;
         const dark =
            row >= 0 && col >= 0 && row < count && col < count && !!modules.get(row, col);
         raw[cursor++] = dark ? 0 : 255;
      }
   }
   const ihdr = new Uint8Array(13);
   u32(ihdr, 0, size);
   u32(ihdr, 4, size);
   ihdr[8] = 8;
   const signature = Uint8Array.from([137, 80, 78, 71, 13, 10, 26, 10]);
   return concat([
      signature,
      chunk("IHDR", ihdr),
      chunk("IDAT", zlibStore(raw)),
      chunk("IEND", new Uint8Array(0)),
   ]);
}
function fileName(text: string) {
   let hash = 0;
   for (let i = 0; i < text.length; i++) {
      hash = (hash * 31 + text.charCodeAt(i)) >>> 0;
   }
   return hash.toString(16);
}
function savePng(bytes: Uint8Array, text: string) {
   const base64 = bytesToBase64(bytes);
   const dataUrl = `data:image/png;base64,${base64}`;
   const root = (globalThis as any).wx?.env?.USER_DATA_PATH;
   if (!root || typeof uni === "undefined" || typeof uni.getFileSystemManager !== "function") {
      return Promise.resolve(dataUrl);
   }
   const filePath = `${root}/recycle-qr-${fileName(text)}.png`;
   return new Promise<string>((resolve) => {
      uni.getFileSystemManager().writeFile({
         filePath,
         data: base64,
         encoding: "base64",
         success: () => resolve(filePath),
         fail: () => resolve(dataUrl),
      });
   });
}
export function generateRecycleQrcode(text: string): Promise<string> {
   return new Promise((resolve) => {
      const cached = uni.getStorageSync(QRCODE_KEY);
      if (cached) return resolve(cached);
export function generateQrcode(text: string) {
   return savePng(createQrPng(text), text);
}
      generateQrcode(text).then((url) => {
         if (url) {
            uni.setStorageSync(QRCODE_KEY, url);
         }
         resolve(url);
      });
   });
export function generateRecycleQrcode(text: string) {
   return generateQrcode(text);
}
/**
@@ -43,10 +177,3 @@
export function clearQrcodeCache(key: string) {
   uni.removeStorageSync(key);
}
/**
 * 清除所有二维码缓存
 */
export function clearAllQrcodeCache() {
   uni.clearStorageSync();
}