import UQRCode from 'uqrcodejs' import { ref, onUnmounted } from 'vue' // 默认二维码有效期:5分钟(毫秒) const DEFAULT_TTL = 300000 /** * 生成会员二维码 * 使用uQRCode库将token编码为二维码的Base64图片 * @param {string} token - 用户token * @returns {Promise} base64编码的二维码图片 */ export function generateQRCode(token) { return new Promise((resolve, reject) => { const qr = new UQRCode() qr.data = token qr.size = 256 qr.make() // 使用Canvas绘制并导出Base64 // 在UniApp环境中使用Canvas上下文 const canvas = createCanvas(qr.moduleCount, 256) const ctx = canvas.getContext('2d') // 绘制白色背景 ctx.fillStyle = '#ffffff' ctx.fillRect(0, 0, 256, 256) // 绘制二维码模块 const tileSize = 256 / qr.moduleCount ctx.fillStyle = '#000000' for (let row = 0; row < qr.moduleCount; row++) { for (let col = 0; col < qr.moduleCount; col++) { if (qr.modules[row][col]) { ctx.fillRect(col * tileSize, row * tileSize, tileSize, tileSize) } } } resolve(canvas.toDataURL('image/png')) }) } /** * 创建Canvas元素(兼容不同环境) * @param {number} moduleCount - 二维码模块数 * @param {number} size - 画布尺寸 * @returns {HTMLCanvasElement|object} */ function createCanvas(moduleCount, size) { // 在H5/Node环境中使用DOM Canvas if (typeof document !== 'undefined') { const canvas = document.createElement('canvas') canvas.width = size canvas.height = size return canvas } // 在UniApp原生环境中,需要使用uni.createCanvasContext // 此处返回一个简单的模拟对象用于非渲染场景 throw new Error('请在UniApp页面中使用组件方式生成二维码') } /** * 纯数据二维码生成(不依赖Canvas,用于测试和数据传递) * 返回二维码的模块矩阵数据 * @param {string} token - 用户token * @returns {{ modules: boolean[][], moduleCount: number }} 二维码模块数据 */ export function generateQRCodeData(token) { const qr = new UQRCode() qr.data = token qr.make() // 将模块数据转换为简单的boolean二维数组 const modules = [] for (let row = 0; row < qr.moduleCount; row++) { const rowData = [] for (let col = 0; col < qr.moduleCount; col++) { rowData.push(!!qr.modules[row][col]) } modules.push(rowData) } return { modules, moduleCount: qr.moduleCount } } /** * 从二维码模块数据中解码token(用于验证Round-Trip) * 注意:uQRCode是编码库,不支持解码。 * 此函数通过重新编码并比较模块数据来验证一致性。 * @param {string} originalToken - 原始token * @param {{ modules: boolean[][], moduleCount: number }} qrData - 二维码模块数据 * @returns {boolean} 模块数据是否与原始token编码结果一致 */ export function verifyQRCodeData(originalToken, qrData) { const regenerated = generateQRCodeData(originalToken) if (regenerated.moduleCount !== qrData.moduleCount) return false for (let row = 0; row < regenerated.moduleCount; row++) { for (let col = 0; col < regenerated.moduleCount; col++) { if (regenerated.modules[row][col] !== qrData.modules[row][col]) return false } } return true } /** * 二维码组合式函数:管理二维码数据、倒计时、自动刷新 * @param {Function} getToken - 获取新token的异步函数 * @param {number} ttl - 有效期(毫秒),默认300000(5分钟) * @returns {{ qrcodeData: Ref, remainingTime: Ref, refresh: Function, destroy: Function }} */ export function useQRCode(getToken, ttl = DEFAULT_TTL) { // 二维码图片数据(Base64或模块数据) const qrcodeData = ref('') // 剩余时间(秒) const remainingTime = ref(0) // 是否正在加载 const loading = ref(false) let countdownTimer = null let refreshTimer = null /** * 刷新二维码:获取新token并生成二维码 */ async function refresh() { loading.value = true try { const token = await getToken() const data = generateQRCodeData(token) // 存储为JSON字符串,组件可解析使用 qrcodeData.value = JSON.stringify(data) // 重置倒计时 remainingTime.value = Math.floor(ttl / 1000) startCountdown() } catch (e) { console.error('二维码生成失败:', e) } finally { loading.value = false } } /** * 启动倒计时 */ function startCountdown() { stopCountdown() countdownTimer = setInterval(() => { remainingTime.value-- if (remainingTime.value <= 0) { stopCountdown() // 自动刷新 refresh() } }, 1000) } /** * 停止倒计时 */ function stopCountdown() { if (countdownTimer) { clearInterval(countdownTimer) countdownTimer = null } } /** * 销毁:清除所有定时器 */ function destroy() { stopCountdown() if (refreshTimer) { clearTimeout(refreshTimer) refreshTimer = null } } // 组件卸载时自动销毁 try { onUnmounted(destroy) } catch (e) { // 非组件上下文中忽略 } return { qrcodeData, remainingTime, loading, refresh, destroy } }