feat(payment): 集成PingPong支付SDK并实现支付功能

- 添加PingPong支付SDK动态加载逻辑
- 实现支付组件与SDK的初始化配置
- 配置支付容器自适应不同屏幕尺寸
- 添加支付token校验和错误提示
- 集成Element Plus消息组件显示支付状态
- 配置SDK基础样式和按钮样式参数
- 添加支付页面路由和基本布局结构
- 实现支付结果页面跳转逻辑
- 添加订单状态管理和响应码常量定义
- 集成工具函数支持金额格式化和日期处理
- 配置开发环境变量支持沙箱模式切换
- 添加防抖节流等常用工具函数实现
- 实现订单号生成和状态文本映射逻辑
- 添加表单验证函数支持邮箱和手机校验
This commit is contained in:
2025-12-19 10:06:24 +08:00
parent ae0b5f27be
commit 57d9c03332
7 changed files with 503 additions and 0 deletions

139
src/views/Checkout.vue Normal file
View File

@@ -0,0 +1,139 @@
<template>
<div class="checkout">
<el-card>
<template #header>
<div class="card-header">
<span>PingPong支付收银台</span>
</div>
</template>
<div id="ufo-container" class="checkout-container"></div>
</el-card>
</div>
</template>
<script setup>
import { onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import config from '../config'
const route = useRoute()
const token = route.query.token
onMounted(() => {
if (!token) {
ElMessage.error('缺少支付token请重新创建订单')
return
}
// 动态加载PingPong SDK
loadPingPongSDK()
})
onUnmounted(() => {
// 清理资源
})
const loadPingPongSDK = () => {
// 检查SDK是否已加载
if (window.ppPay) {
initPingPongPay()
return
}
// 加载SDK
const script = document.createElement('script')
script.src = config.pingpongSdkUrl
script.onload = () => {
initPingPongPay()
}
script.onerror = () => {
ElMessage.error('加载支付SDK失败请刷新页面重试')
}
document.head.appendChild(script)
}
const initPingPongPay = () => {
try {
const client = new window.ppPay({
lang: 'zh',
root: '#ufo-container',
manul: false,
located: true,
showPrice: true,
bill: true,
mode: config.pingpongMode, // 根据环境配置sandbox/test/build
menu: false,
base: {
width: '100%',
height: '100%',
fontSize: '14px',
backgroundColor: '#fff',
showHeader: true,
showHeaderLabel: true,
headerLabelFont: '支付',
headerColor: '#333333',
headerSize: '16px',
headerBackgroundColor: '#fff',
headerPadding: '20px',
btnSize: '100%',
btnColor: '#fff',
btnFontSize: '14px',
btnPaddingX: '20px',
btnPaddingY: '10px',
btnBackgroundColor: '#1fa0e8',
btnBorderRadius: '4px',
btnMarginTop: '20px'
}
})
const sdkConfig = {
token: token
}
client.createPayment(sdkConfig)
// 调整容器大小
adjustContainerSize()
window.addEventListener('resize', adjustContainerSize)
} catch (error) {
console.error('初始化支付失败:', error)
ElMessage.error('初始化支付失败,请刷新页面重试')
}
}
const adjustContainerSize = () => {
const container = document.getElementById('ufo-container')
if (!container) return
const winWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
if (winWidth >= 500) {
const clientW = Math.floor(winWidth / 3)
container.style.width = (clientW >= 500 ? clientW : 500) + 'px'
container.style.margin = '0 auto'
} else {
container.style.width = winWidth + 'px'
}
}
</script>
<style scoped>
.checkout {
max-width: 1200px;
margin: 0 auto;
}
.card-header {
font-size: 18px;
font-weight: bold;
}
.checkout-container {
min-height: 600px;
padding: 20px;
box-sizing: border-box;
}
</style>