fix:多平台兼容

This commit is contained in:
2026-03-16 11:52:27 +08:00
parent b3836b8bf2
commit a79cf10bd4
18 changed files with 358 additions and 295 deletions
+118 -74
View File
@@ -4,7 +4,7 @@
<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>
<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>
@@ -55,6 +55,24 @@
</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 in paymentMethods" :key="item.paymentMethodType"
@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">
@@ -99,7 +117,7 @@
const {
t
} = useI18n()
const orderId = ref(null)
const deviceNo = ref(null)
const orderInfo = ref({})
@@ -107,10 +125,6 @@
const passedTotalAmount = ref(null)
const passedDepositAmount = ref(null)
// 倒计时相关
const countdown = ref(15 * 60) // 15分钟 = 900秒
let countdownTimer = null
// 支付方式相关(微信/支付宝/H5-Antom 多平台)
const paymentMethods = ref([])
const selectedPaymentMethod = ref('WECHAT') // 默认微信
@@ -195,14 +209,14 @@
deviceNo: orderData.deviceNo,
createTime: formattedTime,
deposit: passedDepositAmount.value || orderData.depositAmount || '99.00',
orderStatus:orderData.orderStatus
orderStatus: orderData.orderStatus
}
deviceNo.value = orderData.deviceNo;
await loadDeviceInfo();
await loadPaymentMethods();
// 如果订单状态是等待支付,启动相应的支付状态轮询
if(orderInfo.value.orderStatus=='waiting_for_payment'){
if (orderInfo.value.orderStatus == 'waiting_for_payment') {
// 使用当前选中的支付方式类型进行轮询
startPaymentStatusPolling();
}
@@ -263,10 +277,16 @@
// 小程序环境下:微信 / 支付宝
// #ifdef MP-WEIXIN
methods.push({ paymentMethodType: 'WECHAT', paymentMethodName: '微信支付' })
methods.push({
paymentMethodType: 'WECHAT',
paymentMethodName: '微信支付'
})
// #endif
// #ifdef MP-ALIPAY
methods.push({ paymentMethodType: 'ALIPAY', paymentMethodName: '支付宝支付' })
methods.push({
paymentMethodType: 'ALIPAY',
paymentMethodName: '支付宝支付'
})
// #endif
// H5 环境:使用 Antom 聚合支付(多通道)
@@ -291,7 +311,10 @@
// 兜底:至少保留一个微信
if (!methods.length) {
methods.push({ paymentMethodType: 'WECHAT', paymentMethodName: '微信支付' })
methods.push({
paymentMethodType: 'WECHAT',
paymentMethodName: '微信支付'
})
}
paymentMethods.value = methods
@@ -376,11 +399,17 @@
if (res.resultCode === '9000') {
startPaymentStatusPolling('ALIPAY')
} else {
uni.showToast({ title: t('payment.paymentFailed'), icon: 'none' })
uni.showToast({
title: t('payment.paymentFailed'),
icon: 'none'
})
}
},
fail: () => {
uni.showToast({ title: t('payment.paymentFailed'), icon: 'none' })
uni.showToast({
title: t('payment.paymentFailed'),
icon: 'none'
})
}
})
} else {
@@ -390,11 +419,17 @@
if (res.resultCode === '9000') {
startPaymentStatusPolling('ALIPAY')
} else {
uni.showToast({ title: t('payment.paymentFailed'), icon: 'none' })
uni.showToast({
title: t('payment.paymentFailed'),
icon: 'none'
})
}
},
fail: () => {
uni.showToast({ title: t('payment.paymentFailed'), icon: 'none' })
uni.showToast({
title: t('payment.paymentFailed'),
icon: 'none'
})
}
})
}
@@ -419,7 +454,12 @@
uni.hideLoading();
uni.setStorageSync('pendingPaymentNo', orderId.value);
window.open(paymentUrl);
window.location.href = paymentUrl;
// plus.runtime.openURL(paymentUrl, function(err) {
// console.log('打开失败');
// window.location.href = paymentUrl;
// })
// ==========================================================
// 开始轮询支付状态(传入当前选中的支付方式类型)
startPaymentStatusPolling(method);
@@ -441,7 +481,7 @@
// 轮询定时器
let pollingTimer = null;
/**
* 统一的支付状态轮询方法
* @param {string} paymentMethodType - 支付方式类型,如 'WECHAT' | 'ALIPAY' | 'WECHATPAY' 等,与 selectedPaymentMethod 保持一致
@@ -473,7 +513,7 @@
try {
let res;
let status, successStatus, failStatuses;
// #ifdef H5
// H5 环境统一使用 Antom 聚合支付 API
const osType = getOsType();
@@ -484,7 +524,7 @@
failStatuses = ['FAIL', 'CANCELLED'];
}
// #endif
// #ifdef MP-WEIXIN
// 微信小程序:根据支付方式类型判断
if (methodType === 'WECHAT' || methodType === 'WECHATPAY') {
@@ -496,7 +536,7 @@
}
}
// #endif
// #ifdef MP-ALIPAY
// 支付宝小程序
if (methodType === 'ALIPAY') {
@@ -511,6 +551,8 @@
// 处理支付状态结果
if (res && res.code === 200 && res.data && status) {
console.log(status === successStatus);
// 支付成功
if (status === successStatus) {
clearInterval(pollingTimer);
@@ -526,7 +568,7 @@
url: `/pages/order/success?orderId=${orderId.value}&deviceId=${orderInfo.value.deviceNo}`
});
}, 1500);
}
}
// 支付失败
else if (failStatuses && failStatuses.includes(status)) {
clearInterval(pollingTimer);
@@ -537,66 +579,23 @@
}
}
} catch (error) {
const errorMsg = methodType === 'ALIPAY' ? '支付宝' : (methodType === 'WECHAT' || methodType === 'WECHATPAY') ? '微信' : '支付';
const errorMsg = methodType === 'ALIPAY' ? '支付宝' : (methodType === 'WECHAT' ||
methodType === 'WECHATPAY') ? '微信' : '支付';
console.error(`查询${errorMsg}支付状态失败:`, error);
}
}, 5000); // 每5秒查询一次
}, 10000); // 每10秒查询一次
}
// 兼容性方法:保持原有函数名,内部调用统一方法
const startWxPaymentStatusPolling = () => startPaymentStatusPolling('WECHAT');
const startAliPaymentStatusPolling = () => startPaymentStatusPolling('ALIPAY');
// 更新导航栏倒计时
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);
}
};
});
@@ -612,8 +611,10 @@
}
onLoad((options) => {
// 启动倒计时
startCountdown()
// 设置导航栏标题为待支付
uni.setNavigationBarTitle({
title: t('payment.waitingForPayment')
})
// 优先从 options 中获取 orderId
if (options && options.orderId) {
@@ -637,23 +638,23 @@
console.error('解析URL中的feeConfig失败:', e)
}
}
loadOrderInfo()
}
// #ifdef H5
// 如果 options 中没有 orderId,尝试从缓存中获取(H5 环境)
else {
const cachedOrderId = uni.getStorageSync('pendingPaymentNo');
console.log("订单编号:"+cachedOrderId);
console.log("订单编号:" + cachedOrderId);
if (cachedOrderId) {
orderId.value = cachedOrderId;
}
}
// #endif
loadOrderInfo()
loadOrderInfo()
// 调用 loadOrderInfo,内部会再次检查 orderId 是否存在
})
</script>
@@ -787,6 +788,49 @@ loadOrderInfo()
}
}
}
.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 {
+141
View File
@@ -0,0 +1,141 @@
<template>
<view class="return-map-page">
<view class="image-wrapper">
<movable-area class="movable-area">
<movable-view class="movable-view" direction="all" :scale="true" :scale-min="1" :scale-max="4"
:scale-value="scale" @scale="onScaleChange">
<image :src="imageUrl" mode="widthFix" class="map-image" lazy-load="true"></image>
</movable-view>
</movable-area>
</view>
<view class="bottom-bar">
<view class="btn" @click="resetScale">{{ $t('common.reset') }}</view>
<view class="btn primary" @click="previewImage">{{ $t('common.preview') }}</view>
</view>
</view>
</template>
<script setup>
import {
ref
} from 'vue'
import {
onLoad
} from '@dcloudio/uni-app'
import {
useI18n
} from '@/utils/i18n.js'
const {
t
} = useI18n()
const imageUrl = ref('')
const scale = ref(1)
onLoad((options) => {
if (options && options.imageUrl) {
try {
imageUrl.value = decodeURIComponent(options.imageUrl)
} catch (e) {
imageUrl.value = options.imageUrl
}
}
if (!imageUrl.value) {
uni.showToast({
title: t('common.loadFailed'),
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
uni.setNavigationBarTitle({
title: t('order.returnLocationMap')
})
})
const onScaleChange = (e) => {
if (e && typeof e.detail?.scale === 'number') {
scale.value = e.detail.scale
}
}
const resetScale = () => {
scale.value = 1
}
const previewImage = () => {
if (!imageUrl.value) return
uni.previewImage({
urls: [imageUrl.value]
})
}
</script>
<style lang="scss" scoped>
.return-map-page {
min-height: 100vh;
background-color: #000;
display: flex;
flex-direction: column;
padding-bottom: env(safe-area-inset-bottom);
box-sizing: border-box;
}
.image-wrapper {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
padding: 20rpx;
box-sizing: border-box;
}
.movable-area {
width: 100%;
height: 100%;
}
.movable-view {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.map-image {
width: 100%;
display: block;
border-radius: 16rpx;
}
.bottom-bar {
padding: 16rpx 30rpx 24rpx;
display: flex;
justify-content: flex-end;
gap: 24rpx;
background: rgba(0, 0, 0, 0.8);
}
.btn {
min-width: 160rpx;
height: 72rpx;
border-radius: 36rpx;
border: 2rpx solid #ffffff;
color: #ffffff;
font-size: 28rpx;
display: flex;
align-items: center;
justify-content: center;
&.primary {
background: #07c160;
border-color: #07c160;
}
}
</style>
+15 -15
View File
@@ -131,18 +131,18 @@
}
// 微信快捷登录入口(备用,目前主流程使用一键手机号登录)
const onWeChatLogin = async () => {
try {
await checkAgreement()
await wxLogin()
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
await navigateAfterLogin()
} catch (error) {
if (error.message !== t('auth.pleaseAgreeToTerms')) {
uni.showToast({ title: error.message || t('auth.loginFailed'), icon: 'none' })
}
}
}
// const onWeChatLogin = async () => {
// try {
// await checkAgreement()
// await wxLogin()
// uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
// await navigateAfterLogin()
// } catch (error) {
// if (error.message !== t('auth.pleaseAgreeToTerms')) {
// uni.showToast({ title: error.message || t('auth.loginFailed'), icon: 'none' })
// }
// }
// }
// 支付宝快捷登录入口(支付宝小程序)
const onAlipayLogin = async () => {
@@ -165,12 +165,12 @@
uni.showToast({ title: t('auth.phoneCancelled'), icon: 'none' })
return
}
console.log(e);
try {
// 先完成微信登录(/app/user/quickLogin,后端建立/查询 WECHAT_MINI 用户)
await wxLogin()
await wxLogin(e.detail.code)
// 再用微信返回的临时 code 换取手机号(后端按文档更新 phone 字段)
await getUserPhoneNumber(e.detail.code)
// await getUserPhoneNumber(e.detail.code)
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
await navigateAfterLogin()
} catch (error) {
+12 -2
View File
@@ -69,7 +69,8 @@
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { sendVerifyCode, loginWithCode } from '@/config/api/user.js'
import { sendVerifyCode, quickLogin } from '@/config/api/user.js'
import { appid } from '@/config/url.js'
import { fetchAndCacheCustomerPhone } from '@/util/index.js'
import { useI18n } from '@/utils/i18n.js'
@@ -163,7 +164,16 @@
try {
uni.showLoading({ title: t('common.loggingIn') })
const res = await loginWithCode(phone.value, verifyCode.value)
const res = await quickLogin({
loginType: 'SMS',
appid,
phonenumber: phone.value,
smsCode: verifyCode.value
})
if (res && res.code !== 200) {
throw new Error(res.msg || res.message || t('auth.loginFailed'))
}
// 保存token和client_id
// 兼容多种返回格式:res.data.token, res.token, res.data.access_token
+8 -1
View File
@@ -186,9 +186,16 @@
// 获取广告图片
const getBannerImages = async () => {
try {
let appPlatform;
// #ifdef MP-WEIXIN
appPlatform = 'wechat'
// #endif
// #ifdef MP-ALIPAY
appPlatform = 'ali'
// #endif
// 调用接口获取广告内容
const res = await getCurrentAdvertisement({
appPlatform: 'wechat', // 微信平台
appPlatform: appPlatform, // 微信平台
appType: 'user', // 用户端
pictureLocation:'userProfile_banner'
})