uni-app Vue3 集成uQRCode实现微信支付二维码动态生成与弹窗交互
1. 为什么需要动态生成微信支付二维码
在移动支付场景中,二维码支付已经成为最主流的支付方式之一。我做过不少uni-app项目,发现很多开发者习惯在服务端生成静态二维码图片,然后直接返回给前端展示。这种方式虽然简单,但存在几个明显问题:
- 安全性隐患:静态二维码容易被截获和篡改
- 灵活性不足:无法实时更新支付状态
- 用户体验差:需要额外处理图片加载和错误状态
使用uQRCode动态生成二维码的方案,可以完美解决这些问题。实测下来,这种方案有三大优势:
- 实时性:订单创建成功后立即生成最新支付链接
- 可控性:前端可以灵活控制二维码的显示逻辑
- 性能优化:减少不必要的图片请求
在Vue3的组合式API环境下,我们可以把二维码生成逻辑封装成可复用的composable函数,这在多页面需要支付功能的场景下特别实用。
2. 环境准备与基础配置
2.1 创建uni-app项目
首先确保你已经安装HBuilder X(最新版),创建一个基于Vue3的uni-app项目。我推荐使用typescript模板,后续代码提示会更友好:
# 通过cli创建项目(可选) vue create -p dcloudio/uni-preset-vue my-project2.2 安装必要依赖
除了uQRCode核心库,我们还需要uni-popup弹窗组件来实现优雅的交互体验:
npm install uqrcodejs @dcloudio/uni-ui安装完成后,需要在pages.json中配置easycom自动引入组件:
{ "easycom": { "autoscan": true, "custom": { "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue" } } }2.3 初始化二维码工具类
在utils目录下创建qrcode.js,封装基础生成方法:
import UQRCode from 'uqrcodejs' export const generateQRCode = (url, canvasId, size = 200) => { return new Promise((resolve) => { const qr = new UQRCode() qr.data = url qr.size = size qr.make() const ctx = uni.createCanvasContext(canvasId) qr.canvasContext = ctx qr.drawCanvas() setTimeout(() => resolve(), 300) // 确保绘制完成 }) }3. 核心实现流程
3.1 订单创建与支付触发
在商品页或购物车页,我们需要处理订单创建逻辑。这里建议使用Pinia管理支付状态:
// stores/payment.js export const usePaymentStore = defineStore('payment', { state: () => ({ paymentUrl: '', showQRCode: false }), actions: { async createOrder(params) { try { const res = await uni.request({ url: '/api/createOrder', method: 'POST', data: params }) this.paymentUrl = res.data.pay_url this.showQRCode = true } catch (error) { uni.showToast({ title: '创建订单失败', icon: 'error' }) } } } })3.2 弹窗组件集成
使用uni-popup实现居中显示的支付弹窗,注意这几个关键配置:
<uni-popup ref="popup" type="center" :is-mask-click="false" :safe-area="true" @change="onPopupChange" > <view class="popup-content"> <text>微信支付</text> <canvas id="qrcode" canvas-id="qrcode" style="width: 300rpx; height: 300rpx;" /> <button @click="closePopup">取消支付</button> </view> </uni-popup>样式优化建议:
.popup-content { padding: 40rpx; border-radius: 16rpx; background: #fff; text-align: center; } #qrcode { margin: 20rpx auto; border: 1rpx solid #eee; }3.3 动态生成二维码
在弹窗显示时触发二维码生成,这里有个关键细节:需要确保canvas已经渲染完成。我推荐使用nextTick:
const paymentStore = usePaymentStore() watch(() => paymentStore.showQRCode, async (show) => { if (show) { await nextTick() await generateQRCode( paymentStore.paymentUrl, 'qrcode' ) popup.value.open('center') } })4. 高级优化技巧
4.1 二维码状态管理
实际项目中需要考虑这些状态:
- 生成中
- 已生成
- 已过期
- 支付成功
建议使用枚举管理状态:
const QRCodeStatus = { INIT: 0, GENERATING: 1, VALID: 2, EXPIRED: 3, PAID: 4 }4.2 自动关闭与跳转
支付成功后通常需要自动跳转,可以通过轮询或WebSocket实现:
const checkPaymentStatus = async (orderId) => { const timer = setInterval(async () => { const res = await checkOrderStatus(orderId) if (res.paid) { clearInterval(timer) popup.value.close() uni.navigateTo({ url: '/pages/order/detail?id=' + orderId }) } }, 3000) onUnmounted(() => clearInterval(timer)) }4.3 性能优化建议
- 防抖处理:防止重复生成二维码
- 缓存机制:短时间内重复支付可复用已有二维码
- 错误处理:网络异常时的降级方案
const generateQRCode = _.debounce(async (url) => { // ...生成逻辑 }, 500)5. 常见问题排查
5.1 二维码显示空白
可能原因及解决方案:
- Canvas上下文获取失败:确保canvas-id与代码中一致
- 绘制时机问题:在onReady或nextTick后执行
- 内容过长:微信支付URL超过uQRCode限制时可先编码
5.2 弹窗显示异常
典型问题包括:
- 弹窗位置偏移:检查父元素定位
- 点击穿透:设置合适的mask-click属性
- 样式冲突:使用scoped样式或深度选择器
5.3 支付流程中断
建议添加这些兜底逻辑:
- 二维码过期自动刷新
- 支付超时提醒
- 返回按钮拦截
onBackPress(() => { if (paymentStore.showQRCode) { paymentStore.showQRCode = false return true } })6. 完整实现示例
下面是一个整合所有功能的示例组件:
<script setup> import { ref, watch } from 'vue' import { generateQRCode } from '@/utils/qrcode' import { usePaymentStore } from '@/stores/payment' const paymentStore = usePaymentStore() const popup = ref(null) watch(() => paymentStore.showQRCode, async (show) => { if (show) { await nextTick() await generateQRCode( paymentStore.paymentUrl, 'qrcode' ) popup.value.open('center') checkPaymentStatus() } }) const closePopup = () => { popup.value.close() paymentStore.showQRCode = false } </script> <template> <button @click="paymentStore.createOrder()"> 立即支付 </button> <uni-popup ref="popup" type="center" @change="onPopupChange"> <!-- 弹窗内容 --> </uni-popup> </template>样式部分需要注意多端适配,特别是iOS和Android的显示差异。建议使用rpx单位,并测试不同尺寸设备的显示效果。
