2 Commits

Author SHA1 Message Date
pcwl_yancheng 2a7c6c8e03 fix:阿里适配 2026-01-19 09:16:53 +08:00
pcwl_yancheng 76bdcd1aba fix:对接高德地图,修复地图bug 2025-12-23 10:39:41 +08:00
28 changed files with 1226 additions and 459 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
<script>
import {
wxLogin,
alipayLogin,
getUserInfo
} from './util/index'
@@ -49,7 +49,7 @@
// 保留方法但不调用
async autoLogin() {
try {
const loginResult = await wxLogin()
const loginResult = await alipayLogin()
// await getUserInfo()
} catch (error) {
console.error('自动登录失败:', error)
+63 -59
View File
@@ -2,10 +2,20 @@
<view class="map-container" :class="{ 'full-width': props.fullWidth }" :style="{ '--map-height': props.customHeight || '78vh' }">
<!-- 地图容器 -->
<view class="map-wrapper">
<!-- 使用小程序原生地图组件 -->
<map id="map" class="native-map" :longitude="mapCenter.longitude" :latitude="mapCenter.latitude"
:markers="mapMarkers" :scale="mapZoom" :show-location="false" @regionchange="onMapRegionChange"
@markertap="onMapMarkerTap" @callouttap="onCalloutTap" @updated="onMapUpdated" @error="onMapError">
<!-- 支付宝小程序地图组件使用高德地图 -->
<map id="map" class="native-map"
:longitude="mapCenter.longitude"
:latitude="mapCenter.latitude"
:markers="mapMarkers"
:scale="mapZoom"
:show-location="false"
@regionchange="onMapRegionChange"
@markertap="onMapMarkerTap"
@tap="onMapTap"
@updated="onMapUpdated"
@error="onMapError">
</map>
<!-- 覆盖在地图上的广告轮播使用 cover-view 以兼容小程序原生组件层级 -->
<cover-view class="index-swiper" v-if="!props.hideControls && !props.hideMapOverlays && currentBannerImage">
<cover-image :src="currentBannerImage" class="index-swiper-img" mode="aspectFill" @tap="handleBannerTap"></cover-image>
@@ -25,6 +35,7 @@
<cover-image src="/static/location-icon.png" class="center-marker-icon"></cover-image>
</cover-view>
<!-- 地图侧边控制按钮重定位客服中心查看附近设备 -->
<cover-view class="map-side-controls" v-if="!props.hideControls && !props.hideMapOverlays">
<cover-view class="side-btn locate" @tap="handleRelocate">
<cover-image class="side-icon" src="/static/location.png"></cover-image>
@@ -35,9 +46,7 @@
<cover-view class="side-btn search" @tap="handleSearch">
<cover-image class="side-icon" src="/static/other_device.png"></cover-image>
</cover-view>
</cover-view>
</map>
<!-- 地图加载状态 -->
<view class="map-loading" v-if="isLoading">
@@ -245,6 +254,35 @@
deep: true
})
// 启动广告轮播
const startBannerRotation = () => {
// 如果只有一张或没有图片,不需要轮播
if (!props.bannerImages || props.bannerImages.length <= 1) {
console.log('图片数量不足,不启动轮播')
return
}
// 清除旧的定时器
stopBannerRotation()
console.log('开始广告轮播定时器')
// 每3秒切换一次
bannerTimer = setInterval(() => {
const nextIndex = (currentBannerIndex.value + 1) % props.bannerImages.length
console.log('轮播切换:', currentBannerIndex.value, '->', nextIndex)
currentBannerIndex.value = nextIndex
}, 3000)
}
// 停止广告轮播
const stopBannerRotation = () => {
if (bannerTimer) {
console.log('停止广告轮播')
clearInterval(bannerTimer)
bannerTimer = null
}
}
// 监听广告图片变化,启动或停止轮播
watch(() => props.bannerImages, (newImages, oldImages) => {
console.log('广告图片变化:', newImages?.length, '张')
@@ -270,25 +308,22 @@
isLoading.value = false
}
// 地图区域变化事件(带防抖优化)
// 地图区域变化事件(支付宝小程序,带防抖优化)
const onMapRegionChange = (e) => {
// 只处理结束事件
if (!e || e.type !== 'end') {
if (!e) {
return
}
const causedBy = e.causedBy || e.detail?.causedBy
// 获取触发原因和中心位置
const causedBy = e.detail?.causedBy || e.causedBy
const centerLocation = e.detail?.centerLocation || e.centerLocation || e.detail?.location
if (causedBy === 'gesture' || causedBy === 'scale' || causedBy === 'drag'||causedBy==='update') {
if (causedBy === 'gesture' || causedBy === 'scale' || causedBy === 'drag' || causedBy === 'update') {
// 清除之前的定时器
if (regionChangeTimer) {
clearTimeout(regionChangeTimer)
}
// 直接从事件对象中获取最新的中心点位置
const centerLocation = e.detail?.centerLocation || e.centerLocation
if (centerLocation && centerLocation.longitude && centerLocation.latitude) {
// 防抖:500ms后执行查询
regionChangeTimer = setTimeout(() => {
@@ -298,13 +333,11 @@
}
mapCenter.value = newCenter;
// 触发父组件查询新位置的场地
emit('mapCenterChange', newCenter)
}, 500)
} else {
// 兜底方案:如果事件中没有centerLocation,才使用API获取
// 兜底方案:使用API获取地图中心
regionChangeTimer = setTimeout(() => {
if (mapContext.value) {
mapContext.value.getCenterLocation({
@@ -328,12 +361,17 @@
}
}
// 标记点点击事件
// 标记点点击事件(支付宝小程序)
const onMapMarkerTap = (e) => {
const markerId = e.detail?.markerId || e.markerId
if (!e) {
return
}
// 获取markerId
const markerId = e.detail?.markerId || e.markerId || e.detail?.marker?.id
// 查找对应的场地位置信息
if (props.filteredPositions && props.filteredPositions.length > 0) {
if (props.filteredPositions && props.filteredPositions.length > 0 && markerId) {
const position = props.filteredPositions[markerId - 1]
if (position) {
emit('markerTap', position)
@@ -341,14 +379,9 @@
}
}
// 标记点气泡点击事件
const onCalloutTap = (e) => {
const markerId = e.markerId
const marker = mapMarkers.value.find(item => item.id === markerId)
if (marker && marker.position) {
emit('markerTap', marker.position)
}
// 地图点击事件(支付宝小程序)
const onMapTap = (e) => {
console.log('地图点击事件:', e)
}
// 地图错误事件
@@ -391,35 +424,6 @@ const handleSearch = () => {
handleJoinTap()
}
// 启动广告轮播
const startBannerRotation = () => {
// 如果只有一张或没有图片,不需要轮播
if (!props.bannerImages || props.bannerImages.length <= 1) {
console.log('图片数量不足,不启动轮播')
return
}
// 清除旧的定时器
stopBannerRotation()
console.log('开始广告轮播定时器')
// 每3秒切换一次
bannerTimer = setInterval(() => {
const nextIndex = (currentBannerIndex.value + 1) % props.bannerImages.length
console.log('轮播切换:', currentBannerIndex.value, '->', nextIndex)
currentBannerIndex.value = nextIndex
}, 3000)
}
// 停止广告轮播
const stopBannerRotation = () => {
if (bannerTimer) {
console.log('停止广告轮播')
clearInterval(bannerTimer)
bannerTimer = null
}
}
const handleScan = () => {
emit('scan')
}
@@ -595,7 +599,7 @@ const handleSearch = () => {
// min-width: 160rpx;
margin: auto;
// height: 72rpx;
background: rgba(255, 255, 255, 0.96);
// background: rgba(255, 255, 255, 0.96);
border-radius: 36rpx;
display: inline-flex;
flex-direction: row;
+10 -10
View File
@@ -8,22 +8,22 @@
</view>
<view class="header-right">
<!-- 支付方式标识移到头部右侧 -->
<view class="payment-badge wx-score" v-if="order.payWay == 'wx_score_pay'">
<image src="/static/images/wxpayflag.png" mode="aspectFit" class="badge-icon"></image>
<view class="payment-badge alipay-score" v-if="order.payWay == 'alipay_score_pay'">
<image src="/static/images/alipay.svg" mode="aspectFit" class="badge-icon"></image>
<view class="badge-text">
<text>{{ $t('order.wxPayScore') }}</text>
<text>{{ $t('order.alipayScore') }}</text>
<text class="divider">|</text>
<text class="highlight">{{ $t('order.depositFree') }}</text>
</view>
</view>
<view class="payment-badge whitelist" v-else-if="order.payWay == 'wx_global_pay'">
<view class="payment-badge whitelist" v-else-if="order.payWay == 'alipay_global_pay'">
<text class="badge-text">{{ $t('order.whitelistOrder') }}</text>
</view>
<view class="payment-badge member" v-else-if="order.payWay == 'wx_member_pay'">
<view class="payment-badge member" v-else-if="order.payWay == 'alipay_member_pay'">
<text class="badge-text">{{ $t('order.memberOrder') }}</text>
</view>
<view class="payment-badge deposit" v-else>
<text class="badge-text">{{ $t('order.wxPay') }}</text>
<text class="badge-text">{{ $t('order.alipayPay') }}</text>
<text class="divider">|</text>
<text class="badge-text">{{ $t('order.depositPay') }}</text>
</view>
@@ -107,7 +107,7 @@
const emit = defineEmits(['pay', 'cancel', 'return-device', 'details']);
const rawStatus = computed(() => props.order.orderStatus ?? props.order.status);
const rawStatus = computed(() => props.order.orderStatus != null ? props.order.orderStatus : props.order.status);
const normalizedStatus = computed(() => {
const s = rawStatus.value;
switch (s) {
@@ -272,8 +272,8 @@
border-radius: 8rpx;
white-space: nowrap;
&.wx-score {
background: rgba(7, 193, 96, 0.08);
&.alipay-score {
background: rgba(0, 122, 255, 0.08);
.badge-icon {
width: 32rpx;
@@ -283,7 +283,7 @@
.badge-text {
font-size: 22rpx;
color: #07c160;
color: #007AFF;
display: flex;
align-items: center;
+6 -6
View File
@@ -76,21 +76,21 @@ export const getOrderByOrderNo = (orderNo) => {
})
}
// 通过订单号获取支付分订单信息
// 通过订单号创建支付宝支付订单(芝麻信用免押)
export const getOrderByOrderNoScore = (orderNo) => {
console.log('通过订单号获取支付分订单信息', orderNo);
console.log('通过订单号创建支付宝支付订单(芝麻信用免押)', orderNo);
return request({
url: `/app/wx-payment/score/create/${orderNo}`,
url: `/app/ali-payment/create/${orderNo}`,
method: 'get',
hideLoading: true
})
}
// 通过订单号获取支付订单状态
// 通过订单号查询支付订单支付状态
export const getOrderByOrderNoScorePayStatus = (orderNo) => {
console.log('通过订单号获取支付订单状态', orderNo);
console.log('通过订单号查询支付订单支付状态', orderNo);
return request({
url: `/app/wx-payment/score/status/${orderNo}`,
url: `/app/ali-payment/status/${orderNo}`,
method: 'get',
hideLoading: true
})
+1
View File
@@ -32,6 +32,7 @@ const request = (option) => {
"Content-Type": option.headers && option.headers["Content-Type"] ? option.headers["Content-Type"] : (option.method && option.method.toUpperCase() === 'POST' ? 'application/json' : 'application/x-www-form-urlencoded'),
...option.headers,
'appid': appid,
'platform': 'alipay', // 标识支付宝小程序平台
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
},
+4 -4
View File
@@ -1,8 +1,8 @@
// export const URL = "https://my.gxfs123.com/api" //正式服务器-弃用
// export const URL = "https://manager.fdzpower.com/api" //正式服务器
export const URL = "https://fansdev.gxfs123.com/api" //测试服务器
export const URL = "https://manager.fdzpower.com/api" //正式服务器
// export const URL = "https://fansdev.gxfs123.com/api" //测试服务器
// export const URL = "http://192.168.5.30:8080" //本地调试
// export const URL = "http://127.0.0.1:8080" //本地调试
export const appid = "wx2165f0be356ae7a9" //微信小程序appid
export const ZFBappid = "2021006117693332" //支付宝小程序appid
export const appid = "2021006117693332" //支付宝小程序appid
export const ZFBappid = "2021006117693332" //支付宝小程序appid(保留兼容)
+207
View File
@@ -0,0 +1,207 @@
# 支付宝支付接口文档
## 接口概述
本文档描述支付宝支付相关的API接口,包括创建支付订单和查询支付状态。
---
## 1. 创建支付宝支付订单
### 接口描述
创建支付宝支付订单,用于扫码预下单并返回二维码。
### 请求信息
#### 请求URL
```
GET /app/ali-payment/create/{orderNo}
```
#### 请求方式
`GET`
#### 请求参数
| 参数名 | 参数类型 | 是否必填 | 参数位置 | 参数说明 |
|--------|----------|----------|----------|----------|
| orderNo | String | 是 | Path | 订单号 |
#### 请求示例
```http
GET /app/ali-payment/create/ORD20231223001
```
### 响应信息
#### 响应参数
| 参数名 | 参数类型 | 参数说明 |
|--------|----------|----------|
| code | Integer | 响应状态码,200表示成功 |
| msg | String | 响应消息 |
| data | Object | 返回数据,包含支付宝支付相关信息 |
#### 响应示例
成功响应:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"qrCode": "https://qr.alipay.com/xxx",
"outTradeNo": "ORD20231223001",
"totalAmount": "99.00"
}
}
```
失败响应:
```json
{
"code": 500,
"msg": "订单不存在或已支付"
}
```
### 错误码说明
| 错误码 | 说明 |
|--------|------|
| 200 | 创建成功 |
| 400 | 参数错误,订单号不能为空 |
| 500 | 系统错误或订单状态异常 |
---
## 2. 查询订单支付状态
### 接口描述
查询指定订单的支付状态。
### 请求信息
#### 请求URL
```
GET /app/ali-payment/status/{orderNo}
```
#### 请求方式
`GET`
#### 请求参数
| 参数名 | 参数类型 | 是否必填 | 参数位置 | 参数说明 |
|--------|----------|----------|----------|----------|
| orderNo | String | 是 | Path | 订单号 |
#### 请求示例
```http
GET /app/ali-payment/status/ORD20231223001
```
### 响应信息
#### 响应参数
| 参数名 | 参数类型 | 参数说明 |
|--------|----------|----------|
| code | Integer | 响应状态码,200表示成功 |
| msg | String | 响应消息 |
| data | Object | 订单支付状态信息 |
| data.tradeStatus | String | 交易状态(WAIT_BUYER_PAY-等待支付,TRADE_SUCCESS-支付成功,TRADE_CLOSED-交易关闭) |
| data.tradeNo | String | 支付宝交易号 |
| data.totalAmount | String | 订单金额 |
| data.buyerPayAmount | String | 买家实付金额 |
#### 响应示例
支付成功响应:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"tradeStatus": "TRADE_SUCCESS",
"tradeNo": "2023122322001234567890",
"outTradeNo": "ORD20231223001",
"totalAmount": "99.00",
"buyerPayAmount": "99.00",
"buyerLogonId": "158****5620"
}
}
```
等待支付响应:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"tradeStatus": "WAIT_BUYER_PAY",
"outTradeNo": "ORD20231223001",
"totalAmount": "99.00"
}
}
```
失败响应:
```json
{
"code": 500,
"msg": "订单不存在"
}
```
### 支付状态说明
| 状态码 | 说明 |
|--------|------|
| WAIT_BUYER_PAY | 交易创建,等待买家付款 |
| TRADE_CLOSED | 未付款交易超时关闭,或支付完成后全额退款 |
| TRADE_SUCCESS | 交易支付成功 |
| TRADE_FINISHED | 交易结束,不可退款 |
---
## 公共说明
### 基础路径
```
http://your-domain.com/app/ali-payment
```
### 请求头
```
Content-Type: application/json
```
### 注意事项
1. **订单号格式**:订单号必须唯一,建议使用系统生成的订单编号
2. **幂等性**:同一订单号多次调用创建接口,返回相同的支付信息
3. **超时处理**:支付订单创建后,建议在15分钟内完成支付
4. **状态查询**:建议在支付完成后通过回调通知处理业务逻辑,状态查询接口用于补充查询
5. **安全性**:生产环境建议添加签名验证和请求频率限制
### 业务流程
```
1. 用户发起租借 → 系统创建订单
2. 调用创建支付接口 → 返回支付二维码
3. 用户扫码支付 → 支付宝处理支付
4. 支付宝回调通知 → 系统更新订单状态
5. 前端轮询状态接口 → 确认支付结果
6. 支付成功 → 触发开锁逻辑
```
---
## 联系方式
如有问题,请联系技术支持团队。
**文档版本**v1.0
**最后更新**2025-12-23
**维护人员**:开发团队
+5 -6
View File
@@ -97,7 +97,7 @@ export default {
step1Title: 'Scan QR Code',
step1Desc: 'Find a device and scan its QR code',
step2Title: 'No Deposit',
step2Desc: 'Rent with WeChat Pay Score, no deposit needed',
step2Desc: 'Rent with Sesame Credit, no deposit needed',
step3Title: 'Start Using',
step3Desc: 'Device unlocks, take out the fan',
step4Title: 'Return',
@@ -134,7 +134,7 @@ export default {
autoChargeOvertime: 'Overtime will be charged automatically by hour',
useInDesignatedArea: 'Please use the device in designated area',
rentDepositFree: 'Rent Deposit-free',
wxPayScoreDesc: 'WeChat Pay Score | 550+ points enjoy',
alipayScoreDesc: 'Sesame Credit | 550+ points enjoy',
checking: 'Checking',
deviceNoNotRecognized: 'Device number not recognized',
processFailed: 'Process failed, please try again later',
@@ -144,7 +144,7 @@ export default {
rentSuccess: 'Rent successful',
rentFailedRetry: 'Rent failed, please retry',
getPayParamsFailed: 'Failed to get payment parameters',
payScoreFailedCancelled: 'Pay score call failed, order cancelled'
payScoreFailedCancelled: 'Credit payment failed, order cancelled'
},
order: {
@@ -190,11 +190,11 @@ export default {
returnFailed: 'Return failed',
confirmCancel: 'Confirm to cancel order?',
confirmReturn: 'Confirm to return device?',
wxPayScore: 'WeChat Pay Score',
alipayScore: 'Sesame Credit',
alipayPay: 'Alipay',
depositFree: 'Deposit-free',
whitelistOrder: 'Whitelist Order',
memberOrder: 'Member Order',
wxPay: 'WeChat Pay',
depositPay: 'Deposit Pay',
paymentInProgress: 'Payment in Progress',
paymentFailedRetry: 'Payment failed, please try again',
@@ -318,7 +318,6 @@ export default {
payment: {
paymentAmount: 'Amount',
paymentMethod: 'Method',
wechatPay: 'WeChat',
alipay: 'Alipay',
balance: 'Balance',
payNow: 'Pay',
+9 -8
View File
@@ -60,7 +60,7 @@ export default {
orders: '订单',
settings: '设置',
back: '返回',
title: '风电者共享风扇&暖手充电宝'
title: '风电者共享风扇&暖手宝'
},
app: {
@@ -71,7 +71,7 @@ export default {
},
home: {
title: '风电者共享风扇&暖手充电宝',
title: '风电者共享风扇&暖手宝',
nearbyDevices: '附近设备',
scanToUse: '扫码使用',
personalCenter: '个人中心',
@@ -97,7 +97,7 @@ export default {
step1Title: '扫码使用',
step1Desc: '找到附近设备,扫描设备上的二维码',
step2Title: '免押金支付',
step2Desc: '无需支付押金,使用支付分免押即可完成租借',
step2Desc: '无需支付押金,使用芝麻信用免押即可完成租借',
step3Title: '开始使用',
step3Desc: '设备自动解锁,风扇弹出后取出即可开始使用',
step4Title: '归还设备',
@@ -134,7 +134,7 @@ export default {
autoChargeOvertime: '超出使用时间将自动按小时计费',
useInDesignatedArea: '请在指定区域内使用设备',
rentDepositFree: '免押金租借',
wxPayScoreDesc: '微信支付分 | 550分以上优享',
alipayScoreDesc: '芝麻信用免押 | 550分以上优享',
checking: '检查中',
deviceNoNotRecognized: '未识别到设备编号',
processFailed: '处理失败,请稍后重试',
@@ -144,7 +144,7 @@ export default {
rentSuccess: '租借成功',
rentFailedRetry: '租借失败,请重试',
getPayParamsFailed: '获取支付参数失败',
payScoreFailedCancelled: '支付调用失败,订单已取消'
payScoreFailedCancelled: '信用支付调用失败,订单已取消'
},
order: {
@@ -190,11 +190,11 @@ export default {
returnFailed: '归还失败',
confirmCancel: '确认取消订单?',
confirmReturn: '确认归还设备?',
wxPayScore: '微信支付分',
alipayScore: '芝麻信用免押',
depositFree: '免押租借',
whitelistOrder: '白名单订单',
memberOrder: '会员订单',
wxPay: '微信支付',
alipayPay: '支付宝支付',
depositPay: '押金租借',
paymentInProgress: '支付中',
paymentFailedRetry: '支付失败,请重新支付',
@@ -291,6 +291,7 @@ export default {
getUserInfoSuccess: '获取用户信息成功',
getUserInfoFailed: '获取用户信息失败',
pleaseUseInWechat: '请在微信小程序中使用此功能',
pleaseUseInAlipay: '请在支付宝小程序中使用此功能',
agreeToTerms: '我已阅读并同意',
pleaseAgreeToTerms: '请先阅读并同意《用户协议》和《隐私政策》',
loginSuccess: '登录成功',
@@ -318,7 +319,7 @@ export default {
payment: {
paymentAmount: '支付金额',
paymentMethod: '支付方式',
wechatPay: '微信支付',
alipayPay: '支付宝支付',
alipay: '支付宝',
balance: '余额支付',
payNow: '立即支付',
+9 -19
View File
@@ -43,36 +43,26 @@
"ios" : {},
"sdkConfigs" : {
"maps" : {
"amap" : {
"appkey_ios" : "4c513a688938fd89b88b296e867f66ec",
"appkey_android" : "4c513a688938fd89b88b296e867f66ec"
"qqmap" : {
"appkey_ios" : "RO5BZ-ECZ63-7US3C-RT5QW-TIDZE-2FF35",
"appkey_android" : "RO5BZ-ECZ63-7US3C-RT5QW-TIDZE-2FF35"
}
}
}
}
},
"quickapp" : {},
"mp-weixin" : {
"appid" : "wx2165f0be356ae7a9",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true,
"permission" : {
"scope.getPhoneNumber" : {
"desc" : "您的手机号将用于登录和订单服务"
},
"scope.userLocation" : {
"desc" : "您的位置信息将用于获取附近的设备"
}
},
"requiredPrivateInfos" : [ "getLocation", "chooseLocation" ]
},
"mp-alipay" : {
"component2": true,
"usingComponents" : true,
"appid" : "2021006117693332",
"unipush" : {
"enable" : false
},
"permission" : {
"scope.userLocation" : {
"desc" : "您的位置信息将用于获取附近的设备"
}
}
},
"mp-baidu" : {
+20 -45
View File
@@ -72,12 +72,12 @@
<!-- 底部操作区 -->
<view class="footer">
<button class="rent-button" :class="{ 'return-button': hasActiveOrder }"
@click="handleRent('wx-score-pay')">
@click="handleRent('alipay-score-pay')">
<text>{{ hasActiveOrder ? $t('order.returnDevice') : $t('device.rentDepositFree') }}</text>
</button>
<view class="wechat-credit">
<image src="/static/images/wxpayflag.png" mode="aspectFit" class="wx-icon"></image>
<text class="credit-text">{{ $t('device.wxPayScoreDesc') }}</text>
<view class="alipay-credit">
<image src="/static/images/alipay.svg" mode="aspectFit" class="alipay-icon"></image>
<text class="credit-text">{{ $t('device.alipayScoreDesc') }}</text>
</view>
</view>
@@ -123,7 +123,7 @@
cancelOrder
} from '@/config/api/order.js'
import {
initiateWeChatScorePayment,
initiateAlipayPayment,
getUserInfo,
getUserPhoneNumber
} from '@/util/index.js'
@@ -466,32 +466,8 @@
uni.showLoading({
title: $t('common.processing')
})
// --- 第一步:先请求订阅消息(必须在用户点击的同步上下文中)---
if (payWay === 'wx-score-pay') {
console.log('准备请求订阅消息(在异步操作之前),时间:', new Date().toLocaleTimeString());
try {
await new Promise((resolve, reject) => {
uni.requestSubscribeMessage({
tmplIds: ['o7OMTIcHnFBR7mvsggxFtdt8FfIgSl-v0swVUefGx6w'],
success: (subscribeRes) => {
console.log('订阅消息success回调,时间:', new Date()
.toLocaleTimeString(), subscribeRes);
resolve(subscribeRes);
},
fail: (subscribeErr) => {
console.log('订阅消息fail回调,时间:', new Date().toLocaleTimeString(),
subscribeErr);
// 订阅失败不影响主流程
resolve(subscribeErr);
}
});
});
console.log('订阅消息完成,时间:', new Date().toLocaleTimeString());
} catch (subscribeError) {
console.log('订阅消息异常', subscribeError);
}
}
// --- 订阅消息请求完成 ---
// --- 支付宝小程序不需要订阅消息,移除相关代码 ---
// 支付宝小程序使用消息推送,不需要订阅消息
console.log(deviceId.value);
// 调用设备租借接口
@@ -504,7 +480,7 @@
const order = rentResult.data
console.log('订单信息', order);
if (payWay == 'wx-pay') {
if (payWay == 'alipay-pay') {
// 当支付方式为押金支付时
uni.hideLoading()
const res = await getOrderByOrderNo(order.orderNo);
@@ -519,29 +495,28 @@
url: `/pages/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') {
// 当支付方式为支付支付时
} else if (payWay == 'alipay-score-pay') {
// 当支付方式为支付宝信用免押支付时
uni.hideLoading()
// 获取支付所需参数
// 获取支付宝信用免押所需参数
const res = await getOrderByOrderNoScore(order.orderNo);
uni.hideLoading()
if (res && res.code === 200) {
try {
// 调用微信支付分小程序
const payResult = await initiateWeChatScorePayment(res);
console.log('支付调用结果', payResult);
// 调用支付宝信用免押小程序
const payResult = await initiateAlipayPayment(res);
console.log('支付宝信用免押调用结果', payResult);
// 成功则跳转到等待页面
if (payResult.errCode == '0' && payResult.extraData && Object.keys(payResult.extraData)
.length > 0) {
console.log('支付分授权成功,准备跳转到等待页,时间:', new Date().toLocaleTimeString());
// 跳转到等待页面(订阅消息已经在前面完成了)
if (payResult && payResult.success !== false) {
console.log('支付宝信用免押授权成功,准备跳转到等待页,时间:', new Date().toLocaleTimeString());
// 跳转到等待页面
uni.redirectTo({
url: `/pages/waiting/index?orderNo=${order.orderNo}&orderId=${order.orderId}&deviceId=${deviceId.value}`
});
return;
} else {
console.log('支付未完成授权或用户取消extraData:', payResult.extraData);
console.log('支付宝信用免押未完成授权或用户取消:', payResult);
// 用户取消授权,需要取消订单
try {
uni.showLoading({
@@ -876,13 +851,13 @@
}
}
.wechat-credit {
.alipay-credit {
display: flex;
align-items: center;
justify-content: center;
margin-top: 16rpx;
.wx-icon {
.alipay-icon {
width: 48rpx;
height: 38rpx;
margin-right: 8rpx;
+1 -1
View File
@@ -306,7 +306,7 @@
// 获取图片列表(支持字符串或数组)
const getImageList = (item) => {
if (!item) return [];
const pictureSource = item.pictureUrls ?? item.picturePath;
const pictureSource = item.pictureUrls != null ? item.pictureUrls : item.picturePath;
if (!pictureSource) return [];
if (Array.isArray(pictureSource)) {
return pictureSource.filter(img => !!img);
+30 -25
View File
@@ -1,14 +1,14 @@
<template>
<view class="container fullscreen">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content" :style="{ height: navBarHeight + 'px' }">
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'rpx' }">
<view class="navbar-content" :style="{ height: navBarHeight + 'rpx' }">
<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="{ top: (statusBarHeight + navBarHeight) + 'rpx' }">
<!-- 通知栏 -->
<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,13 +17,14 @@
</view>
<!-- 内容区域 -->
<view class="main-content" :style="{ paddingTop: (statusBarHeight + navBarHeight + noticeHeight) + 'px' }">
<view class="main-content"
:style="{ paddingTop: (statusBarHeight + navBarHeight+navBarHeight + noticeHeight) + 'rpx' }">
<!-- 全屏地图组件 -->
<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"
:hideMapOverlays="showGuidePopup || showNoticePopup || showActivityPopup" @relocate="handleRelocate"
@scan="handleScan" @showList="showLocationList" @markerTap="selectPosition"
@mapCenterChange="onMapCenterChange" @bannerClick="handleBannerClick" />
<!-- 地图加载状态 -->
@@ -187,7 +188,7 @@
} from 'vue'
import {
getQueryString,
wxLogin
alipayLogin
} from '../../util/index.js'
import {
URL
@@ -218,12 +219,9 @@
useI18n
} from '../../utils/i18n.js'
// 开启右上角分享菜单(仅 mp-weixin 有效)
// #ifdef MP-WEIXIN
wx.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline']
})
// 支付宝小程序不支持分享菜单,移除相关代码
// #ifdef MP-ALIPAY
// 支付宝小程序分享功能通过页面配置实现
// #endif
const {
@@ -260,7 +258,7 @@
},
{
title: '免押金支付',
desc: '无需支付押金,使用支付分免押即可完成租借'
desc: '无需支付押金,使用芝麻信用免押即可完成租借'
},
{
title: '开始使用',
@@ -317,7 +315,7 @@
'Content-Language': languageCode
},
data: {
type: 'wx_user_type' // 微信小程序用户端
type: 'zfb_user_type' // 支付宝小程序用户端
}
})
@@ -365,7 +363,7 @@
'Content-Language': languageCode
},
data: {
appPlatform: 'wechat', // 微信平台
appPlatform: 'alipay', // 支付宝平台
appType: 'user' // 用户端
}
})
@@ -394,7 +392,9 @@
console.log('点击首页广告:', index, bannerImages.value[index])
// 可以根据需要添加跳转逻辑
// 例如跳转到合作加盟页面
uni.navigateTo({ url: '/pages/join/index' })
uni.navigateTo({
url: '/pages/join/index'
})
}
// 查询最近的活动
@@ -480,15 +480,13 @@
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight || 0
// #ifdef MP-WEIXIN
// 获取胶囊按钮位置信息
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
// 计算导航栏内容高度:(胶囊底部坐标 - 状态栏高度) 确保与胶囊对齐
navBarHeight.value = (menuButtonInfo.top - systemInfo.statusBarHeight) * 2 + menuButtonInfo.height
// #ifdef MP-ALIPAY
// 支付宝小程序导航栏高度计算
navBarHeight.value = 44
// #endif
// #ifndef MP-WEIXIN
// 非微信小程序使用默认高度
// #ifndef MP-ALIPAY
// 其他平台使用默认高度
navBarHeight.value = 44
// #endif
@@ -890,7 +888,11 @@
})
})
let deviceNo = getQueryString(scanResult.path, 'deviceNo')
// 兼容微信和支付宝小程序的扫码结果结构
// 微信小程序:scanResult.path
// 支付宝小程序:scanResult.result
const scanPath = scanResult.path || scanResult.result || ''
let deviceNo = getQueryString(scanPath, 'deviceNo')
if (!deviceNo) {
uni.showToast({
@@ -1079,10 +1081,13 @@
</script>
<style lang="scss" scoped>
/* 支付宝小程序页面背景色通过外层 view 设置 */
.container {
height: 100vh;
width: 100vw;
background-color: #fff;
min-height: 100vh;
/* 支付宝小程序需要设置 min-height */
display: flex;
flex-direction: column;
}
+5 -5
View File
@@ -11,7 +11,7 @@
<view class="p">Before using {{ brandName }}, please carefully read and fully understand all contents of this Agreement, especially the terms highlighted in bold (including but not limited to liability limitations, dispute resolution, applicable law, protection of minors, etc.). By clicking "Login/Use" or actually using the service, you are deemed to have read and agreed to be bound by this Agreement.</view>
<view class="h1">II. Account and Login</view>
<view class="p">2.1 You can log in and use this service through WeChat authorization. To complete deposit-free rental and order settlement, you agree that we conduct credit assessment and post-order settlement based on WeChat Payment Score.</view>
<view class="p">2.1 You can log in and use this service through Alipay authorization. To complete deposit-free rental and order settlement, you agree that we conduct credit assessment and post-order settlement based on Sesame Credit.</view>
<view class="p">2.2 You should ensure that the information provided is true, accurate, and complete, and update it in a timely manner. Any service restrictions, order abnormalities, or losses caused by untrue information or failure to update in time shall be borne by you.</view>
<view class="p">2.3 You shall be responsible for all activities under the account, properly keep the device and account credentials, and shall not lend, rent, or otherwise provide them to others.</view>
@@ -20,10 +20,10 @@
<view class="p">3.2 Usage specifications: Please use the device properly, avoid water ingress, dropping, unauthorized disassembly or modification; do not approach open flames and high-temperature environments; avoid outdoor use in rainy days; children should use under supervision.</view>
<view class="p">3.3 Prohibited behaviors: Using the device for illegal or improper purposes; affecting the normal operation of the device or system in any way; circumventing billing or return processes through abnormal means.</view>
<view class="h1">IV. Billing and Settlement (Including WeChat Payment Score)</view>
<view class="h1">IV. Billing and Settlement (Including Sesame Credit)</view>
<view class="p"><text class="bold">4.1 Billing rules</text>: Subject to the real-time billing rules displayed in the mini program, which may include duration billing, capped prices, service fees, etc. Charges will be based on this after order generation.</view>
<view class="p"><text class="bold">4.2 WeChat Payment Score deposit-free</text>: If you activate and pass the credit assessment, you can enjoy deposit-free rental; if the assessment fails, pre-authorization or deposit may be required. Please refer to the page prompts for details.</view>
<view class="p">4.3 Settlement and deduction: After the order ends, we will complete the settlement based on actual usage and platform rules, and deduct through WeChat Payment Score/WeChat Pay.</view>
<view class="p"><text class="bold">4.2 Sesame Credit deposit-free</text>: If you activate and pass the credit assessment, you can enjoy deposit-free rental; if the assessment fails, pre-authorization or deposit may be required. Please refer to the page prompts for details.</view>
<view class="p">4.3 Settlement and deduction: After the order ends, we will complete the settlement based on actual usage and platform rules, and deduct through Sesame Credit/Alipay.</view>
<view class="p">4.4 Exceptions and disputes: If you have any objection to billing or settlement, please submit it through "My-Customer Service" within 48 hours after order completion; overdue submissions may affect processing results.</view>
<view class="h1">V. Device Return and Overdue Handling</view>
@@ -49,7 +49,7 @@
<view class="p">9.2 You should be responsible for your own use. Any losses caused by your violation of this Agreement or improper storage and use of equipment shall be borne by you or compensated to relevant parties.</view>
<view class="h1">X. Privacy and Personal Information Protection</view>
<view class="p">10.1 We strictly handle your personal information in accordance with the Privacy Policy, including WeChat login information, mobile phone number (obtained after your authorization), device and order information, location and outlet information, etc.</view>
<view class="p">10.1 We strictly handle your personal information in accordance with the Privacy Policy, including Alipay login information, mobile phone number (obtained after your authorization), device and order information, location and outlet information, etc.</view>
<view class="p">10.2 For details, please refer to the Privacy Policy in this mini program.</view>
<view class="h1">XI. Service Changes and Termination</view>
+4 -4
View File
@@ -11,7 +11,7 @@
<view class="p">在使用{{ brandName }}请您务必仔细阅读并充分理解本协议全部内容尤其是以加粗方式提示的条款包括但不限于责任限制争议解决适用法律未成年人保护等您点击"登录/使用"或实际使用服务即视为您已阅读并同意受本协议约束</view>
<view class="h1">账号与登录</view>
<view class="p">2.1 您可通过微信授权登录使用本服务为完成免押租借与订单结算您同意我们基于微信支付分进行信用评估及订单后结等必要处理</view>
<view class="p">2.1 您可通过支付宝授权登录使用本服务为完成免押租借与订单结算您同意我们基于芝麻信用进行信用评估及订单后结等必要处理</view>
<view class="p">2.2 您应保证提供信息真实准确完整并及时更新因您提供的信息不真实或未及时更新导致的服务受限订单异常或损失由您自行承担</view>
<view class="p">2.3 您应对账户下的全部行为负责妥善保管设备与账户凭证不得转借出租或以其他方式提供给他人使用</view>
@@ -20,10 +20,10 @@
<view class="p">3.2 使用规范请合理使用设备避免进水摔落私自拆卸或改装请勿靠近明火与高温环境室外雨天请避免使用儿童应在监护下使用</view>
<view class="p">3.3 禁止行为将设备用于违法或不当用途以任何方式影响设备或系统的正常运行通过非正常手段规避计费或归还流程</view>
<view class="h1">计费与结算微信支付分</view>
<view class="h1">计费与结算芝麻信用</view>
<view class="p"><text class="bold">4.1 计费规则</text>以小程序展示的实时计费规则为准可能包含时长计费封顶价服务费等订单生成后将据此计费</view>
<view class="p"><text class="bold">4.2 微信支付分免押</text>若您开通并通过信用评估可享受免押租借如评估未通过可能需预授权或押金具体以页面提示为准</view>
<view class="p">4.3 结算与扣款订单结束后我们将基于实际使用情况与平台规则完成结算并通过微信支付分/微信支付进行扣款</view>
<view class="p"><text class="bold">4.2 芝麻信用免押</text>若您开通并通过信用评估可享受免押租借如评估未通过可能需预授权或押金具体以页面提示为准</view>
<view class="p">4.3 结算与扣款订单结束后我们将基于实际使用情况与平台规则完成结算并通过芝麻信用/支付宝支付进行扣款</view>
<view class="p">4.4 异常与争议如对计费或结算有异议请在订单完成后48小时内通过"我的-客服"提交逾期可能影响处理结果</view>
<view class="h1">设备归还与逾期处理</view>
+4 -4
View File
@@ -16,18 +16,18 @@
</view>
<view class="h1">II. Information We Collect</view>
<view class="p">2.1 Account information: WeChat login identifier (such as openId/unionId), nickname and avatar (with your authorization), mobile phone number (obtained through WeChat after your authorization).</view>
<view class="p">2.1 Account information: Alipay login identifier (such as userId), nickname and avatar (with your authorization), mobile phone number (obtained through Alipay after your authorization).</view>
<view class="p">2.2 Order and device information: rental records, usage duration, fees, return points, device status, abnormal records, etc.</view>
<view class="p">2.3 Location and outlet information: used to find nearby outlets and navigation after your authorization, and will not be obtained without authorization.</view>
<view class="p">2.4 Log information: To ensure service security and stability, we may record operation logs, network requests, and error information.</view>
<view class="h1">III. Purpose of Information Use</view>
<view class="p">3.1 Provide core functions: identity verification, deposit-free rental (WeChat Payment Score assessment), order billing and settlement, customer service and after-sales.</view>
<view class="p">3.1 Provide core functions: identity verification, deposit-free rental (Sesame Credit assessment), order billing and settlement, customer service and after-sales.</view>
<view class="p">3.2 Security risk control: prevent fraud, violations and risk control; ensure system and device security.</view>
<view class="p">3.3 Product optimization: statistics and analysis to improve experience (conducted after de-identification/anonymization).</view>
<view class="h1">IV. WeChat Payment Score and Payment</view>
<view class="p">4.1 To implement deposit-free rental, we will conduct necessary data interaction with WeChat Payment Score (such as credit assessment results and order settlement). Related data processing follows the rules of WeChat Pay and WeChat Payment Score.</view>
<view class="h1">IV. Sesame Credit and Payment</view>
<view class="p">4.1 To implement deposit-free rental, we will conduct necessary data interaction with Sesame Credit (such as credit assessment results and order settlement). Related data processing follows the rules of Alipay and Sesame Credit.</view>
<view class="p">4.2 If you fail the assessment, pre-authorization or deposit processing may be required, subject to page prompts.</view>
<view class="h1">V. Sharing, Transfer and Public Disclosure</view>
+3 -3
View File
@@ -22,12 +22,12 @@
<view class="p">2.4 日志信息为保障服务安全与稳定我们可能记录操作日志网络请求与错误信息</view>
<view class="h1">信息使用目的</view>
<view class="p">3.1 提供核心功能身份验证免押租借微信支付分评估订单计费结算客服与售后</view>
<view class="p">3.1 提供核心功能身份验证免押租借芝麻信用评估订单计费结算客服与售后</view>
<view class="p">3.2 安全风控防范欺诈违规与风险控制保障系统与设备安全</view>
<view class="p">3.3 产品优化统计与分析以改进体验在去标识化/匿名化后进行</view>
<view class="h1">微信支付分与支付</view>
<view class="p">4.1 为实现免押租借我们将与微信支付分进行必要的数据交互如信用评估结果订单结算相关数据处理遵循微信支付与微信支付分的规则</view>
<view class="h1">芝麻信用与支付</view>
<view class="p">4.1 为实现免押租借我们将与芝麻信用进行必要的数据交互如信用评估结果订单结算相关数据处理遵循支付宝与芝麻信用的规则</view>
<view class="p">4.2 如您未通过评估可能需进行预授权或押金处理以页面提示为准</view>
<view class="h1">共享转移与公开披露</view>
+17 -12
View File
@@ -8,16 +8,16 @@
<view class="title">{{ $t('auth.loginTitle') }}</view>
<view class="subtitle">{{ $t('auth.loginDesc') }}</view>
<!-- 微信一键手机号快捷登录推荐 -->
<!-- 支付宝一键手机号快捷登录推荐 -->
<button v-if="!isAgreed" class="btn primary" @click="handleLoginClick">
{{ $t('auth.getPhoneNumber') }}
</button>
<button v-else class="btn primary" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
<button v-else class="btn primary" open-type="getAuthorize" scope="phoneNumber" @getAuthorize="onGetPhoneNumber">
{{ $t('auth.getPhoneNumber') }}
</button>
<!-- 微信登录不授权手机号时使用 -->
<!-- <button class="btn outline" @click="onWeChatLogin">微信登录</button> -->
<!-- 支付宝登录不授权手机号时使用 -->
<!-- <button class="btn outline" @click="onAlipayLogin">支付宝登录</button> -->
<view class="agreement-box">
<checkbox-group @change="onAgreementChange">
@@ -38,7 +38,7 @@
<script setup>
import { ref, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { wxLogin, getUserPhoneNumber, getUserInfo } from '../../util/index.js'
import { alipayLogin, getUserPhoneNumber, getUserInfo } from '../../util/index.js'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
@@ -113,12 +113,12 @@
uni.reLaunch({ url: target })
}
const onWeChatLogin = async () => {
const onAlipayLogin = async () => {
try {
// 先检查是否同意协议
await checkAgreement()
await wxLogin()
await alipayLogin()
uni.showToast({ title: $t('auth.loginSuccess'), icon: 'success' })
await navigateAfterLogin()
} catch (error) {
@@ -129,16 +129,21 @@
}
const onGetPhoneNumber = async (e) => {
if (!e || e.detail.errMsg !== 'getPhoneNumber:ok') {
// 支付宝获取手机号的回调处理
if (!e || !e.detail || !e.detail.response) {
uni.showToast({ title: $t('auth.phoneCancelled'), icon: 'none' })
return
}
try {
// 先微信登录,获取 token
await wxLogin()
// 再用微信返回的临时 code 换取手机号
await getUserPhoneNumber(e.detail.code)
// 先支付宝登录,获取 token
await alipayLogin()
// 再用支付宝返回的授权码换取手机号
// 支付宝的授权码在 e.detail.response 中
const authCode = e.detail.response?.response?.code || e.detail.response?.code
if (authCode) {
await getUserPhoneNumber(authCode)
}
uni.showToast({ title: $t('auth.loginSuccess'), icon: 'success' })
await navigateAfterLogin()
} catch (error) {
+27 -55
View File
@@ -9,7 +9,7 @@
<view class="nickname">{{ userInfo.nickName || $t('user.clickToLogin') }}</view>
<view class="subtext">{{ userInfo.phone ? maskPhone(userInfo.phone) : $t('user.loginPrompt') }}</view>
</view>
<uv-icon type="right" size="16" color="#999"></uv-icon>
<uv-icon name="arrow-right" size="16" color="#999"></uv-icon>
</view>
@@ -113,7 +113,7 @@
<u-popup ref="authPopup" mode="center" border-radius="15" width="600rpx" @open="onPopupOpen" @close="onPopupClose">
<view class="auth-popup">
<view class="auth-title">授权登录</view>
<view class="auth-desc">获取您的微信头像、昵称等公开信息</view>
<view class="auth-desc">获取您的支付宝头像、昵称等公开信息</view>
<view class="auth-buttons">
<button class="cancel-btn" @click="closeAuthPopup">取消</button>
<button class="confirm-btn" @click="getUserProfile">确定</button>
@@ -135,7 +135,7 @@
} from '@dcloudio/uni-app';
import {
wxLogin,
alipayLogin,
getUserInfo
} from '../../util/index.js';
import {
@@ -184,7 +184,7 @@ import {
'Content-Language': languageCode
},
data: {
appPlatform: 'wechat', // 微信平台
appPlatform: 'alipay', // 支付宝平台
appType: 'user' // 用户端
}
})
@@ -267,11 +267,12 @@ import {
// 初始化应用版本号(多端兼容,取可用信息)
const initVersion = () => {
// #ifdef MP-WEIXIN
// #ifdef MP-ALIPAY
try {
const info = wx.getAccountInfoSync && wx.getAccountInfoSync();
if (info && info.miniProgram && info.miniProgram.version) {
appVersion.value = info.miniProgram.version;
// 支付宝小程序获取版本号
const systemInfo = uni.getSystemInfoSync();
if (systemInfo && systemInfo.version) {
appVersion.value = systemInfo.version;
}
} catch (e) {}
// #endif
@@ -368,12 +369,13 @@ import {
redirectToLogin()
return
}
// #ifdef MP-WEIXIN
getUserProfile()
// #ifdef MP-ALIPAY
// 支付宝小程序通过页面跳转处理用户资料
navigateTo('/pages/userProfile/index')
// #endif
// #ifndef MP-WEIXIN
// #ifndef MP-ALIPAY
uni.showToast({
title: $t('auth.pleaseUseInWechat'),
title: $t('auth.pleaseUseInAlipay'),
icon: 'none'
})
// #endif
@@ -446,65 +448,35 @@ import {
// 这里可以添加弹窗关闭后的逻辑
};
// 获取微信用户个人信息
// 获取支付宝用户个人信息(已废弃,使用页面跳转方式)
const getUserProfile = () => {
// #ifdef MP-WEIXIN
uni.showLoading({
title: $t('common.getting'),
mask: true
});
wx.getUserProfile({
desc: '用于完善会员资料',
success: (res) => {
console.log('获取用户信息成功:', res);
updateUserInfo(res.userInfo);
uploadAvatarAndRefresh(res.userInfo);
},
fail: (err) => {
console.error('获取用户信息失败:', err);
uni.showToast({
title: '获取用户信息失败',
icon: 'none'
});
},
complete: () => {
uni.hideLoading();
closeAuthPopup();
}
});
// #ifdef MP-ALIPAY
// 支付宝小程序通过页面跳转处理用户资料
navigateTo('/pages/userProfile/index')
// #endif
// #ifndef MP-WEIXIN
// #ifndef MP-ALIPAY
uni.showToast({
title: $t('auth.pleaseUseInWechat'),
title: $t('auth.pleaseUseInAlipay'),
icon: 'none'
});
closeAuthPopup();
// #endif
};
// 更新用户信息
const updateUserInfo = async (wxUserInfo) => {
// 更新用户信息(支付宝小程序通过页面跳转处理)
const updateUserInfo = async (alipayUserInfo) => {
try {
// 更新本地用户信息
const updatedInfo = {
...userInfo.value,
nickName: wxUserInfo.nickName,
avatar: wxUserInfo.avatarUrl
nickName: alipayUserInfo.nickName,
avatar: alipayUserInfo.avatarUrl
};
userInfo.value = updatedInfo;
uni.setStorageSync('userInfo', updatedInfo);
// 这里可以添加调用后端API更新用户信息的代码
// const updateRes = await updateUserInfoApi({
// openId: openId.value,
// nickName: wxUserInfo.nickName,
// avatarUrl: wxUserInfo.avatarUrl,
// gender: wxUserInfo.gender
// });
uni.showToast({
title: $t('user.updateSuccess'),
icon: 'success'
@@ -522,9 +494,9 @@ import {
};
// 下载并上传头像,更新用户信息
const uploadAvatarAndRefresh = async (wxUserInfo) => {
const uploadAvatarAndRefresh = async (alipayUserInfo) => {
try {
const avatarUrl = wxUserInfo?.avatarUrl
const avatarUrl = alipayUserInfo?.avatarUrl
if (!avatarUrl) {
uni.showToast({
title: '未获取到头像地址',
@@ -532,7 +504,7 @@ import {
})
return
}
// 下载微信头像为本地临时文件
// 下载支付宝头像为本地临时文件
const tempFilePath = await new Promise((resolve, reject) => {
uni.downloadFile({
url: avatarUrl,
+3 -3
View File
@@ -230,9 +230,9 @@
// 获取支付方式文本
const getPayWayText = () => {
const payWayMap = {
'wx_score_pay': $t('order.depositFree'),
'wx_member_pay': $t('order.memberOrder'),
'wx_pay': $t('order.depositPay')
'alipay_score_pay': $t('order.depositFree'),
'alipay_member_pay': $t('order.memberOrder'),
'alipay_pay': $t('order.depositPay')
}
return payWayMap[orderInfo.value.payWay] || $t('order.depositFree')
}
+4 -3
View File
@@ -271,9 +271,9 @@
title: $t('common.processing')
});
//
//
const res = await uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/wx-payment/create/${order.orderNo}`,
url: `${URL || 'http://127.0.0.1:8080'}/app/alipay-payment/create/${order.orderNo}`,
method: 'GET',
header: {
'Authorization': "Bearer " + uni.getStorageSync('token'),
@@ -284,8 +284,9 @@
if (res.statusCode === 200 && res.data.code === 200) {
const payParams = res.data.data;
//
//
await uni.requestPayment({
provider: 'alipay',
...payParams,
success: async () => {
uni.showToast({
+5 -4
View File
@@ -246,9 +246,9 @@ export default {
title: this.$t('common.processing')
})
//
//
const res = await uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/wx-payment/create/${this.orderInfo.orderNo}`,
url: `${URL || 'http://127.0.0.1:8080'}/app/alipay-payment/create/${this.orderInfo.orderNo}`,
method: 'GET',
header: {
'Authorization': "Bearer " + uni.getStorageSync('token'),
@@ -259,8 +259,9 @@ export default {
if (res.statusCode === 200 && res.data.code === 200) {
const payParams = res.data.data
//
//
await uni.requestPayment({
provider: 'alipay',
...payParams,
success: async () => {
uni.showToast({
@@ -377,7 +378,7 @@ export default {
async checkOrderStatus() {
try {
const res = await uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/wx-payment/status/${this.orderInfo.orderNo}`,
url: `${URL || 'http://127.0.0.1:8080'}/app/alipay-payment/status/${this.orderInfo.orderNo}`,
method: 'GET',
header: {
'Authorization': "Bearer " + uni.getStorageSync('token'),
+8 -8
View File
@@ -15,21 +15,21 @@
<!-- 支付方式标识 -->
<view class="device-right">
<!-- 微信支付分标识 -->
<view class="payment-badge wx-score" v-if="orderInfo.payWay == 'wx_score_pay'">
<image src="/static/images/wxpayflag.png" mode="aspectFit" class="badge-icon"></image>
<!-- 支付宝信用免押标识 -->
<view class="payment-badge alipay-score" v-if="orderInfo.payWay == 'alipay_score_pay'">
<image src="/static/images/alipay.svg" mode="aspectFit" class="badge-icon"></image>
<view class="badge-text">
<text>{{ $t('order.wxPayScore') }}</text>
<text>{{ $t('order.alipayScore') }}</text>
<text class="divider">|</text>
<text class="highlight">{{ $t('order.depositFree') }}</text>
</view>
</view>
<!-- 会员订单标识 -->
<view class="payment-badge member" v-else-if="orderInfo.payWay == 'wx_member_pay'">
<view class="payment-badge member" v-else-if="orderInfo.payWay == 'alipay_member_pay'">
<text class="badge-text">{{ $t('order.memberOrder') }}</text>
</view>
<!-- 微信支付押金标识 -->
<view class="payment-badge deposit" v-else-if="orderInfo.payWay == 'wx_pay'">
<!-- 支付宝支付押金标识 -->
<view class="payment-badge deposit" v-else-if="orderInfo.payWay == 'alipay_pay'">
<text class="badge-text">{{ $t('order.depositPay') }}</text>
</view>
</view>
@@ -322,7 +322,7 @@
this.countdownTimer = null
}
},
// iOS/
// iOS/
parseStartTimeToMs(timeStr) {
if (!timeStr) return NaN
if (typeof timeStr === 'number') {
+1 -1
View File
@@ -6,7 +6,7 @@
<script>
import {
wxLogin,
alipayLogin,
} from '../../../util/index'
import {
+3 -3
View File
@@ -4,8 +4,8 @@
<view class="avatar-container">
<image class="avatar" v-if="userInfo.avatar" :src="userInfo.avatar" mode="aspectFill"></image>
<image v-else class="avatar" src="@/static/head.png" mode="aspectFill"></image>
<!-- 覆盖在头像上的微信选择头像授权按钮仅小程序生效 -->
<!-- #ifdef MP-WEIXIN -->
<!-- 覆盖在头像上的支付宝选择头像授权按钮仅小程序生效 -->
<!-- #ifdef MP-ALIPAY -->
<button class="avatar-choose-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"></button>
<!-- #endif -->
</view>
@@ -282,7 +282,7 @@ function maskPhone(phone) {
}
/* 仅小程序端存在,此按钮覆盖在头像上捕获点击以触发选择头像 */
/* #ifdef MP-WEIXIN */
/* #ifdef MP-ALIPAY */
.avatar-choose-btn {
position: absolute;
left: 0;
+29 -27
View File
@@ -10,12 +10,12 @@ import { getCommonByBrand } from "@/config/api/system"
import { HELP_CONTENT } from "@/constants/help"
// import { GET_PHONE_NUMBER_URL } from "../config/url"
// 微信登录方法
export const wxLogin = () => {
// 支付宝登录方法
export const alipayLogin = () => {
return new Promise((resolve, reject) => {
// 1. 获取微信登录凭证
// 1. 获取支付宝登录凭证
uni.login({
provider: 'weixin',
provider: 'alipay',
success: async (loginRes) => {
try {
@@ -23,7 +23,7 @@ export const wxLogin = () => {
// 2. 发送 code 到后端换取 token
const result = await login({
code: loginRes.code,
appid: "wx2165f0be356ae7a9"
appid: "2021006117693332"
})
if (result.code === 200) {
@@ -42,7 +42,7 @@ export const wxLogin = () => {
throw new Error(result.message || '登录失败')
}
} else {
throw new Error('获取微信登录凭证失败')
throw new Error('获取支付宝登录凭证失败')
}
} catch (error) {
@@ -55,7 +55,7 @@ export const wxLogin = () => {
},
fail: (error) => {
uni.showToast({
title: '微信登录失败',
title: '支付宝登录失败',
icon: 'none'
})
reject(error)
@@ -64,6 +64,9 @@ export const wxLogin = () => {
})
}
// 兼容旧代码,保留wxLogin别名
export const wxLogin = alipayLogin
// 检查登录状态
// export const checkLogin = () => {
// const token = uni.getStorageSync('token')
@@ -104,7 +107,7 @@ export const getUserPhoneNumber = (code) => {
'Content-Type': 'application/json'
},
data: {
code: code, // 微信获取手机号授权后的临时code
code: code, // 支付宝获取手机号授权后的临时code
appid: appid
},
success: (res) => {
@@ -137,34 +140,26 @@ export const getUserPhoneNumber = (code) => {
})
}
// 调用微信支付分接口
export const initiateWeChatScorePayment = (paymentData) => {
// 调用支付宝支付接口
export const initiateAlipayPayment = (paymentData) => {
return new Promise((resolve, reject) => {
// 确保paymentData包含所需数据
if (!paymentData || !paymentData.data || !paymentData.data.package) {
if (!paymentData || !paymentData.data) {
reject(new Error('支付参数不完整'));
return;
}
// 使用wx.openBusinessView打开微信支付分小程序
wx.openBusinessView({
businessType: 'wxpayScoreUse',
extraData: {
mch_id: paymentData.data.mch_id,
package: paymentData.data.package
},
success: (businessRes) => {
// 根据返回结果判断是否完成支付
if (businessRes.errMsg === 'openBusinessView:ok') {
resolve(businessRes);
} else {
reject(new Error('支付流程未完成'));
}
// 使用uni.requestPayment调用支付宝支付
uni.requestPayment({
provider: 'alipay',
orderInfo: paymentData.data.orderInfo || paymentData.data,
success: (res) => {
resolve(res);
},
fail: (error) => {
console.error('微信支付分小程序调用失败', error);
console.error('支付宝支付调用失败', error);
uni.showToast({
title: error.errMsg || '支付接口调用失败',
title: error.errMsg || '支付接口调用失败',
icon: 'none'
});
reject(error);
@@ -173,7 +168,14 @@ export const initiateWeChatScorePayment = (paymentData) => {
});
}
// 兼容旧代码,保留旧函数名
export const initiateWeChatScorePayment = initiateAlipayPayment
export const getQueryString = function(url, name) {
// 添加空值检查,兼容支付宝小程序
if (!url || typeof url !== 'string') {
return null
}
var reg = new RegExp('(^|&|/?)' + name + '=([^&|/?]*)(&|/?|$)', 'i')
var r = url.substr(1).match(reg)
if (r != null) {
+614 -19
View File
@@ -39,6 +39,287 @@ const getPermissionText = (key) => {
// 腾讯地图Key
const QQMAP_KEY = 'RO5BZ-ECZ63-7US3C-RT5QW-TIDZE-2FF35';
// 高德地图配置(支付宝小程序专用)
const AMAP_KEY = 'a07af802ff0a04f012954ff4e69b36d0';
const AMAP_SECURITY_KEY = '119f36535ab42b8b2c857f29f9320b06';
const AMAP_BASE_URL = 'https://restapi.amap.com/v3';
const AMAP_PATH_PREFIX = '/v3';
// 运行环境标识(编译期指令)
const isAlipayEnv = (() => {
let flag = false;
// #ifdef MP-ALIPAY
flag = true;
// #endif
return flag;
})();
// 轻量级 MD5 实现(用于生成高德安全 sig)
function md5(string) {
function RotateLeft(lValue, iShiftBits) {
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
}
function AddUnsigned(lX, lY) {
const lX4 = lX & 0x40000000;
const lY4 = lY & 0x40000000;
const lX8 = lX & 0x80000000;
const lY8 = lY & 0x80000000;
const lResult = (lX & 0x3fffffff) + (lY & 0x3fffffff);
if (lX4 & lY4) {
return lResult ^ 0x80000000 ^ lX8 ^ lY8;
}
if (lX4 | lY4) {
if (lResult & 0x40000000) {
return lResult ^ 0xc0000000 ^ lX8 ^ lY8;
}
return lResult ^ 0x40000000 ^ lX8 ^ lY8;
}
return lResult ^ lX8 ^ lY8;
}
function F(x, y, z) { return (x & y) | (~x & z); }
function G(x, y, z) { return (x & z) | (y & ~z); }
function H(x, y, z) { return x ^ y ^ z; }
function I(x, y, z) { return y ^ (x | ~z); }
function FF(a, b, c, d, x, s, ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
}
function GG(a, b, c, d, x, s, ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
}
function HH(a, b, c, d, x, s, ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
}
function II(a, b, c, d, x, s, ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
}
function ConvertToWordArray(str) {
const lWordArray = [];
let lMessageLength = str.length;
let lNumberOfWordsTempOne = lMessageLength + 8;
const lNumberOfWordsTempTwo = (lNumberOfWordsTempOne - (lNumberOfWordsTempOne % 64)) / 64;
const lNumberOfWords = (lNumberOfWordsTempTwo + 1) * 16;
let lBytePosition = 0;
let lByteCount = 0;
while (lByteCount < lMessageLength) {
const lWordCount = (lByteCount - (lByteCount % 4)) / 4;
lBytePosition = (lByteCount % 4) * 8;
lWordArray[lWordCount] = (lWordArray[lWordCount] || 0) | (str.charCodeAt(lByteCount) << lBytePosition);
lByteCount++;
}
const lWordCount = (lByteCount - (lByteCount % 4)) / 4;
lBytePosition = (lByteCount % 4) * 8;
lWordArray[lWordCount] = lWordArray[lWordCount] || 0;
lWordArray[lWordCount] |= 0x80 << lBytePosition;
lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
return lWordArray;
}
function WordToHex(lValue) {
let WordToHexValue = '';
for (let lCount = 0; lCount <= 3; lCount++) {
const lByte = (lValue >>> (lCount * 8)) & 255;
const WordToHexValueTemp = '0' + lByte.toString(16);
WordToHexValue += WordToHexValueTemp.substr(WordToHexValueTemp.length - 2, 2);
}
return WordToHexValue;
}
function Utf8Encode(str) {
str = str.replace(/\r\n/g, '\n');
let utftext = '';
for (let n = 0; n < str.length; n++) {
const c = str.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
} else if (c > 127 && c < 2048) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
} else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
}
let x = [];
let k, AA, BB, CC, DD, a, b, c, d;
const S11 = 7, S12 = 12, S13 = 17, S14 = 22;
const S21 = 5, S22 = 9, S23 = 14, S24 = 20;
const S31 = 4, S32 = 11, S33 = 16, S34 = 23;
const S41 = 6, S42 = 10, S43 = 15, S44 = 21;
string = Utf8Encode(String(string));
x = ConvertToWordArray(string);
a = 0x67452301;
b = 0xefcdab89;
c = 0x98badcfe;
d = 0x10325476;
for (k = 0; k < x.length; k += 16) {
AA = a; BB = b; CC = c; DD = d;
a = FF(a, b, c, d, x[k + 0], S11, 0xd76aa478);
d = FF(d, a, b, c, x[k + 1], S12, 0xe8c7b756);
c = FF(c, d, a, b, x[k + 2], S13, 0x242070db);
b = FF(b, c, d, a, x[k + 3], S14, 0xc1bdceee);
a = FF(a, b, c, d, x[k + 4], S11, 0xf57c0faf);
d = FF(d, a, b, c, x[k + 5], S12, 0x4787c62a);
c = FF(c, d, a, b, x[k + 6], S13, 0xa8304613);
b = FF(b, c, d, a, x[k + 7], S14, 0xfd469501);
a = FF(a, b, c, d, x[k + 8], S11, 0x698098d8);
d = FF(d, a, b, c, x[k + 9], S12, 0x8b44f7af);
c = FF(c, d, a, b, x[k + 10], S13, 0xffff5bb1);
b = FF(b, c, d, a, x[k + 11], S14, 0x895cd7be);
a = FF(a, b, c, d, x[k + 12], S11, 0x6b901122);
d = FF(d, a, b, c, x[k + 13], S12, 0xfd987193);
c = FF(c, d, a, b, x[k + 14], S13, 0xa679438e);
b = FF(b, c, d, a, x[k + 15], S14, 0x49b40821);
a = GG(a, b, c, d, x[k + 1], S21, 0xf61e2562);
d = GG(d, a, b, c, x[k + 6], S22, 0xc040b340);
c = GG(c, d, a, b, x[k + 11], S23, 0x265e5a51);
b = GG(b, c, d, a, x[k + 0], S24, 0xe9b6c7aa);
a = GG(a, b, c, d, x[k + 5], S21, 0xd62f105d);
d = GG(d, a, b, c, x[k + 10], S22, 0x2441453);
c = GG(c, d, a, b, x[k + 15], S23, 0xd8a1e681);
b = GG(b, c, d, a, x[k + 4], S24, 0xe7d3fbc8);
a = GG(a, b, c, d, x[k + 9], S21, 0x21e1cde6);
d = GG(d, a, b, c, x[k + 14], S22, 0xc33707d6);
c = GG(c, d, a, b, x[k + 3], S23, 0xf4d50d87);
b = GG(b, c, d, a, x[k + 8], S24, 0x455a14ed);
a = GG(a, b, c, d, x[k + 13], S21, 0xa9e3e905);
d = GG(d, a, b, c, x[k + 2], S22, 0xfcefa3f8);
c = GG(c, d, a, b, x[k + 7], S23, 0x676f02d9);
b = GG(b, c, d, a, x[k + 12], S24, 0x8d2a4c8a);
a = HH(a, b, c, d, x[k + 5], S31, 0xfffa3942);
d = HH(d, a, b, c, x[k + 8], S32, 0x8771f681);
c = HH(c, d, a, b, x[k + 11], S33, 0x6d9d6122);
b = HH(b, c, d, a, x[k + 14], S34, 0xfde5380c);
a = HH(a, b, c, d, x[k + 1], S31, 0xa4beea44);
d = HH(d, a, b, c, x[k + 4], S32, 0x4bdecfa9);
c = HH(c, d, a, b, x[k + 7], S33, 0xf6bb4b60);
b = HH(b, c, d, a, x[k + 10], S34, 0xbebfbc70);
a = HH(a, b, c, d, x[k + 13], S31, 0x289b7ec6);
d = HH(d, a, b, c, x[k + 0], S32, 0xeaa127fa);
c = HH(c, d, a, b, x[k + 3], S33, 0xd4ef3085);
b = HH(b, c, d, a, x[k + 6], S34, 0x4881d05);
a = HH(a, b, c, d, x[k + 9], S31, 0xd9d4d039);
d = HH(d, a, b, c, x[k + 12], S32, 0xe6db99e5);
c = HH(c, d, a, b, x[k + 15], S33, 0x1fa27cf8);
b = HH(b, c, d, a, x[k + 2], S34, 0xc4ac5665);
a = II(a, b, c, d, x[k + 0], S41, 0xf4292244);
d = II(d, a, b, c, x[k + 7], S42, 0x432aff97);
c = II(c, d, a, b, x[k + 14], S43, 0xab9423a7);
b = II(b, c, d, a, x[k + 5], S44, 0xfc93a039);
a = II(a, b, c, d, x[k + 12], S41, 0x655b59c3);
d = II(d, a, b, c, x[k + 3], S42, 0x8f0ccc92);
c = II(c, d, a, b, x[k + 10], S43, 0xffeff47d);
b = II(b, c, d, a, x[k + 1], S44, 0x85845dd1);
a = II(a, b, c, d, x[k + 8], S41, 0x6fa87e4f);
d = II(d, a, b, c, x[k + 15], S42, 0xfe2ce6e0);
c = II(c, d, a, b, x[k + 6], S43, 0xa3014314);
b = II(b, c, d, a, x[k + 13], S44, 0x4e0811a1);
a = II(a, b, c, d, x[k + 4], S41, 0xf7537e82);
d = II(d, a, b, c, x[k + 11], S42, 0xbd3af235);
c = II(c, d, a, b, x[k + 2], S43, 0x2ad7d2bb);
b = II(b, c, d, a, x[k + 9], S44, 0xeb86d391);
a = AddUnsigned(a, AA);
b = AddUnsigned(b, BB);
c = AddUnsigned(c, CC);
d = AddUnsigned(d, DD);
}
const temp = WordToHex(a) + WordToHex(b) + WordToHex(c) + WordToHex(d);
return temp.toLowerCase();
}
// 将参数按字典序拼装为查询字符串
function buildSortedQuery(params) {
const sortedKeys = Object.keys(params || {}).sort();
return sortedKeys.map(key => `${key}=${params[key] !== undefined ? params[key] : ''}`).join('&');
}
// 调试开关:如需打印签名原串和 sig,临时改为 true
const DEBUG_AMAP_SIG = false;
// 构造高德 API 签名(正确规则:参数字符串 + 私钥,不含路径)
function buildAmapSig(params) {
const query = buildSortedQuery(params);
const raw = `${query}${AMAP_SECURITY_KEY}`;
if (DEBUG_AMAP_SIG) {
console.log('[AMap Sig Debug] query:', query, 'raw:', raw, 'sig:', md5(raw));
}
return md5(raw);
}
// 过滤掉 undefined / null 参数,避免签名与请求不一致
function sanitizeParams(obj = {}) {
const cleaned = {};
Object.keys(obj).forEach((k) => {
const v = obj[k];
if (v !== undefined && v !== null) {
cleaned[k] = v;
}
});
return cleaned;
}
// 统一的高德 API 请求
function requestAmap(path, params = {}) {
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
const baseParams = sanitizeParams({
key: AMAP_KEY,
...params
});
const sig = buildAmapSig(baseParams);
const queryParams = sanitizeParams({
...baseParams,
sig
});
return new Promise((resolve, reject) => {
uni.request({
url: `${AMAP_BASE_URL}${normalizedPath}`,
method: 'GET',
data: queryParams,
success: (res) => {
if (res.data && res.data.status === '1') {
resolve(res.data);
} else {
reject(res.data || { message: 'AMap request failed' });
}
},
fail: reject
});
});
}
// 将 WGS84 转 GCJ02(支付宝 my.getLocation 返回 WGS84
async function convertToGcj02(longitude, latitude) {
if (!isAlipayEnv) {
return { longitude, latitude };
}
try {
const data = await requestAmap('/assistant/coordinate/convert', {
locations: `${longitude},${latitude}`,
coordsys: 'gps' // gps 即 WGS84
});
const locStr = (data.locations || '').split(';')[0] || '';
const [lngStr, latStr] = locStr.split(',');
const lng = Number(lngStr);
const lat = Number(latStr);
if (!isNaN(lng) && !isNaN(lat)) {
return { longitude: lng, latitude: lat };
}
} catch (err) {
console.warn('坐标转换失败,使用原始坐标:', err);
}
return { longitude, latitude };
}
// 内联腾讯地图SDK核心代码
const QQMapWX = (function() {
// 错误配置
@@ -118,22 +399,55 @@ const QQMapWX = (function() {
}
};
if (!param.location) {
// 微信小程序使用 wx.getLocation
// #ifdef MP-WEIXIN
wx.getLocation({
type: 'gcj02',
success: locationsuccess,
fail: locationfail,
complete: locationcomplete
});
// #endif
// 支付宝小程序使用 my.getLocation(不支持 type
// #ifdef MP-ALIPAY
my.getLocation({
success: (res) => {
// 确保经纬度是数字类型
const result = {
...res,
longitude: Number(res.longitude),
latitude: Number(res.latitude)
};
locationsuccess(result);
},
fail: locationfail,
complete: locationcomplete
});
// #endif
// 其他平台使用 uni.getLocation
// #ifndef MP-WEIXIN
// #ifndef MP-ALIPAY
uni.getLocation({
type: 'gcj02',
success: locationsuccess,
fail: locationfail,
complete: locationcomplete
});
// #endif
// #endif
} else if (that.checkLocation(param)) {
const location = Utils.getLocationParam(param.location);
locationsuccess(location);
}
},
// 构造微信请求参数
// 构造腾讯地图API请求参数(跨平台兼容)
buildWxRequestConfig(param, options, feature) {
const that = this;
options.header = { "content-type": "application/json" };
// GET请求不需要设置content-typeuni.request会自动处理
options.header = options.header || {};
options.method = 'GET';
options.success = function (res) {
const data = res.data;
@@ -280,8 +594,10 @@ const QQMapWX = (function() {
const locationsuccess = function (result) {
requestParam.location = result.latitude + ',' + result.longitude;
wx.request(Utils.buildWxRequestConfig(options, {
url: URL_GET_GEOCODER,
// 调用腾讯地图API - 逆地理编码接口
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_GET_GEOCODER, // 腾讯地图API地址:https://apis.map.qq.com/ws/geocoder/v1/
data: requestParam
}, 'reverseGeocoder'));
};
@@ -323,8 +639,10 @@ const QQMapWX = (function() {
const locationsuccess = function (result) {
requestParam.boundary = "nearby(" + result.latitude + "," + result.longitude + "," + distance + "," + auto_extend + ")";
wx.request(Utils.buildWxRequestConfig(options, {
url: URL_SEARCH,
// 调用腾讯地图API - POI周边检索接口
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_SEARCH, // 腾讯地图API地址:https://apis.map.qq.com/ws/place/v1/search
data: requestParam
}, 'search'));
};
@@ -368,15 +686,19 @@ const QQMapWX = (function() {
if (options.location) {
const locationsuccess = function (result) {
requestParam.location = result.latitude + ',' + result.longitude;
wx.request(Utils.buildWxRequestConfig(options, {
url: URL_SUGGESTION,
// 调用腾讯地图API - 地点搜索建议接口
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_SUGGESTION, // 腾讯地图API地址:https://apis.map.qq.com/ws/place/v1/suggestion
data: requestParam
}, "suggest"));
};
Utils.locationProcess(options, locationsuccess);
} else {
wx.request(Utils.buildWxRequestConfig(options, {
url: URL_SUGGESTION,
// 调用腾讯地图API - 地点搜索建议接口(无位置信息)
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_SUGGESTION, // 腾讯地图API地址:https://apis.map.qq.com/ws/place/v1/suggestion
data: requestParam
}, "suggest"));
}
@@ -408,8 +730,10 @@ const QQMapWX = (function() {
const locationsuccess = function (result) {
requestParam.from = result.latitude + ',' + result.longitude;
wx.request(Utils.buildWxRequestConfig(options, {
url: URL_DISTANCE,
// 调用腾讯地图API - 距离计算接口
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_DISTANCE, // 腾讯地图API地址:https://apis.map.qq.com/ws/distance/v1/
data: requestParam
}, 'calculateDistance'));
};
@@ -426,6 +750,10 @@ let qqmapInstance = null;
// 初始化腾讯地图SDK
function initQQMap() {
if (isAlipayEnv) {
// 支付宝端改用高德地图服务
return null;
}
if (!qqmapInstance) {
try {
qqmapInstance = new QQMapWX({
@@ -441,6 +769,9 @@ function initQQMap() {
// 获取腾讯地图SDK实例
function getQQMapInstance() {
if (isAlipayEnv) {
return null;
}
return qqmapInstance || initQQMap();
}
@@ -487,8 +818,9 @@ function getUserLocation() {
wx.getLocation({
type: 'gcj02',
success: (res) => {
const longitude = parseFloat(res.longitude.toFixed(5));
const latitude = parseFloat(res.latitude.toFixed(5));
// 确保经纬度是数字类型
const longitude = parseFloat(Number(res.longitude).toFixed(5));
const latitude = parseFloat(Number(res.latitude).toFixed(5));
console.log('地址获取成功');
resolve({
longitude,
@@ -533,8 +865,9 @@ function getUserLocation() {
wx.getLocation({
type: 'gcj02',
success: (res) => {
const longitude = parseFloat(res.longitude.toFixed(5));
const latitude = parseFloat(res.latitude.toFixed(5));
// 确保经纬度是数字类型
const longitude = parseFloat(Number(res.longitude).toFixed(5));
const latitude = parseFloat(Number(res.latitude).toFixed(5));
console.log('地址获取成功');
resolve({
longitude,
@@ -550,13 +883,114 @@ function getUserLocation() {
});
// #endif
// 非微信小程序平台:使用 uni.getLocation 做一个尽量兼容的兜底
// 支付宝小程序:使用 my.getLocation API,不支持 type 参数
// #ifdef MP-ALIPAY
// 支付宝小程序权限检查
my.getSetting({
success: (settingRes) => {
const authSetting = settingRes.authSetting || {};
const hasLocationAuth = Object.prototype.hasOwnProperty.call(authSetting, 'location');
const locationAuth = authSetting['location'];
// 已明确拒绝定位
if (locationAuth === false) {
my.showModal({
title: getPermissionText('locationTitle'),
content: getPermissionText('locationNeed'),
confirmText: getPermissionText('goToSettings'),
cancelText: getPermissionText('later'),
success: (modalRes) => {
if (modalRes.confirm) {
my.openSetting({});
}
}
});
reject({
code: 'LOCATION_DENIED',
errMsg: 'user denied location permission'
});
return;
}
const doGetLocation = () => {
// 支付宝小程序使用 my.getLocation,不支持 type 参数
my.getLocation({
success: (res) => {
// 支付宝小程序返回的经纬度可能是字符串,需要先转换为数字
// 注意:支付宝返回的是 WGS84 坐标系,如需 GCJ02 需要转换
const longitude = parseFloat(Number(res.longitude).toFixed(5));
const latitude = parseFloat(Number(res.latitude).toFixed(5));
console.log('支付宝地址获取成功');
resolve({
longitude,
latitude
});
},
fail: (error) => {
console.error('获取位置失败:', error);
reject(error);
}
});
};
// 未显式授权时,主动申请一次权限
if (!hasLocationAuth || locationAuth === undefined) {
my.authorize({
scope: 'scope.userLocation',
success: () => {
doGetLocation();
},
fail: (authErr) => {
console.error('定位授权失败:', authErr);
my.showModal({
title: getPermissionText('locationTitle'),
content: getPermissionText('locationDenied'),
confirmText: getPermissionText('gotIt'),
showCancel: false
});
reject({
code: 'LOCATION_AUTH_FAIL',
errMsg: authErr.errorMessage || 'authorize location fail'
});
}
});
} else {
// 已授权,直接获取定位
doGetLocation();
}
},
fail: (err) => {
console.warn('获取授权设置失败,直接尝试定位:', err);
// 支付宝小程序使用 my.getLocation,不支持 type 参数
my.getLocation({
success: (res) => {
// 支付宝小程序返回的经纬度可能是字符串,需要先转换为数字
const longitude = parseFloat(Number(res.longitude).toFixed(5));
const latitude = parseFloat(Number(res.latitude).toFixed(5));
console.log('支付宝地址获取成功');
resolve({
longitude,
latitude
});
},
fail: (error) => {
console.error('获取位置失败:', error);
reject(error);
}
});
}
});
// #endif
// 其他非微信、非支付宝小程序平台
// #ifndef MP-WEIXIN
// #ifndef MP-ALIPAY
uni.getLocation({
type: 'gcj02',
success: (res) => {
const longitude = parseFloat(res.longitude.toFixed(5));
const latitude = parseFloat(res.latitude.toFixed(5));
// 确保经纬度是数字类型
const longitude = parseFloat(Number(res.longitude).toFixed(5));
const latitude = parseFloat(Number(res.latitude).toFixed(5));
console.log('地址获取成功');
resolve({
longitude,
@@ -569,12 +1003,46 @@ function getUserLocation() {
}
});
// #endif
// #endif
});
}
// 逆地理编码 - 根据经纬度获取地址信息
function getRegeo(longitude, latitude) {
return new Promise((resolve, reject) => {
// 支付宝端使用高德逆地理编码
if (isAlipayEnv) {
(async () => {
try {
const gcj = await convertToGcj02(longitude, latitude);
const data = await requestAmap('/geocode/regeo', {
location: `${gcj.longitude},${gcj.latitude}`,
extensions: 'all',
batch: 'false'
});
const info = data.regeocode || {};
const comp = info.addressComponent || {};
resolve({
success: true,
data: {
formatted_address: info.formatted_address || '',
addressComponent: {
city: comp.city || comp.province || '',
district: comp.district || '',
province: comp.province || '',
street: comp.streetNumber?.street || comp.street || '',
street_number: comp.streetNumber?.number || comp.streetNumber?.street_number || ''
}
}
});
} catch (error) {
console.error('高德逆地理编码失败:', error);
reject({ success: false, message: error.message || '逆地理编码失败' });
}
})();
return;
}
const qqmap = getQQMapInstance();
if (!qqmap) {
reject({ success: false, message: '腾讯地图SDK未初始化' });
@@ -614,6 +1082,45 @@ function getRegeo(longitude, latitude) {
// 搜索周边POI
function getPoiAround(longitude, latitude, keyword = '', radius = 1000) {
return new Promise((resolve, reject) => {
// 支付宝端使用高德周边搜索
if (isAlipayEnv) {
(async () => {
try {
const gcj = await convertToGcj02(longitude, latitude);
const data = await requestAmap('/place/around', {
location: `${gcj.longitude},${gcj.latitude}`,
keywords: keyword,
radius,
offset: 20,
page: 1,
extensions: 'base',
sortrule: 'distance'
});
const pois = data.pois || [];
const normalized = pois.map((item, idx) => ({
id: item.id || idx + 1,
title: item.name || '',
latitude: Number(item.location?.split(',')[1] || item.location?.lat || 0),
longitude: Number(item.location?.split(',')[0] || item.location?.lng || 0),
address: item.address || '',
category: item.type || '',
tel: item.tel || '',
city: item.cityname || '',
district: item.adname || '',
province: item.pname || ''
}));
resolve({
success: true,
data: normalized
});
} catch (error) {
console.error('高德周边搜索失败:', error);
reject({ success: false, message: error.message || '搜索POI失败' });
}
})();
return;
}
const qqmap = getQQMapInstance();
if (!qqmap) {
reject({ success: false, message: '腾讯地图SDK未初始化' });
@@ -645,6 +1152,63 @@ function getPoiAround(longitude, latitude, keyword = '', radius = 1000) {
// 计算距离(异步)
function calculateDistance(from, to) {
return new Promise((resolve, reject) => {
// 支付宝端使用高德距离测量接口
if (isAlipayEnv) {
// to 支持数组
const targets = Array.isArray(to) ? to : [to];
if (!targets.length) {
reject({ success: false, message: '缺少目标坐标' });
return;
}
const doRequest = async (originPoint) => {
if (!originPoint || originPoint.longitude === undefined || originPoint.latitude === undefined) {
reject({ success: false, message: '缺少起点坐标' });
return;
}
try {
const originGcj = await convertToGcj02(originPoint.longitude || originPoint.lng, originPoint.latitude || originPoint.lat);
const convertedTargets = await Promise.all(targets.map(async (item) => {
const lng = item.longitude || item.lng;
const lat = item.latitude || item.lat;
const res = await convertToGcj02(lng, lat);
return `${res.longitude},${res.latitude}`;
}));
const origins = convertedTargets.join('|');
const destination = `${originGcj.longitude},${originGcj.latitude}`;
const data = await requestAmap('/distance', {
origins,
destination,
type: 3 // 3:步行距离
});
const results = data.results || [];
const distance = results.map(r => Number(r.distance || 0));
resolve({
success: true,
data: distance
});
} catch (error) {
console.error('高德距离计算失败:', error);
reject({ success: false, message: error.message || '计算距离失败' });
}
};
if (from && typeof from === 'object') {
doRequest(from);
} else {
// 未传入起点时,使用用户当前定位
getUserLocation().then(loc => {
doRequest({
longitude: loc.longitude,
latitude: loc.latitude
});
}).catch(err => {
reject({ success: false, message: err.message || '获取起点位置失败' });
});
}
return;
}
const qqmap = getQQMapInstance();
if (!qqmap) {
reject({ success: false, message: '腾讯地图SDK未初始化' });
@@ -685,6 +1249,37 @@ function calculateDistanceSync(lat1, lng1, lat2, lng2) {
// 关键词提示
function getSuggestion(keyword, region = '全国') {
return new Promise((resolve, reject) => {
// 支付宝端使用高德输入提示
if (isAlipayEnv) {
requestAmap('/assistant/inputtips', {
keywords: keyword,
city: region === '全国' ? undefined : region,
datatype: 'all'
}).then((data) => {
const tips = data.tips || [];
const normalized = tips.map((item, idx) => ({
id: item.id || idx + 1,
title: item.name || '',
adcode: item.adcode || '',
address: item.address || '',
category: item.type || '',
city: item.city || '',
district: item.district || '',
latitude: item.location ? Number(item.location.split(',')[1]) : null,
longitude: item.location ? Number(item.location.split(',')[0]) : null,
province: item.province || ''
}));
resolve({
success: true,
data: normalized
});
}).catch((error) => {
console.error('高德关键词提示失败:', error);
reject({ success: false, message: error.message || '关键词提示失败' });
});
return;
}
const qqmap = getQQMapInstance();
if (!qqmap) {
reject({ success: false, message: '腾讯地图SDK未初始化' });
+12 -3
View File
@@ -248,8 +248,10 @@ class OrderMonitor {
// 导出单例实例
export const orderMonitor = new OrderMonitor()
// 监听页面切换事件
uni.onAppRoute((route) => {
// 监听页面切换事件(仅微信小程序支持)
// #ifdef MP-WEIXIN
if (typeof uni.onAppRoute === 'function') {
uni.onAppRoute((route) => {
const pagePath = route.path || ''
const pageSegments = pagePath.split('/')
const pageName = pageSegments[pageSegments.length - 1]
@@ -258,7 +260,14 @@ uni.onAppRoute((route) => {
orderMonitor.setActivePage(pageName || null)
console.log('页面切换:', pagePath, '当前活跃页面:', pageName)
})
})
}
// #endif
// #ifdef MP-ALIPAY
// 支付宝小程序不支持 onAppRoute,通过页面生命周期手动设置
// 各个页面需要在 onShow 中调用 orderMonitor.setActivePage()
// #endif
// 页面加载时自动恢复监控上次的活跃订单(如果有)
const initOrderMonitor = () => {