基于Vue 3的智能支付二维码弹窗组件设计与实现
前言
本文是集成qrcode-vue3和element的弹窗 请先下载qrcode-vue3
npm install qrcode-vue3 --save
完整组件代码如下
<script setup lang="ts">
import { ref, computed, watch, onUnmounted } from 'vue'
import QRCodeVue3, { type QRCodeProps } from 'qrcode-vue3'
import type { OrderStatus } from '@/api/types' // 根据实际类型定义调整
interface Props {
modelValue: boolean
value: string
orderId: string | number
size?: number
pollInterval?: number
timeout?: number
imageOptions?: QRCodeProps['imageOptions']
qrOptions?: QRCodeProps['qrOptions']
downloadOptions?: QRCodeProps['downloadOptions']
}
const props = withDefaults(defineProps<Props>(), {
size: 200,
pollInterval: 3000, // 3秒轮询
timeout: 60000, // 60秒超时
imageOptions: () => ({
hideBackgroundDots: true,
imageSize: 0.4,
margin: 0
}),
qrOptions: () => ({
typeNumber: 0,
mode: 'Byte',
errorCorrectionLevel: 'H'
}),
downloadOptions: () => ({
name: 'qrcode',
extension: 'png'
})
})
const emit = defineEmits(['update:modelValue', 'success', 'timeout'])
const dialogVisible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
const remainingTime = ref(0)
const pollingTimer = ref<number>()
const countdownTimer = ref<number>()
// 默认二维码样式配置
const defaultDotsOptions = {
type: 'dots',
color: '#26249a',
gradient: {
type: 'linear',
rotation: 0,
colorStops: [
{ offset: 0, color: '#26249a' },
{ offset: 1, color: '#26249a' }
]
}
}
const defaultCornersOptions = {
squareOptions: { type: 'dot', color: '#000000' },
dotOptions: { type: undefined, color: '#000000' }
}
const startPolling = () => {
stopPolling() // 先停止已有轮询
remainingTime.value = Math.floor(props.timeout / 1000)
// 启动倒计时
countdownTimer.value = window.setInterval(() => {
remainingTime.value -= 1
if (remainingTime.value <= 0) {
handleTimeout()
}
}, 1000)
// 立即执行第一次查询
pollOrderStatus()
}
const pollOrderStatus = async () => {
try {
const result = await getPayResultById(props.orderId) // 替换为实际的API调用
if (result.success) {
emit('success', result.data)
closeDialog()
return
}
} catch (error) {
console.error('轮询请求失败:', error)
}
// 继续轮询
if (remainingTime.value > 0) {
pollingTimer.value = window.setTimeout(pollOrderStatus, props.pollInterval)
}
}
const handleTimeout = () => {
emit('timeout')
closeDialog()
}
const closeDialog = () => {
dialogVisible.value = false
stopPolling()
}
const stopPolling = () => {
clearTimeout(pollingTimer.value)
clearInterval(countdownTimer.value)
pollingTimer.value = undefined
countdownTimer.value = undefined
}
// 监听弹窗显示状态
watch(() => props.modelValue, (newVal) => {
if (newVal) {
startPolling()
} else {
stopPolling()
}
})
// 组件卸载时清除定时器
onUnmounted(stopPolling)
// 格式化剩余时间显示
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60)
const secs = seconds % 60
return `${mins}:${secs.toString().padStart(2, '0')}`
}
</script>
<template>
<el-dialog
v-model="dialogVisible"
:title="`扫描二维码 (剩余时间 ${formatTime(remainingTime)})`"
class="qr-code-dialog"
width="auto"
@closed="stopPolling"
>
<div class="qr-container">
<QRCodeVue3
:value="value"
:width="size"
:height="size"
:qr-options="qrOptions"
:image-options="imageOptions"
:dots-options="defaultDotsOptions"
:corners-square-options="defaultCornersOptions.squareOptions"
:corners-dot-options="defaultCornersOptions.dotOptions"
:background-options="{ color: '#ffffff' }"
:download="true"
:download-options="downloadOptions"
/>
</div>
<template #footer>
<el-button type="danger" @click="closeDialog">
关闭二维码 ({{ formatTime(remainingTime) }})
</el-button>
</template>
</el-dialog>
</template>
<style scoped lang="scss">
.qr-code-dialog {
.qr-container {
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
border-radius: 8px;
}
:deep(.el-dialog__header) {
margin-right: 0;
}
:deep(.el-dialog__body) {
padding: 0;
}
}
</style>
一、应用场景
在支付类系统中,我们经常需要实现这样的功能:当用户发起支付请求后,展示支付二维码,并持续轮询服务器查询支付状态。本文介绍的组件完美解决了以下需求:
-
动态二维码展示:支持自定义样式和参数
-
状态轮询机制:自动查询支付结果
-
智能超时处理:60秒自动销毁二维码
-
事件驱动机制:支付成功/超时事件通知
-
用户体验优化:实时倒计时显示
二、核心功能设计
1. 组件参数设计
interface Props {
modelValue: boolean // 控制弹窗显示
value: string // 二维码内容
orderId: string | number // 订单ID
size?: number // 二维码尺寸(默认200px)
pollInterval?: number // 轮询间隔(默认3000ms)
timeout?: number // 超时时间(默认60000ms)
// ...其他二维码配置参数
}
2. 生命周期管理
sequenceDiagram
participant User
component Component
component Server
User->>Component: 打开弹窗
Component->>Server: 首次查询支付状态
Server-->>Component: 返回支付状态
loop 轮询周期
Component->>Server: 定时查询状态
Server-->>Component: 返回最新状态
end
alt 支付成功
Component->>User: 触发success事件
else 超时未支付
Component->>User: 触发timeout事件
end
三、关键技术实现
1. 复合定时器管理
const startPolling = () => {
// 启动倒计时
countdownTimer.value = window.setInterval(() => {
remainingTime.value -= 1
if (remainingTime.value <= 0) handleTimeout()
}, 1000)
// 轮询逻辑
const poll = async () => {
if (remainingTime.value <= 0) return
try {
const result = await getPayResultById(props.orderId)
if (result.success) {
emit('success', result.data)
closeDialog()
} else {
pollingTimer.value = window.setTimeout(poll, props.pollInterval)
}
} catch (error) {
pollingTimer.value = window.setTimeout(poll, props.pollInterval)
}
}
poll()
}
实现亮点:
-
双定时器独立管理(倒计时+轮询)
-
错误边界处理:网络异常时自动重试
-
自动清理机制:组件卸载时清除定时器
2. 响应式时间显示
<template>
<el-dialog
:title="`扫描二维码 (剩余时间 ${formatTime(remainingTime)})`"
>
<!-- ... -->
<el-button>
关闭二维码 ({{ formatTime(remainingTime) }})
</el-button>
</el-dialog>
</template>
<script>
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60)
const secs = seconds % 60
return `${mins}:${secs.toString().padStart(2, '0')}`
}
</script>
四、组件集成使用
1. 基础使用示例
<template>
<QrPaymentDialog
v-model="showDialog"
:value="qrUrl"
:order-id="orderId"
@success="handleSuccess"
@timeout="handleTimeout"
/>
</template>
<script setup>
const handleSuccess = (orderData) => {
ElNotification.success('支付成功')
router.push('/payment/success')
}
const handleTimeout = () => {
ElMessage.error('支付超时')
// 重新生成订单...
}
</script>
2. 自定义配置示例
<QrPaymentDialog
:size="300"
:poll-interval="5000"
:timeout="120000"
:download-options="{
name: 'wechat-pay',
extension: 'jpg'
}"
/>
五、性能优化实践
-
定时器优化:
onUnmounted(() => { clearTimeout(pollingTimer.value) clearInterval(countdownTimer.value) })
-
渲染优化:
<QRCodeVue3 v-if="dialogVisible" <!-- 仅在可见时渲染 --> />
-
网络请求优化:
let isPending = false const poll = async () => { if (isPending) return isPending = true try { // 请求处理... } finally { isPending = false } }
六、扩展方向建议
-
二维码刷新机制:
const refreshQR = () => { qrVersion.value++ // 通过key强制重新渲染 }
-
支付方式切换:
<template> <el-radio-group v-model="payMethod"> <el-radio label="wechat">微信</el-radio> <el-radio label="alipay">支付宝</el-radio> </el-radio-group> </template>
-
多通道轮询:
const channels = ['/api/pay/status', '/api/pay/backup-status'] let currentChannel = 0 const poll = () => { get(channels[currentChannel]).catch(() => { currentChannel = (currentChannel + 1) % channels.length }) }
七、最佳实践建议
-
服务端配合优化:
-
实现幂等性查询接口
-
设置合适的HTTP缓存头
-
返回扩展状态码(如:100-继续轮询,200-支付成功)
-
-
前端监控:
const poll = async () => { const start = Date.now() try { await getPayResult() } finally { const duration = Date.now() - start trackApiPerformance(duration) // 上报性能数据 } }
-
降级方案:
<template> <div v-if="qrError" class="fallback"> <p>二维码加载失败</p> <button @click="retry">重新生成</button> </div> </template>
八、总结
本文介绍的智能支付二维码组件具有以下优势:
特性 | 优势说明 |
---|---|
开箱即用 | 简单props配置即可快速集成 |
高可定制性 | 支持样式/定时策略全方位定制 |
健壮性 | 完善的错误处理和定时器清理机制 |
良好的用户体验 | 实时反馈+倒计时提示 |
类型安全 | 完整的TypeScript支持 |
如果对你有帮助,请帮忙点个赞