Files
uni-fans-score/pages/order/payment.vue
T
2026-02-05 17:38:19 +08:00

708 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="payment-container">
<!-- 地点信息卡片 -->
<view class="location-card">
<view class="location-header">
<!-- <view class="location-icon">📍</view> -->
<image src="@/static/device_location.png" mode="aspectFit" class="location-icon"></image>
<text class="location-name">{{ locationName }}</text>
<view class="status-badge">{{ $t('device.available') }}</view>
</view>
<view class="device-info">
<text class="device-label">{{ $t('order.deviceNo') }}</text>
<text class="device-value">{{ orderInfo.deviceNo || '-' }}</text>
</view>
</view>
<!-- 订单信息和费用信息 -->
<view class="order-card">
<view class="card-header">
<view class="card-title-bar"></view>
<view class="card-title">{{ $t('payment.orderInfo') }}</view>
</view>
<view class="info-item">
<text class="label">{{ $t('order.orderNo') }}</text>
<text class="value">{{ orderInfo.orderNo || '-' }}</text>
</view>
<view class="info-item">
<text class="label">{{ $t('order.deviceNo') }}</text>
<text class="value">{{ orderInfo.deviceNo || '-' }}</text>
</view>
<view class="info-item">
<text class="label">{{ $t('payment.createTime') }}</text>
<text class="value">{{ orderInfo.createTime || '-' }}</text>
</view>
<!-- 费用信息部分 -->
<view class="card-header" style="margin-top: 32rpx;">
<view class="card-title-bar"></view>
<view class="card-title">{{ $t('payment.feeInfo') }}</view>
</view>
<view class="price-item">
<text class="label">{{ $t('payment.deposit') }}</text>
<text class="value">¥ {{ orderInfo.deposit || '90' }}</text>
</view>
<!-- <view class="price-item">
<text class="label">{{ $t('payment.package') }}</text>
<text class="value">{{ packageText }}</text>
</view> -->
<view class="price-item total">
<text class="label">{{ $t('payment.total') }}</text>
<view class="total-value">
<text class="currency">¥</text>
<text class="amount">{{ totalAmount }}</text>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="pay-btn" @click="handlePayment">
<text class="currency-small">¥</text>
<text class="amount-large">{{ totalAmount }}</text>
<text class="pay-text">{{ $t('payment.payNow') }}</text>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
computed,
reactive,
onMounted
} from 'vue'
import {
onLoad
} from '@dcloudio/uni-app'
import {
queryById,
createAntomPayment,
getAntomPaymentMethods,
getAntomPaymentStatus
} from '@/config/api/order.js'
import {
getDeviceInfo
} from '@/config/api/device.js'
import {
updateUserBalance
} from '@/config/api/user.js'
import {
useI18n
} from '@/utils/i18n.js'
const {
t
} = useI18n()
const orderId = ref(null)
const deviceNo = ref(null)
const orderInfo = ref({})
const deviceInfo = ref(null)
const passedTotalAmount = ref(null)
const passedDepositAmount = ref(null)
// 倒计时相关
const countdown = ref(15 * 60) // 15分钟 = 900秒
let countdownTimer = null
// 支付方式相关
const paymentMethods = ref([])
const selectedPaymentMethod = ref('ALIPAY') // 默认选择支付宝
// 地点名称(可以从设备信息中获取,这里先用默认值)
const locationName = ref('澎创办公室')
// 套餐文本(可以从设备信息中获取,这里先用默认值)
const packageText = computed(() => {
// 这里可以根据实际的设备信息动态生成
return '2元/小时'
})
const orderStatus = reactive({
get text() {
return t('payment.waitingForPayment')
},
get desc() {
return t('payment.pleasePayIn15Min')
},
class: 'waiting'
})
const totalAmount = computed(() => {
if (passedTotalAmount.value !== null) {
return parseFloat(passedTotalAmount.value).toFixed(2);
}
const deposit = parseFloat(orderInfo.value.deposit || passedDepositAmount.value || 99)
return deposit.toFixed(2)
})
// 加载订单信息
const loadOrderInfo = async () => {
// 检查 orderId 是否存在,如果不存在则尝试从缓存获取
if (!orderId.value) {
// #ifdef H5
const cachedOrderId = uni.getStorageSync('pendingPaymentNo');
if (cachedOrderId) {
orderId.value = cachedOrderId;
}
// #endif
}
// 如果两种方式都获取不到 orderId,提示并跳转
if (!orderId.value) {
uni.showToast({
title: t('order.orderNotExist'),
icon: 'none'
});
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index'
});
}, 1500);
return;
}
try {
uni.showLoading({
title: t('common.loading')
})
const res = await queryById(orderId.value)
if (res.code === 200 && res.data) {
const orderData = res.data
// 处理创建时间
let formattedTime;
try {
if (orderData.createTime) {
formattedTime = formatTime(new Date(orderData.createTime));
} else {
formattedTime = formatTime(new Date());
}
} catch (e) {
console.error('时间格式化错误:', e);
formattedTime = formatTime(new Date());
}
orderInfo.value = {
orderNo: orderData.orderNo || orderData.orderId,
deviceNo: orderData.deviceNo,
createTime: formattedTime,
deposit: passedDepositAmount.value || orderData.depositAmount || '99.00',
orderStatus:orderData.orderStatus
}
deviceNo.value = orderData.deviceNo;
await loadDeviceInfo();
await loadPaymentMethods();
// #ifdef H5
if(orderInfo.value.orderStatus=='waiting_for_payment'){
startPaymentStatusPolling();
}
// #endif
} else {
throw new Error(t('order.getOrderFailed'))
}
} catch (error) {
console.error('获取订单信息失败:', error)
uni.showToast({
title: error.message || t('order.getOrderFailed'),
icon: 'none'
})
} finally {
uni.hideLoading()
}
}
// 加载设备信息
const loadDeviceInfo = async () => {
if (!deviceNo.value) return;
try {
const res = await getDeviceInfo(deviceNo.value);
if (res.code === 200 && res.data) {
deviceInfo.value = res.data.device;
if (deviceInfo.value && deviceInfo.value.depositAmount) {
orderInfo.value.deposit = deviceInfo.value.depositAmount;
}
}
} catch (error) {
console.error('获取设备信息失败:', error);
}
}
// 获取操作系统类型
const getOsType = () => {
const systemInfo = uni.getSystemInfoSync();
console.log(uni.getSystemInfoSync());
const platform = systemInfo.platform;
console.log('当前系统类型:', uni.getSystemInfoSync().platform);
// 根据平台返回对应的 osType
if (platform === 'android') {
return 'ANDROID';
} else if (platform === 'ios') {
return 'IOS';
} else {
// 默认返回 ANDROID
return 'ANDROID';
}
}
// 加载支付方式列表
const loadPaymentMethods = async () => {
if (!orderInfo.value.orderNo) return;
try {
const osType = getOsType();
console.log('当前系统类型:', osType);
const res = await getAntomPaymentMethods(orderInfo.value.orderNo, osType);
if (res.code === 200 && res.data && res.data.paymentOptions) {
paymentMethods.value = res.data.paymentOptions;
console.log('支付方式列表:', paymentMethods.value);
// 如果有支付方式,默认选择第一个
if (paymentMethods.value.length > 0) {
selectedPaymentMethod.value = paymentMethods.value[0].paymentMethodType;
}
}
} catch (error) {
console.error('获取支付方式失败:', error);
// 如果获取失败,使用默认支付方式
paymentMethods.value = [{
paymentMethodType: 'ALIPAY',
paymentMethodName: '支付宝'
},
{
paymentMethodType: 'WECHATPAY',
paymentMethodName: '微信支付'
}
];
}
}
// 选择支付方式
const selectPaymentMethod = (methodType) => {
selectedPaymentMethod.value = methodType;
}
// 获取支付方式图标
const getPaymentIcon = (methodType) => {
const iconMap = {
'ALIPAY': 'alipay',
'WECHATPAY': 'wechat',
'WECHAT': 'wechat'
};
return iconMap[methodType] || 'default';
}
// 处理支付
const handlePayment = async () => {
if (!selectedPaymentMethod.value) {
uni.showToast({
title: '请选择支付方式',
icon: 'none'
});
return;
}
try {
uni.showLoading({
title: t('common.processing')
})
const osType = getOsType();
const res = await createAntomPayment(orderInfo.value.orderNo, selectedPaymentMethod.value, osType)
if (res && res.code === 200 && res.data) {
const paymentUrl = res.data.h5Url;
if (!paymentUrl) {
throw new Error('未获取到支付链接');
}
uni.hideLoading();
// #ifdef H5
uni.setStorageSync('pendingPaymentNo', orderId.value);
// 跳转到支付页面
// uni.navigateTo({
// url: `/pages/webview/index?url=${encodeURIComponent(paymentUrl)}&title=支付`
// });
window.open(paymentUrl);
// #endif
// 开始轮询支付状态
startPaymentStatusPolling();
} else {
throw new Error(res?.msg || t('payment.createPayOrderFailed'))
}
} catch (error) {
console.error('支付失败:', error)
uni.showToast({
title: error.message || t('payment.paymentFailed'),
icon: 'none'
})
} finally {
uni.hideLoading()
}
}
// 轮询支付状态
let pollingTimer = null;
const startPaymentStatusPolling = () => {
// 清除之前的定时器
if (pollingTimer) {
clearInterval(pollingTimer);
}
let pollCount = 0;
const maxPollCount = 60; // 最多轮询60次(5分钟)
pollingTimer = setInterval(async () => {
pollCount++;
if (pollCount > maxPollCount) {
clearInterval(pollingTimer);
uni.showToast({
title: '支付超时,请重新支付',
icon: 'none'
});
return;
}
try {
const osType = getOsType();
const res = await getAntomPaymentStatus(orderInfo.value.orderNo, osType);
if (res && res.code === 200 && res.data) {
const paymentStatus = res.data.paymentStatus;
if (paymentStatus === 'SUCCESS') {
clearInterval(pollingTimer);
// uni.showToast({
// title: t('payment.paymentSuccess'),
// icon: 'success'
// });
try {
await updateUserBalance(orderId.value);
} catch (error) {
console.warn('更新用户余额失败:', error);
}
console.log(orderInfo);
setTimeout(() => {
uni.redirectTo({
url: `/pages/order/success?orderId=${orderId.value}&deviceId=${orderInfo.value.deviceNo}`
});
}, 1500);
} else if (paymentStatus === 'FAIL' || paymentStatus === 'CANCELLED') {
clearInterval(pollingTimer);
uni.showToast({
title: '支付失败,请重新支付',
icon: 'none'
});
}
}
} catch (error) {
console.error('查询支付状态失败:', error);
}
}, 5000); // 每5秒查询一次
}
// 更新导航栏倒计时
const updateNavBarCountdown = () => {
const minutes = Math.floor(countdown.value / 60).toString().padStart(2, '0')
const seconds = (countdown.value % 60).toString().padStart(2, '0')
uni.setNavigationBarTitle({
title: `待支付 ${minutes}:${seconds}`
})
}
// 开始倒计时
const startCountdown = () => {
// 清除之前的定时器
if (countdownTimer) {
clearInterval(countdownTimer)
}
// 立即更新一次
updateNavBarCountdown()
// 每秒更新
countdownTimer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(countdownTimer)
uni.showToast({
title: t('order.paymentFailedRetry'),
icon: 'none'
})
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index'
})
}, 1500)
return
}
updateNavBarCountdown()
}, 1000)
}
// 页面卸载时清除定时器
onMounted(() => {
return () => {
if (pollingTimer) {
clearInterval(pollingTimer);
}
if (countdownTimer) {
clearInterval(countdownTimer);
}
};
});
// 格式化时间
const formatTime = (date) => {
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const hour = date.getHours().toString().padStart(2, '0')
const minute = date.getMinutes().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hour}:${minute}`
}
onLoad((options) => {
// 启动倒计时
startCountdown()
// 优先从 options 中获取 orderId
if (options && options.orderId) {
orderId.value = options.orderId
if (options.totalAmount) {
passedTotalAmount.value = options.totalAmount;
}
if (options.depositAmount) {
passedDepositAmount.value = options.depositAmount;
}
if (options.feeConfig) {
try {
const feeConfigStr = decodeURIComponent(options.feeConfig)
deviceInfo.value = {
feeConfig: feeConfigStr
}
} catch (e) {
console.error('解析URL中的feeConfig失败:', e)
}
}
loadOrderInfo()
}
// #ifdef H5
// 如果 options 中没有 orderId,尝试从缓存中获取(H5 环境)
else {
const cachedOrderId = uni.getStorageSync('pendingPaymentNo');
console.log("订单编号:"+cachedOrderId);
if (cachedOrderId) {
orderId.value = cachedOrderId;
}
}
// #endif
loadOrderInfo()
// 调用 loadOrderInfo,内部会再次检查 orderId 是否存在
})
</script>
<style lang="scss" scoped>
.payment-container {
min-height: 100vh;
background: #F5F5F5;
padding: 24rpx;
padding-bottom: 200rpx;
box-sizing: border-box;
.location-card {
background: #fff;
border-radius: 24rpx;
padding: 32rpx;
margin-bottom: 24rpx;
.location-header {
display: flex;
align-items: center;
margin-bottom: 24rpx;
.location-icon {
font-size: 40rpx;
width: 40rpx;
height: 40rpx;
margin-right: 12rpx;
}
.location-name {
font-size: 36rpx;
font-weight: 600;
color: #333;
flex: 1;
}
.status-badge {
background: #D4F4DD;
color: #52C41A;
font-size: 24rpx;
padding: 8rpx 20rpx;
border-radius: 8rpx;
}
}
.device-info {
font-size: 28rpx;
color: #666;
.device-label {
color: #999;
}
.device-value {
color: #333;
}
}
}
.order-card {
background: #fff;
border-radius: 24rpx;
padding: 32rpx;
margin-bottom: 24rpx;
.card-header {
display: flex;
align-items: center;
margin-bottom: 32rpx;
.card-title-bar {
width: 8rpx;
height: 32rpx;
background: #52C41A;
border-radius: 4rpx;
margin-right: 12rpx;
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
}
.info-item,
.price-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 0;
.label {
font-size: 28rpx;
color: #666;
}
.value {
font-size: 28rpx;
color: #333;
text-align: right;
}
}
.price-item.total {
padding-top: 32rpx;
justify-content: flex-end !important;
// border-top: 1rpx solid #F0F0F0;
margin-top: 16rpx;
.label {
font-size: 28rpx;
color: #666;
margin-right: 10rpx;
}
.total-value {
display: flex;
align-items: baseline;
.currency {
font-size: 22rpx;
color: #52C41A;
margin-right: 4rpx;
}
.amount {
font-size: 32rpx;
font-weight: 700;
color: #52C41A;
}
}
}
}
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background: #fff;
padding: 24rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
z-index: 100;
.pay-btn {
width: 100%;
padding: 20rpx 0;
// height: 96rpx;
background: linear-gradient(135deg, #52C41A 0%, #73D13D 100%);
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
box-shadow: 0 8rpx 24rpx rgba(82, 196, 26, 0.3);
.currency-small {
font-size: 32rpx;
font-weight: 600;
margin-right: 4rpx;
// text-align: ;
}
.amount-large {
font-size: 52rpx;
font-weight: 700;
margin-right: 16rpx;
}
.pay-text {
font-size: 32rpx;
font-weight: 600;
}
&:active {
opacity: 0.9;
transform: scale(0.98);
}
}
}
}
</style>