984 lines
24 KiB
Vue
984 lines
24 KiB
Vue
<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">{{ currencySymbol }} {{ 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">{{ currencySymbol }}</text>
|
||
<text class="amount">{{ totalAmount }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 支付方式选择 -->
|
||
<view class="order-card" v-if="paymentMethods.length">
|
||
<view class="card-header">
|
||
<view class="card-title-bar"></view>
|
||
<view class="card-title">{{ $t('payment.paymentMethod') }}</view>
|
||
</view>
|
||
<view class="payment-method-item" v-for="(item, idx) in paymentMethods" :key="`${item.paymentMethodType}-${idx}`"
|
||
@click="selectPaymentMethod(item.paymentMethodType)">
|
||
<view class="method-left">
|
||
<text class="method-name">{{ item.paymentMethodName }}</text>
|
||
</view>
|
||
<view class="method-right">
|
||
<view class="method-radio" :class="{ active: selectedPaymentMethod === item.paymentMethodType }">
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部操作栏 -->
|
||
<view class="bottom-bar">
|
||
<view class="pay-btn" @click="handlePayment">
|
||
<text class="currency-small">{{ currencySymbol }}</text>
|
||
<text class="amount-large">{{ totalAmount }}</text>
|
||
<text class="pay-text">{{ $t('payment.payNow') }}</text>
|
||
</view>
|
||
<view class="cancel-btn" @click="handleCancelOrder">
|
||
{{ $t('order.cancelOrder') }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import {
|
||
ref,
|
||
computed,
|
||
reactive,
|
||
onMounted
|
||
} from 'vue'
|
||
import {
|
||
onLoad
|
||
} from '@dcloudio/uni-app'
|
||
import {
|
||
queryById,
|
||
cancelOrder,
|
||
createWxPayment,
|
||
getWxPaymentStatus,
|
||
createAliPayment,
|
||
getAliPaymentStatus,
|
||
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 currencyCode = ref('USD')
|
||
|
||
// 支付方式相关(微信/支付宝/H5-Antom 多平台)
|
||
const paymentMethods = ref([])
|
||
const selectedPaymentMethod = ref('WECHAT') // 默认微信
|
||
|
||
// 地点名称(可以从设备信息中获取,这里先用默认值)
|
||
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 (currencyCode.value === 'IDR') {
|
||
const raw =
|
||
passedTotalAmount.value != null && passedTotalAmount.value !== ''
|
||
? passedTotalAmount.value
|
||
: orderInfo.value.deposit
|
||
const num = Number(String(raw ?? '').replace(/[^\d.-]/g, ''))
|
||
return Number.isFinite(num) ? String(Math.trunc(num)) : String(raw || '0')
|
||
}
|
||
if (passedTotalAmount.value !== null) {
|
||
return parseFloat(passedTotalAmount.value).toFixed(2);
|
||
}
|
||
const deposit = parseFloat(orderInfo.value.deposit || passedDepositAmount.value || 99)
|
||
return deposit.toFixed(2)
|
||
})
|
||
|
||
const currencySymbol = computed(() => {
|
||
if (currencyCode.value === 'IDR') return 'Rp'
|
||
return 'USD'
|
||
})
|
||
|
||
// 加载订单信息
|
||
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();
|
||
// 如果订单状态是等待支付,启动相应的支付状态轮询
|
||
if (orderInfo.value.orderStatus == 'waiting_for_payment') {
|
||
// 使用当前选中的支付方式类型进行轮询
|
||
startPaymentStatusPolling();
|
||
}
|
||
} 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;
|
||
currencyCode.value = (res.data?.position?.currency || currencyCode.value || 'USD').toUpperCase()
|
||
|
||
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 () => {
|
||
const methods = []
|
||
|
||
// 小程序环境下:微信 / 支付宝
|
||
// #ifdef MP-WEIXIN
|
||
methods.push({
|
||
paymentMethodType: 'WECHAT',
|
||
paymentMethodName: '微信支付'
|
||
})
|
||
// #endif
|
||
// #ifdef MP-ALIPAY
|
||
methods.push({
|
||
paymentMethodType: 'ALIPAY',
|
||
paymentMethodName: '支付宝支付'
|
||
})
|
||
// #endif
|
||
|
||
// H5 环境:使用 Antom 聚合支付(多通道)
|
||
// #ifdef H5
|
||
if (orderInfo.value.orderNo) {
|
||
try {
|
||
const osType = getOsType();
|
||
const res = await getAntomPaymentMethods(orderInfo.value.orderNo, osType);
|
||
console.log(res.data);
|
||
console.log(res.data.paymentOptions,'支付方式');
|
||
// if (res.code === 200 && res.data && res.data.paymentOptions) {
|
||
// res.data.paymentOptions.forEach(item => {
|
||
// methods.push({
|
||
// paymentMethodType: item.paymentMethodType,
|
||
// paymentMethodName: item.paymentMethodName || item.paymentMethodType
|
||
// });
|
||
// });
|
||
// }
|
||
// 每条选项的 paymentMethodType 必须唯一下发到 Antom 的 paymentType 参数,否则 v-for 的 key 与单选态会异常
|
||
methods.push({
|
||
paymentMethodType: 'ALIPAY_DANA',
|
||
paymentMethodName: t('payment.ALIPAYDANA')
|
||
})
|
||
// methods.push({
|
||
// paymentMethodType: 'ALIPAY_HK',
|
||
// paymentMethodName: t('payment.alipayHk')
|
||
// })
|
||
// methods.push({
|
||
// paymentMethodType: 'ALIPAY_ID',
|
||
// paymentMethodName: t('payment.alipayId')
|
||
// })
|
||
|
||
} catch (error) {
|
||
console.error('获取 Antom 支付方式失败:', error);
|
||
}
|
||
}
|
||
// #endif
|
||
|
||
// 兜底:至少保留一个微信
|
||
if (!methods.length) {
|
||
methods.push({
|
||
paymentMethodType: 'WECHAT',
|
||
paymentMethodName: '微信支付'
|
||
})
|
||
}
|
||
|
||
paymentMethods.value = methods
|
||
if (paymentMethods.value.length > 0) {
|
||
selectedPaymentMethod.value = paymentMethods.value[0].paymentMethodType
|
||
}
|
||
}
|
||
|
||
// 选择支付方式
|
||
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 method = selectedPaymentMethod.value
|
||
|
||
// 微信小程序支付押金
|
||
// #ifdef MP-WEIXIN
|
||
if (method === 'WECHAT') {
|
||
const wxRes = await createWxPayment(orderInfo.value.orderNo)
|
||
if (wxRes.code === 200 && wxRes.data) {
|
||
const payData = wxRes.data
|
||
await new Promise((resolve, reject) => {
|
||
wx.requestPayment({
|
||
...payData,
|
||
success: resolve,
|
||
fail: reject
|
||
})
|
||
})
|
||
// 支付成功后轮询微信支付状态
|
||
startPaymentStatusPolling('WECHAT')
|
||
return
|
||
} else {
|
||
throw new Error(wxRes.msg || t('payment.createPayOrderFailed'))
|
||
}
|
||
}
|
||
// #endif
|
||
|
||
// 支付宝小程序支付押金
|
||
// #ifdef MP-ALIPAY
|
||
if (method === 'ALIPAY') {
|
||
const aliRes = await createAliPayment(orderInfo.value.orderNo)
|
||
if (aliRes.code === 200 && aliRes.data) {
|
||
// 后端当前实际返回结构示例:
|
||
// { code:200, msg:'操作成功', data:{ tradeNo:'xxx', outTradeNo:'yyy' } }
|
||
const tradeNO = aliRes.data.tradeNo || aliRes.data.outTradeNo
|
||
const payForm = aliRes.data.payForm || aliRes.data.orderStr
|
||
|
||
if (!tradeNO && !payForm) {
|
||
throw new Error('未获取到支付宝支付参数')
|
||
}
|
||
|
||
// 优先使用 tradeNO 方式,其次兼容老的 orderStr 方式
|
||
if (tradeNO) {
|
||
my.tradePay({
|
||
tradeNO,
|
||
success: (res) => {
|
||
if (res.resultCode === '9000') {
|
||
startPaymentStatusPolling('ALIPAY')
|
||
} else {
|
||
uni.showToast({
|
||
title: t('payment.paymentFailed'),
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
fail: () => {
|
||
uni.showToast({
|
||
title: t('payment.paymentFailed'),
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
} else {
|
||
my.tradePay({
|
||
orderStr: payForm,
|
||
success: (res) => {
|
||
if (res.resultCode === '9000') {
|
||
startPaymentStatusPolling('ALIPAY')
|
||
} else {
|
||
uni.showToast({
|
||
title: t('payment.paymentFailed'),
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
fail: () => {
|
||
uni.showToast({
|
||
title: t('payment.paymentFailed'),
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
}
|
||
return
|
||
} else {
|
||
throw new Error(aliRes.msg || t('payment.createPayOrderFailed'))
|
||
}
|
||
}
|
||
// #endif
|
||
|
||
// H5 + Antom 聚合支付
|
||
// #ifdef H5
|
||
const osType = getOsType();
|
||
const res = await createAntomPayment(orderInfo.value.orderNo, method, osType)
|
||
|
||
if (res && res.code === 200 && res.data) {
|
||
const paymentUrl = res.data.h5Url;
|
||
|
||
if (!paymentUrl) {
|
||
throw new Error('未获取到支付链接');
|
||
}
|
||
|
||
uni.hideLoading();
|
||
uni.setStorageSync('pendingPaymentNo', orderId.value);
|
||
window.location.href = paymentUrl;
|
||
// plus.runtime.openURL(paymentUrl, function(err) {
|
||
// console.log('打开失败');
|
||
// window.location.href = paymentUrl;
|
||
// })
|
||
// ==========================================================
|
||
|
||
// 开始轮询支付状态(传入当前选中的支付方式类型)
|
||
startPaymentStatusPolling(method);
|
||
return
|
||
} else {
|
||
throw new Error(res?.msg || t('payment.createPayOrderFailed'))
|
||
}
|
||
// #endif
|
||
} catch (error) {
|
||
console.error('支付失败:', error)
|
||
uni.showToast({
|
||
title: error.message || t('payment.paymentFailed'),
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
|
||
const handleCancelOrder = () => {
|
||
if (!orderId.value) {
|
||
uni.showToast({
|
||
title: t('order.orderNotExist'),
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
uni.showModal({
|
||
title: t('order.confirmCancel'),
|
||
content: t('order.confirmCancelContent'),
|
||
success: async (res) => {
|
||
if (!res.confirm) return;
|
||
try {
|
||
uni.showLoading({
|
||
title: t('common.processing')
|
||
});
|
||
const result = await cancelOrder({
|
||
orderId: orderId.value
|
||
});
|
||
if (result && result.code === 200) {
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: t('order.cancelSuccess'),
|
||
icon: 'success'
|
||
});
|
||
setTimeout(() => {
|
||
uni.reLaunch({
|
||
url: '/pages/index/index'
|
||
});
|
||
}, 800);
|
||
} else {
|
||
throw new Error(result?.msg || t('order.cancelFailed'));
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: error.message || t('order.cancelFailed'),
|
||
icon: 'none'
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 轮询定时器
|
||
let pollingTimer = null;
|
||
|
||
/**
|
||
* 统一的支付状态轮询方法
|
||
* @param {string} paymentMethodType - 支付方式类型,如 'WECHAT' | 'ALIPAY' | 'WECHATPAY' 等,与 selectedPaymentMethod 保持一致
|
||
*/
|
||
const startPaymentStatusPolling = (paymentMethodType = null) => {
|
||
// 清除之前的定时器
|
||
if (pollingTimer) {
|
||
clearInterval(pollingTimer);
|
||
}
|
||
|
||
// 如果没有传入支付方式类型,使用当前选中的支付方式
|
||
const methodType = paymentMethodType || selectedPaymentMethod.value;
|
||
|
||
let pollCount = 0;
|
||
const maxPollCount = 60; // 最多轮询60次(5分钟)
|
||
|
||
pollingTimer = setInterval(async () => {
|
||
pollCount++;
|
||
|
||
if (pollCount > maxPollCount) {
|
||
clearInterval(pollingTimer);
|
||
uni.showToast({
|
||
title: '支付超时,请重新支付',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
try {
|
||
let res;
|
||
let status, successStatus, failStatuses;
|
||
|
||
// #ifdef H5
|
||
// H5 环境统一使用 Antom 聚合支付 API
|
||
const osType = getOsType();
|
||
res = await getAntomPaymentStatus(orderInfo.value.orderNo, osType);
|
||
if (res && res.code === 200 && res.data) {
|
||
status = res.data.paymentStatus;
|
||
successStatus = 'SUCCESS';
|
||
failStatuses = ['FAIL', 'CANCELLED'];
|
||
}
|
||
// #endif
|
||
|
||
// #ifdef MP-WEIXIN
|
||
// 微信小程序:根据支付方式类型判断
|
||
if (methodType === 'WECHAT' || methodType === 'WECHATPAY') {
|
||
res = await getWxPaymentStatus(orderInfo.value.orderNo);
|
||
if (res && res.code === 200 && res.data) {
|
||
status = res.data.tradeStatus;
|
||
successStatus = 'SUCCESS';
|
||
failStatuses = ['FAIL', 'CANCELLED'];
|
||
}
|
||
}
|
||
// #endif
|
||
|
||
// #ifdef MP-ALIPAY
|
||
// 支付宝小程序
|
||
if (methodType === 'ALIPAY') {
|
||
res = await getAliPaymentStatus(orderInfo.value.orderNo);
|
||
if (res && res.code === 200 && res.data) {
|
||
status = res.data.tradeStatus;
|
||
successStatus = 'TRADE_SUCCESS';
|
||
failStatuses = ['TRADE_FAIL', 'TRADE_CANCELLED'];
|
||
}
|
||
}
|
||
// #endif
|
||
|
||
// 处理支付状态结果
|
||
if (res && res.code === 200 && res.data && status) {
|
||
console.log(status === successStatus);
|
||
|
||
// 支付成功
|
||
if (status === successStatus) {
|
||
clearInterval(pollingTimer);
|
||
|
||
try {
|
||
await updateUserBalance(orderId.value);
|
||
} catch (error) {
|
||
console.warn('更新用户余额失败:', error);
|
||
}
|
||
|
||
setTimeout(() => {
|
||
uni.redirectTo({
|
||
url: `/pages/order/success?orderId=${orderId.value}&deviceId=${orderInfo.value.deviceNo}`
|
||
});
|
||
}, 1500);
|
||
}
|
||
// 支付失败
|
||
else if (failStatuses && failStatuses.includes(status)) {
|
||
clearInterval(pollingTimer);
|
||
uni.showToast({
|
||
title: '支付失败,请重新支付',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
}
|
||
} catch (error) {
|
||
const errorMsg = methodType === 'ALIPAY' ? '支付宝' : (methodType === 'WECHAT' ||
|
||
methodType === 'WECHATPAY') ? '微信' : '支付';
|
||
console.error(`查询${errorMsg}支付状态失败:`, error);
|
||
}
|
||
}, 10000); // 每10秒查询一次
|
||
}
|
||
|
||
// 兼容性方法:保持原有函数名,内部调用统一方法
|
||
const startWxPaymentStatusPolling = () => startPaymentStatusPolling('WECHAT');
|
||
const startAliPaymentStatusPolling = () => startPaymentStatusPolling('ALIPAY');
|
||
|
||
// 页面卸载时清除定时器
|
||
onMounted(() => {
|
||
return () => {
|
||
if (pollingTimer) {
|
||
clearInterval(pollingTimer);
|
||
}
|
||
};
|
||
});
|
||
|
||
// 格式化时间
|
||
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) => {
|
||
// 设置导航栏标题为待支付
|
||
uni.setNavigationBarTitle({
|
||
title: t('payment.waitingForPayment')
|
||
})
|
||
|
||
// 优先从 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;
|
||
}
|
||
}
|
||
}
|
||
|
||
.payment-method-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 24rpx 0;
|
||
border-bottom: 1rpx solid #f5f5f5;
|
||
|
||
&:last-child {
|
||
border-bottom-width: 0;
|
||
}
|
||
|
||
.method-left {
|
||
display: flex;
|
||
align-items: center;
|
||
flex: 1;
|
||
}
|
||
|
||
.method-name {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.method-right {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
margin-left: 24rpx;
|
||
}
|
||
|
||
.method-radio {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
border-radius: 50%;
|
||
border: 2rpx solid #d9d9d9;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.method-radio.active {
|
||
border-color: #52c41a;
|
||
background-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);
|
||
}
|
||
}
|
||
|
||
.cancel-btn {
|
||
width: 100%;
|
||
//height: 84rpx;
|
||
margin-top: 16rpx;
|
||
//border-radius: 42rpx;
|
||
//border: 2rpx solid #d9d9d9;
|
||
//background: #fff;
|
||
color: #666;
|
||
font-size: 24rpx;
|
||
font-weight: 500;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
&:active {
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style> |