修正布局,删改无用页面

This commit is contained in:
2026-06-24 08:13:12 +08:00
parent 8d8c823616
commit 0bebce3dc1
24 changed files with 2677 additions and 2796 deletions
@@ -66,7 +66,7 @@
<view class="pc-payment__methods">
<view class="pc-payment__method pc-payment__method--selected">
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/alipay.png" mode="aspectFit" class="pc-payment__icon" />
<text class="pc-payment__name">支付宝支付</text>
<text class="pc-payment__name">{{ isWebEnv ? '扫码支付 (Web环境)' : 'App支付' }}</text>
<view class="pc-payment__check"></view>
</view>
</view>
@@ -88,6 +88,72 @@
</view>
</view>
</view>
<!-- 二维码支付对话框 (Web环境) -->
<view class="qr-payment-overlay" v-if="showQrPayment" @tap="closeQrPayment">
<view class="qr-payment-dialog" @tap.stop>
<view class="qr-payment-dialog__header">
<text class="qr-payment-dialog__title">{{ qrPaymentStatus === 'pending' ? '扫码支付' : (qrPaymentStatus === 'success' ? '支付成功' : '支付失败') }}</text>
<view class="qr-payment-dialog__close" @tap="closeQrPayment" v-if="qrPaymentStatus !== 'pending'">×</view>
</view>
<view class="qr-payment-dialog__body">
<!-- 待支付状态显示二维码 -->
<template v-if="qrPaymentStatus === 'pending'">
<view class="qr-payment-dialog__order">
<text class="qr-payment-dialog__label">订单信息</text>
<text class="qr-payment-dialog__value">{{ selectedCard?.cardName }} × 1</text>
</view>
<view class="qr-payment-dialog__amount">
<text class="qr-payment-dialog__label">支付金额</text>
<text class="qr-payment-dialog__money">¥{{ selectedCard?.price || 0 }}</text>
</view>
<view class="qr-payment-dialog__qrcode">
<image
v-if="qrCodeUrl"
:src="'https://api.qrserver.com/v1/create-qr-code?size=200x200&data=' + encodeURIComponent(qrCodeUrl)"
class="qr-payment-dialog__qrcode-img"
mode="aspectFit"
/>
<view v-else class="qr-payment-dialog__qrcode-loading">加载中...</view>
</view>
<view class="qr-payment-dialog__tips">
<text class="qr-payment-dialog__tip">请使用支付宝扫码支付</text>
<text class="qr-payment-dialog__tip qr-payment-dialog__tip--warn" v-if="isWebEnv"> 沙箱环境测试</text>
</view>
</template>
<!-- 支付成功状态 -->
<template v-else-if="qrPaymentStatus === 'success'">
<view class="qr-payment-dialog__result">
<text class="qr-payment-dialog__icon"></text>
<text class="qr-payment-dialog__result-text">支付成功</text>
</view>
</template>
<!-- 支付失败状态 -->
<template v-else>
<view class="qr-payment-dialog__result">
<text class="qr-payment-dialog__icon"></text>
<text class="qr-payment-dialog__result-text">支付失败</text>
</view>
</template>
</view>
<view class="qr-payment-dialog__footer">
<template v-if="qrPaymentStatus === 'pending'">
<view class="qr-payment-dialog__btn qr-payment-dialog__btn--secondary" @tap="closeQrPayment">
<text>取消支付</text>
</view>
</template>
<template v-else>
<view class="qr-payment-dialog__btn qr-payment-dialog__btn--primary" @tap="closeQrPayment">
<text>确定</text>
</view>
</template>
</view>
</view>
</view>
</view>
</template>
@@ -95,9 +161,27 @@
import { ref, onMounted, onUnmounted } from 'vue'
import { onBackPress } from '@dcloudio/uni-app'
import MemberInfoSubNav from '@/components/memberInfo/MemberInfoSubNav.vue'
import { purchaseMemberCard, createPayment, getPaymentStatus } from '@/api/main.js'
import { purchaseMemberCard, createPayment, createQrCodePayment, getPaymentStatus } from '@/api/main.js'
import { loadMemberStore } from '@/common/memberInfo/store.js'
// ==================== Web环境检测 ====================
// #ifdef H5
const isWebEnv = typeof navigator !== 'undefined' && !typeof plus !== 'undefined'
// #endif
// #ifndef H5
const isWebEnv = false
// #endif
// ==================== 二维码支付相关状态 ====================
const showQrPayment = ref(false)
const qrPaymentStatus = ref('pending') // pending | success | fail
const qrCodeUrl = ref('')
let qrPaymentResolve = null
let qrPaymentReject = null
let currentQrOrderId = null
let qrPollingTimer = null
let isQrPolling = false
const cardTypes = ref([
{ id: 1, cardName: '月卡', cardType: 'DURATION', validityDays: 30, price: 1, originalPrice: 399 },
{ id: 2, cardName: '季卡', cardType: 'DURATION', validityDays: 90, price: 1, originalPrice: 999 },
@@ -265,6 +349,103 @@ function startPolling(orderId) {
})
}
// ==================== Web环境扫码支付 ====================
function openQrPayment(qrCode, orderId) {
qrCodeUrl.value = qrCode
currentQrOrderId = orderId
qrPaymentStatus.value = 'pending'
showQrPayment.value = true
// 开始轮询支付状态
startQrPolling(orderId)
}
function closeQrPayment() {
showQrPayment.value = false
stopQrPolling()
if (qrPaymentStatus.value === 'pending' && qrPaymentReject) {
qrPaymentReject(new Error('用户取消支付'))
}
qrPaymentResolve = null
qrPaymentReject = null
currentQrOrderId = null
qrCodeUrl.value = ''
}
// ==================== 二维码支付轮询 ====================
function startQrPolling(orderId) {
if (isQrPolling) return
isQrPolling = true
let pollCount = 0
const maxRetry = 300 // 5分钟超时 (300秒)
function doQuery() {
if (!showQrPayment.value || qrPaymentStatus.value !== 'pending') return
pollCount++
getPaymentStatus(orderId)
.then(res => {
if (res.code === 200 && res.data) {
const status = res.data.status || res.data.payStatus
console.log(`[QrPoll] 第 ${pollCount} 次查询,状态:`, status)
if (status === 'SUCCESS') {
qrPaymentStatus.value = 'success'
stopQrPolling()
if (qrPaymentResolve) {
qrPaymentResolve(true)
}
return
} else if (status === 'FAIL' || status === 'CLOSED') {
qrPaymentStatus.value = 'fail'
stopQrPolling()
if (qrPaymentReject) {
qrPaymentReject(new Error('支付失败'))
}
return
}
}
// 继续轮询
if (pollCount < maxRetry && showQrPayment.value && qrPaymentStatus.value === 'pending') {
qrPollingTimer = setTimeout(doQuery, 1000)
} else if (qrPaymentStatus.value === 'pending') {
qrPaymentStatus.value = 'fail'
stopQrPolling()
if (qrPaymentReject) {
qrPaymentReject(new Error('支付超时'))
}
}
})
.catch(err => {
console.error('[QrPoll] 查询异常:', err)
if (pollCount < maxRetry && showQrPayment.value && qrPaymentStatus.value === 'pending') {
qrPollingTimer = setTimeout(doQuery, 1000)
} else if (qrPaymentStatus.value === 'pending') {
qrPaymentStatus.value = 'fail'
stopQrPolling()
if (qrPaymentReject) {
qrPaymentReject(new Error('支付查询失败'))
}
}
})
}
// 延迟 2 秒开始轮询
qrPollingTimer = setTimeout(doQuery, 2000)
}
function stopQrPolling() {
isQrPolling = false
if (qrPollingTimer) {
clearTimeout(qrPollingTimer)
qrPollingTimer = null
}
}
// ==================== 核心购买逻辑 ====================
async function handlePurchase() {
if (!selectedCard.value) {
@@ -278,72 +459,114 @@ async function handlePurchase() {
try {
const store = loadMemberStore()
const memberId = store.memberProfile?.id || store.profile?.id
// if (!memberId) {
// uni.showToast({ title: '请先登录', icon: 'none' })
// loading.value = false
// return
// }
// 金额转换:元 -> 分
const transAmt = String(Math.round(selectedCard.value.price * 100))
const cardName = selectedCard.value.cardName
// 1. 创建支付订单
uni.showLoading({ title: '创建订单中...' })
const payRes = await createPayment({
memberId: memberId,
orderType: 'MEMBER_CARD',
goodsDesc: `购买${cardName}`,
transAmt: transAmt,
tradeType: TRADE_TYPE,
remark: `会员卡类型ID:${selectedCard.value.id}`
})
// 根据环境选择支付方式
if (isWebEnv) {
// ==================== Web环境:扫码支付 ====================
uni.showLoading({ title: '创建订单...' })
if (payRes.code !== 200) {
const payRes = await createQrCodePayment({
memberId: memberId,
orderType: 'MEMBER_CARD',
goodsDesc: `购买${cardName}`,
transAmt: transAmt,
tradeType: 'QRCODE',
remark: `会员卡类型ID:${selectedCard.value.id}`
})
if (payRes.code !== 200) {
uni.hideLoading()
uni.showToast({ title: payRes.message || '创建支付订单失败', icon: 'none' })
loading.value = false
return
}
const payData = payRes.data
const orderId = payData.orderId
const qrCode = payData.qrCode
if (!qrCode) {
uni.hideLoading()
uni.showToast({ title: '获取二维码失败', icon: 'none' })
loading.value = false
return
}
// 显示二维码支付对话框
uni.hideLoading()
uni.showToast({ title: payRes.message || '创建支付订单失败', icon: 'none' })
loading.value = false
return
await new Promise((resolve, reject) => {
qrPaymentResolve = resolve
qrPaymentReject = reject
openQrPayment(qrCode, orderId)
})
// 支付成功后继续购买会员卡
} else {
// ==================== App环境:唤起支付宝App ====================
uni.showLoading({ title: '创建订单...' })
const payRes = await createPayment({
memberId: memberId,
orderType: 'MEMBER_CARD',
goodsDesc: `购买${cardName}`,
transAmt: transAmt,
tradeType: TRADE_TYPE,
remark: `会员卡类型ID:${selectedCard.value.id}`
})
if (payRes.code !== 200) {
uni.hideLoading()
uni.showToast({ title: payRes.message || '创建支付订单失败', icon: 'none' })
loading.value = false
return
}
const payData = payRes.data
const orderId = payData.orderId
const payUrl = payData.payUrl || payData.payInfo || payData.h5PayUrl
if (!payUrl) {
uni.hideLoading()
uni.showToast({ title: '获取支付链接失败', icon: 'none' })
loading.value = false
return
}
// 唤起支付宝
uni.hideLoading()
uni.showLoading({ title: '正在唤起支付宝...' })
try {
await invokeAlipayApp(payUrl)
} catch (err) {
uni.hideLoading()
uni.showToast({ title: err.message || '唤起支付宝失败', icon: 'none' })
loading.value = false
return
}
// 轮询支付状态
uni.showLoading({ title: '等待支付结果...' })
await startPolling(orderId)
}
const payData = payRes.data
const orderId = payData.orderId
// 2. 获取支付链接
const payUrl = payData.payUrl || payData.payInfo || payData.h5PayUrl
if (!payUrl) {
uni.hideLoading()
uni.showToast({ title: '获取支付链接失败', icon: 'none' })
loading.value = false
return
}
// 3. 唤起支付宝 App
uni.hideLoading()
uni.showLoading({ title: '正在唤起支付宝...' })
try {
await invokeAlipayApp(payUrl)
} catch (err) {
uni.hideLoading()
uni.showToast({ title: '唤起支付宝失败,请确认已安装支付宝', icon: 'none' })
loading.value = false
return
}
// 4. 轮询支付状态
uni.showLoading({ title: '等待支付结果...' })
await startPolling(orderId)
// 5. 支付成功,购买会员卡
// 支付成功,购买会员卡
uni.hideLoading()
const memberRes = await purchaseMemberCard({
memberId: memberId,
memberCardId: selectedCard.value.id,
sourceOrderId: orderId
sourceOrderId: currentQrOrderId || currentQrOrderId
})
if (memberRes.code === 200) {
// 关闭二维码对话框
if (showQrPayment.value) {
showQrPayment.value = false
}
uni.showToast({ title: '购买成功 🎉', icon: 'success' })
setTimeout(() => {
uni.redirectTo({
@@ -377,6 +600,7 @@ onUnmounted(() => {
pollingTimer.value = null
}
isPolling.value = false
stopQrPolling()
if (typeof plus !== 'undefined') {
plus.globalEvent.removeEventListener('resume', () => {})
}
@@ -691,4 +915,157 @@ onUnmounted(() => {
font-weight: 600;
color: #FFFFFF;
}
// ==================== 模拟支付对话框 ====================
.mock-payment-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.qr-payment-dialog {
width: 320px;
background: #FFFFFF;
border-radius: 16px;
overflow: hidden;
}
.qr-payment-dialog__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 24px;
border-bottom: 1px solid #E2E8F0;
}
.qr-payment-dialog__title {
font-size: 18px;
font-weight: 600;
color: #1A202C;
}
.qr-payment-dialog__close {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: #A0AEC0;
}
.qr-payment-dialog__body {
padding: 24px;
}
.qr-payment-dialog__order,
.qr-payment-dialog__amount {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.qr-payment-dialog__label {
font-size: 14px;
color: #718096;
}
.qr-payment-dialog__value {
font-size: 14px;
color: #2D3748;
}
.qr-payment-dialog__money {
font-size: 24px;
font-weight: 700;
color: #E53E3E;
}
.qr-payment-dialog__qrcode {
display: flex;
justify-content: center;
align-items: center;
padding: 20px 0;
background: #F7FAFC;
border-radius: 8px;
margin: 16px 0;
}
.qr-payment-dialog__qrcode-img {
width: 200px;
height: 200px;
}
.qr-payment-dialog__qrcode-loading {
font-size: 14px;
color: #A0AEC0;
padding: 80px 0;
}
.qr-payment-dialog__tips {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.qr-payment-dialog__tip {
font-size: 12px;
color: #718096;
&--warn {
color: #ED8936;
}
}
.qr-payment-dialog__result {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0;
}
.qr-payment-dialog__icon {
font-size: 48px;
margin-bottom: 16px;
}
.qr-payment-dialog__result-text {
font-size: 16px;
font-weight: 600;
color: #2D3748;
}
.qr-payment-dialog__footer {
display: flex;
gap: 12px;
padding: 0 24px 24px;
}
.qr-payment-dialog__btn {
flex: 1;
padding: 14px 0;
border-radius: 8px;
text-align: center;
font-size: 16px;
font-weight: 500;
&--primary {
background: #1677FF;
color: #FFFFFF;
}
&--secondary {
background: #E2E8F0;
color: #4A5568;
}
}
</style>