支付宝兼容

This commit is contained in:
2026-03-09 09:07:58 +08:00
parent 069677957e
commit b3836b8bf2
31 changed files with 2382 additions and 307 deletions
+36 -7
View File
@@ -91,7 +91,7 @@
<!-- 底部操作区 -->
<view class="footer">
<view class="rent-button" :class="{ 'return-button': hasActiveOrder }"
@click="handleRent(isWechatMiniProgram ? 'wx-score-pay' : 'wx-pay')">
@click="handleRent">
<text>{{ hasActiveOrder ? $t('order.returnDevice') : getRentButtonText() }}</text>
</view>
<!-- 微信支付分标识仅在微信小程序环境显示 -->
@@ -176,6 +176,8 @@
const phoneNumber = ref('')
const showPhoneAuthPopup = ref(false)
const isWechatMiniProgram = ref(false)
const isAlipayMiniProgram = ref(false)
const isH5 = ref(false)
// 生命周期 onLoad 钩子
onLoad(async (options) => {
@@ -193,13 +195,27 @@
uni.setNavigationBarTitle({
title: t('device.deviceInfo')
})
// 检测当前运行环境
// 检测当前运行环境:微信小程序 / 支付宝小程序 / H5
// #ifdef MP-WEIXIN
isWechatMiniProgram.value = true
isAlipayMiniProgram.value = false
isH5.value = false
// #endif
// #ifdef MP-ALIPAY
isWechatMiniProgram.value = false
isAlipayMiniProgram.value = true
isH5.value = false
// #endif
// #ifdef H5
isWechatMiniProgram.value = false
isAlipayMiniProgram.value = false
isH5.value = true
// #endif
console.log('当前运行环境:', {
isWechatMiniProgram: isWechatMiniProgram.value,
isAlipayMiniProgram: isAlipayMiniProgram.value,
isH5: isH5.value
})
await checkUserPhone()
await fetchDeviceInfo()
})
@@ -437,7 +453,7 @@
}
// 处理租借操作
const handleRent = (payWay) => {
const handleRent = () => {
if (!isLoggedIn.value) {
showLoginTip()
return
@@ -448,9 +464,22 @@
showPhoneAuthPopup.value = true
return
}
// 提交订单
submitRentOrder(payWay)
// 根据运行环境选择不同的租借/支付流程
// 微信小程序:走微信支付分免押租借
if (isWechatMiniProgram.value) {
submitRentOrder('wx-score-pay')
return
}
// 支付宝小程序:走押金租借,后续在支付页内调起支付宝支付
if (isAlipayMiniProgram.value) {
submitRentOrder('wx-pay')
return
}
// H5 等其他环境:统一走押金租借,支付页内根据平台选择支付方式(Antom 等)
submitRentOrder('wx-pay')
}
// 获取价格单位文本
@@ -603,7 +632,7 @@
// 跳转到订单支付页面
uni.redirectTo({
url: `/pages/order/payment?orderId=${order.orderId}&packagePrice=${packagePrice}&totalAmount=${totalAmount}&depositAmount=${deposit}${deviceInfo.value && deviceInfo.value.feeConfig ? '&feeConfig=' + encodeURIComponent(deviceInfo.value.feeConfig) : ''}`
url: `/subPackages/order/payment?orderId=${order.orderId}&packagePrice=${packagePrice}&totalAmount=${totalAmount}&depositAmount=${deposit}${deviceInfo.value && deviceInfo.value.feeConfig ? '&feeConfig=' + encodeURIComponent(deviceInfo.value.feeConfig) : ''}`
})
} else if (payWay == 'wx-score-pay') {
+192 -13
View File
@@ -1,14 +1,14 @@
<template>
<view class="container fullscreen">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="custom-navbar" :style="navbarStyle">
<view class="navbar-content" :style="{ height: navBarHeight + 'px' }">
<text class="navbar-title">{{ $t('home.title') }}</text>
</view>
</view>
<!-- 顶部信息区域通知招商等 -->
<view class="top-info-section" :style="{ top: (statusBarHeight + navBarHeight) + 'px' }">
<view class="top-info-section" :style="topInfoSectionStyle">
<!-- 通知栏 -->
<view class="notice-wrapper" v-if="noticeText" @click="openNoticePopup">
<uv-notice-bar :text="noticeText" :speed="50" :show-icon="true" color="#07c160" bg-color="#E8F8EF"
@@ -17,14 +17,31 @@
</view>
<!-- 内容区域 -->
<!-- #ifdef MP-ALIPAY -->
<view class="main-content" :style="{ paddingTop: (navBarHeight + noticeHeight) + 'px' }">
<!-- #endif -->
<!-- #ifndef MP-ALIPAY -->
<view class="main-content" :style="{ paddingTop: (statusBarHeight + navBarHeight + noticeHeight) + 'px' }">
<!-- #endif -->
<!-- 全屏地图组件 -->
<!-- 支付宝小程序使用专用组件 -->
<!-- #ifdef MP-ALIPAY -->
<MapComponentAlipay v-if="!isLoading && userLocation" ref="mapRef" :userLocation="userLocation"
:positionList="positionList" :filteredPositions="filteredPositions" :searchKeyword="searchKeyword"
:enableMarkers="true" :bannerImages="bannerImages"
:hideMapOverlays="showGuidePopup || showNoticePopup || showActivityPopup" @relocate="handleRelocate"
@scan="handleScan" @showList="showLocationList" @markerTap="selectPosition"
@mapCenterChange="onMapCenterChange" @bannerClick="handleBannerClick" @guide="openGuidePopup" />
<!-- #endif -->
<!-- 非支付宝小程序使用通用组件 -->
<!-- #ifndef MP-ALIPAY -->
<MapComponent v-if="!isLoading && userLocation" ref="mapRef" :userLocation="userLocation"
:positionList="positionList" :filteredPositions="filteredPositions" :searchKeyword="searchKeyword"
:enableMarkers="true" :bannerImages="bannerImages"
:hideMapOverlays="showGuidePopup || showNoticePopup || showActivityPopup" @relocate="handleRelocate"
@scan="handleScan" @showList="showLocationList" @markerTap="selectPosition"
@mapCenterChange="onMapCenterChange" @bannerClick="handleBannerClick" @guide="openGuidePopup" />
<!-- #endif -->
<!-- 地图加载状态 -->
<view v-if="isLoading || !userLocation" class="map-loading-placeholder">
@@ -79,7 +96,7 @@
</view>
</view> -->
<!-- 手机号授权弹窗 -->
<!-- 手机号校验/授权弹窗扫码前校验 -->
<view class="phone-auth-popup" v-if="showPhoneAuthPopup">
<view class="popup-mask" @click.stop="showPhoneAuthPopup = false"></view>
<view class="popup-content">
@@ -90,9 +107,25 @@
<view class="auth-desc">
<text>{{ $t('auth.authDesc') }}</text>
</view>
<button class="auth-btn" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
<!-- 微信获取手机号 code 并上报后端绑定 -->
<!-- #ifdef MP-WEIXIN -->
<button class="auth-btn" open-type="getPhoneNumber" @getphonenumber="onWxGetPhoneNumber">
<text>{{ $t('auth.getPhoneNumber') }}</text>
</button>
<!-- #endif -->
<!-- 支付宝先授权 phoneNumber再调用 my.getPhoneNumber上报后端解密并校验一致性 -->
<!-- #ifdef MP-ALIPAY -->
<button
class="auth-btn"
open-type="getAuthorize"
scope="phoneNumber"
@getAuthorize="onAliAuthorizePhoneNumber"
@error="onAliAuthorizePhoneNumberError"
>
<text>{{ $t('auth.getPhoneNumber') }}</text>
</button>
<!-- #endif -->
<view class="auth-cancel" @click="showPhoneAuthPopup = false">
<text>{{ $t('auth.notNow') }}</text>
</view>
@@ -187,7 +220,10 @@
} from 'vue'
import {
getQueryString,
wxLogin
wxLogin,
getUserInfo,
getUserPhoneNumber,
getAlipayUserPhoneNumber
} from '../../util/index.js'
import {
URL
@@ -219,6 +255,9 @@
// 同样需要使用相对路径引入组件
// 注意:从 pages/index/ 目录访问 components/ 需要使用 ../../components/ 路径
import MapComponent from '../../components/MapComponent.vue'
// #ifdef MP-ALIPAY
import MapComponentAlipay from '../../components/MapComponentAlipay.vue'
// #endif
import LocationListSheet from '../../components/LocationListSheet.vue'
import {
useI18n
@@ -244,6 +283,8 @@
const isExpanded = ref(false)
const isLoading = ref(false)
const showPhoneAuthPopup = ref(false)
const pendingScan = ref(false) // 是否是扫码流程触发的手机号校验
const expectedUserPhone = ref('') // 当前登录态下绑定的手机号(用于支付宝一致性校验)
const isLocationInitialized = ref(false)
const showLocationPopup = ref(false)
const isRelocating = ref(false) // 防抖标志:是否正在重新定位
@@ -259,6 +300,30 @@
const navBarHeight = ref(44) // 默认导航栏内容高度
const noticeHeight = ref(0) // 通知栏高度
// 导航栏样式:支付宝小程序不设置 paddingTop
const navbarStyle = computed(() => {
// #ifdef MP-ALIPAY
// 支付宝小程序:不设置 paddingTop
return { paddingTop: '0px' }
// #endif
// #ifndef MP-ALIPAY
// 非支付宝小程序:设置 statusBarHeight
return { paddingTop: statusBarHeight.value + 'px' }
// #endif
})
// 顶部信息区域样式:支付宝小程序使用 paddingTop,非支付宝小程序使用 top
const topInfoSectionStyle = computed(() => {
// #ifdef MP-ALIPAY
// 支付宝小程序:使用 paddingTop
return { top: ( navBarHeight.value) + 'px' }
// #endif
// #ifndef MP-ALIPAY
// 非支付宝小程序:使用 top
return { top: (statusBarHeight.value + navBarHeight.value) + 'px' }
// #endif
})
// 使用指南步骤
const guideSteps = ref([{
title: '扫码使用',
@@ -474,11 +539,13 @@
// 距离格式化函数
const formatDistance = (distanceInMeters) => {
if (distanceInMeters < 1000) {
return `${Math.round(distanceInMeters)}m`
} else {
return `${(distanceInMeters / 1000).toFixed(1)}km`
}
// 支付宝小程序等环境下,可能传入 String/BigInt/异常对象,导致 toFixed 不存在
let meters = distanceInMeters
if (typeof meters === 'bigint') meters = Number(meters)
meters = Number(meters)
if (!Number.isFinite(meters) || meters < 0) return ''
if (meters < 1000) return `${Math.round(meters)}m`
return `${(meters / 1000).toFixed(1)}km`
}
@@ -964,7 +1031,58 @@
}
}
const handleScan = async () => {
const normalizePhone = (p) => {
if (!p) return ''
return String(p).replace(/\s+/g, '').replace(/^\+?86/, '')
}
const isLoggedIn = () => {
const token = uni.getStorageSync('token')
return !!token
}
const precheckBeforeScan = async () => {
if (!isLoggedIn()) {
redirectToLogin()
return false
}
try {
const userInfoRes = await getUserInfo()
console.log('userInfoRes', userInfoRes.data.phone);
const phone = userInfoRes.data.phone || ''
expectedUserPhone.value = phone
console.log('expectedUserPhone', expectedUserPhone.value);
// 没有手机号:弹窗引导获取/绑定
if (!phone) {
pendingScan.value = true
showPhoneAuthPopup.value = true
return false
}
// // 支付宝:即使已有手机号,也要求扫码前做一次一致性校验(或命中缓存)
// // #ifdef MP-ALIPAY
// const cached = uni.getStorageSync('alipay_phone_verified')
// const cachedPhone = cached && cached.phone ? cached.phone : ''
// if (cachedPhone && normalizePhone(cachedPhone) === normalizePhone(phone)) {
// return true
// }
// pendingScan.value = true
// showPhoneAuthPopup.value = true
// return false
// // #endif
return true
} catch (e) {
console.error('扫码前检查用户信息失败:', e)
return true // 不阻断,避免影响主流程;后续租借仍会二次校验
}
}
const doScan = async () => {
// #ifdef H5
uni.navigateTo({
url: '/pages/scan/index'
@@ -987,6 +1105,12 @@
}
}
const handleScan = async () => {
const ok = await precheckBeforeScan()
if (!ok) return
await doScan()
}
const processScanResult = async (scanResult) => {
try {
console.log('===== 处理扫码结果 =====');
@@ -1162,12 +1286,67 @@
showLocationPopup.value = false
}
const onGetPhoneNumber = (e) => {
if (e.detail.errMsg === 'getPhoneNumber:ok') {
// 微信:绑定手机号后继续扫码
const onWxGetPhoneNumber = async (e) => {
try {
if (!e || e.detail.errMsg !== 'getPhoneNumber:ok') {
return
}
await getUserPhoneNumber(e.detail.code)
showPhoneAuthPopup.value = false
if (pendingScan.value) {
pendingScan.value = false
await doScan()
}
} catch (err) {
console.error('微信绑定手机号失败:', err)
}
}
// 支付宝:获取手机号(后端解密)并与 userInfo.phone 对比,一致才允许继续扫码
const onAliAuthorizePhoneNumber = async (e) => {
// #ifdef MP-ALIPAY
try {
console.log('[ALIPAY] getAuthorize(phoneNumber) event:', e)
uni.showLoading({ title: t('common.processing') })
const res = await getAlipayUserPhoneNumber()
const aliPhone = res?.data?.phoneNumber || res?.data?.phone || res?.phoneNumber || ''
if (!aliPhone) {
throw new Error('未获取到手机号')
}
const expected = expectedUserPhone.value
if (expected && normalizePhone(aliPhone) !== normalizePhone(expected)) {
uni.showModal({
title: t('common.tips'),
content: '当前支付宝授权手机号与账号绑定手机号不一致,无法扫码租借,请更换账号或联系管理员。',
showCancel: false
})
return
}
// 缓存本次校验结果(避免每次扫码都弹)
uni.setStorageSync('alipay_phone_verified', { phone: aliPhone, ts: Date.now() })
showPhoneAuthPopup.value = false
if (pendingScan.value) {
pendingScan.value = false
await doScan()
}
} catch (err) {
console.error('支付宝手机号校验失败:', err)
} finally {
uni.hideLoading()
}
// #endif
}
const onAliAuthorizePhoneNumberError = (e) => {
// #ifdef MP-ALIPAY
console.error('支付宝手机号授权失败:', e)
// #endif
}
// 使用指南弹窗控制
const openPopup = () => {
uni.navigateTo({
+7 -2
View File
@@ -134,8 +134,13 @@
}
const formatDistance = (meters) => {
if (meters < 1000) return `${Math.round(meters)}m`
return `${(meters / 1000).toFixed(1)}km`
// 兼容支付宝小程序等环境:保证始终对 Number 调用 toFixed
let m = meters
if (typeof m === 'bigint') m = Number(m)
m = Number(m)
if (!Number.isFinite(m) || m < 0) return ''
if (m < 1000) return `${Math.round(m)}m`
return `${(m / 1000).toFixed(1)}km`
}
const setTab = (name) => {