支付宝兼容
This commit is contained in:
+73
-23
@@ -3,40 +3,88 @@
|
|||||||
<!-- 地图容器 -->
|
<!-- 地图容器 -->
|
||||||
<view class="map-wrapper">
|
<view class="map-wrapper">
|
||||||
<!-- 使用小程序原生地图组件 -->
|
<!-- 使用小程序原生地图组件 -->
|
||||||
<map id="map" class="native-map" :longitude="mapCenter.longitude" :latitude="mapCenter.latitude"
|
<map
|
||||||
:markers="mapMarkers" :scale="mapZoom" :show-location="false" @regionchange="onMapRegionChange"
|
id="map"
|
||||||
@markertap="onMapMarkerTap" @callouttap="onCalloutTap" @updated="onMapUpdated" @error="onMapError">
|
class="native-map"
|
||||||
<!-- 覆盖在地图上的广告轮播(使用 cover-view 以兼容小程序原生组件层级) -->
|
:longitude="mapCenter.longitude"
|
||||||
<cover-view class="index-swiper" v-if="!props.hideControls && !props.hideMapOverlays && currentBannerImage">
|
:latitude="mapCenter.latitude"
|
||||||
<cover-image :src="currentBannerImage" class="index-swiper-img" mode="aspectFill" @tap="handleBannerTap"></cover-image>
|
:markers="mapMarkers"
|
||||||
<!-- 轮播指示器 -->
|
:scale="mapZoom"
|
||||||
<cover-view class="banner-indicators" v-if="props.bannerImages.length > 1">
|
:show-location="false"
|
||||||
<cover-view
|
@regionchange="onMapRegionChange"
|
||||||
v-for="(img, idx) in props.bannerImages"
|
@markertap="onMapMarkerTap"
|
||||||
:key="idx"
|
@callouttap="onCalloutTap"
|
||||||
class="indicator-dot"
|
@updated="onMapUpdated"
|
||||||
:class="{ active: idx === currentBannerIndex }">
|
@error="onMapError"
|
||||||
|
>
|
||||||
|
<!-- 覆盖在地图上的广告轮播(使用 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>
|
||||||
|
<!-- 轮播指示器 -->
|
||||||
|
<cover-view
|
||||||
|
class="banner-indicators"
|
||||||
|
v-if="props.bannerImages.length > 1"
|
||||||
|
>
|
||||||
|
<cover-view
|
||||||
|
v-for="(img, idx) in props.bannerImages"
|
||||||
|
:key="idx"
|
||||||
|
class="indicator-dot"
|
||||||
|
:class="{ active: idx === currentBannerIndex }"
|
||||||
|
>
|
||||||
|
</cover-view>
|
||||||
</cover-view>
|
</cover-view>
|
||||||
</cover-view>
|
</cover-view>
|
||||||
</cover-view>
|
|
||||||
|
|
||||||
<!-- 地图中心固定定位图标 -->
|
<!-- 地图中心固定定位图标 -->
|
||||||
<cover-view class="center-location-marker" v-if="!props.hideMapOverlays">
|
<cover-view
|
||||||
<cover-image src="/static/location-icon.png" class="center-marker-icon"></cover-image>
|
class="center-location-marker"
|
||||||
|
v-if="!props.hideMapOverlays"
|
||||||
|
>
|
||||||
|
<cover-image
|
||||||
|
src="/static/location-icon.png"
|
||||||
|
class="center-marker-icon"
|
||||||
|
></cover-image>
|
||||||
</cover-view>
|
</cover-view>
|
||||||
|
|
||||||
|
|
||||||
<cover-view class="map-side-controls" v-if="!props.hideControls && !props.hideMapOverlays">
|
<!-- 侧边控制按钮 -->
|
||||||
|
<cover-view
|
||||||
|
class="map-side-controls"
|
||||||
|
v-if="!props.hideControls && !props.hideMapOverlays"
|
||||||
|
>
|
||||||
<cover-view class="side-btn guide" @tap="handleGuide">
|
<cover-view class="side-btn guide" @tap="handleGuide">
|
||||||
<cover-image class="side-icon" src="/static/use_help.png" style="border-radius: 50%;"></cover-image>
|
<cover-image
|
||||||
|
class="side-icon"
|
||||||
|
src="/static/use_help.png"
|
||||||
|
style="border-radius: 50%;"
|
||||||
|
></cover-image>
|
||||||
</cover-view>
|
</cover-view>
|
||||||
<cover-view class="side-btn locate" @tap="handleRelocate">
|
<cover-view class="side-btn locate" @tap="handleRelocate">
|
||||||
<cover-image class="side-icon" src="/static/location.png"></cover-image>
|
<cover-image
|
||||||
|
class="side-icon"
|
||||||
|
src="/static/location.png"
|
||||||
|
></cover-image>
|
||||||
</cover-view>
|
</cover-view>
|
||||||
<cover-view class="side-btn search" @tap="handleSearch">
|
<cover-view class="side-btn search" @tap="handleSearch">
|
||||||
<cover-image class="side-icon" src="/static/other_device.png"></cover-image>
|
<cover-image
|
||||||
|
class="side-icon"
|
||||||
|
src="/static/other_device.png"
|
||||||
|
></cover-image>
|
||||||
</cover-view>
|
</cover-view>
|
||||||
<cover-view class="side-btn service" @tap="handleService">
|
<cover-view class="side-btn service" @tap="handleService">
|
||||||
<cover-image class="side-icon" src="/static/customer-service.png"></cover-image>
|
<cover-image
|
||||||
|
class="side-icon"
|
||||||
|
src="/static/customer-service.png"
|
||||||
|
></cover-image>
|
||||||
</cover-view>
|
</cover-view>
|
||||||
</cover-view>
|
</cover-view>
|
||||||
</map>
|
</map>
|
||||||
@@ -508,15 +556,16 @@ const handleSearch = () => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
height:78vh;
|
height: 78vh;
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
height: 72vh;
|
height: 72vh;
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
|
|
||||||
&.full-width {
|
&.full-width {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -682,6 +731,7 @@ const handleSearch = () => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8rpx;
|
gap: 8rpx;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
.indicator-dot {
|
.indicator-dot {
|
||||||
width: 12rpx;
|
width: 12rpx;
|
||||||
|
|||||||
@@ -0,0 +1,687 @@
|
|||||||
|
<template>
|
||||||
|
<view class="map-container" :class="{ 'full-width': props.fullWidth }">
|
||||||
|
<!-- 地图容器 -->
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<!-- 支付宝小程序:所有 cover-image 和 cover-view 必须放在 map 标签外部 -->
|
||||||
|
<!-- 广告轮播(直接使用 cover-image,不能嵌套在 cover-view 中) -->
|
||||||
|
<cover-image
|
||||||
|
v-if="!props.hideControls && !props.hideMapOverlays && currentBannerImage"
|
||||||
|
:src="currentBannerImage"
|
||||||
|
class="index-swiper-img-alipay"
|
||||||
|
mode="aspectFill"
|
||||||
|
@tap="handleBannerTap"
|
||||||
|
></cover-image>
|
||||||
|
|
||||||
|
<!-- 轮播指示器(每个点都是独立的 cover-view,避免嵌套) -->
|
||||||
|
<template v-if="!props.hideControls && !props.hideMapOverlays && props.bannerImages.length > 1">
|
||||||
|
<cover-view
|
||||||
|
v-for="(img, idx) in props.bannerImages"
|
||||||
|
:key="idx"
|
||||||
|
class="indicator-dot-alipay"
|
||||||
|
:class="{ active: idx === currentBannerIndex }"
|
||||||
|
:style="{
|
||||||
|
left: `calc(50% + ${(idx - (props.bannerImages.length - 1) / 2) * 20}rpx)`,
|
||||||
|
transform: 'translateX(-50%)'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
</cover-view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 地图中心固定定位图标(使用 cover-view 嵌套 cover-image) -->
|
||||||
|
<cover-view
|
||||||
|
class="center-location-marker-alipay"
|
||||||
|
v-if="!props.hideMapOverlays"
|
||||||
|
>
|
||||||
|
<cover-image
|
||||||
|
src="/static/location-icon.png"
|
||||||
|
class="center-marker-icon-alipay"
|
||||||
|
></cover-image>
|
||||||
|
</cover-view>
|
||||||
|
|
||||||
|
<!-- 侧边控制按钮(使用 cover-view 嵌套 cover-image) -->
|
||||||
|
<cover-view
|
||||||
|
class="map-side-controls-alipay"
|
||||||
|
v-if="!props.hideControls && !props.hideMapOverlays"
|
||||||
|
>
|
||||||
|
<cover-view class="side-btn-alipay guide-alipay" @tap="handleGuide">
|
||||||
|
<cover-image
|
||||||
|
class="side-icon-alipay"
|
||||||
|
src="/static/use_help.png"
|
||||||
|
style="border-radius: 50%;"
|
||||||
|
></cover-image>
|
||||||
|
</cover-view>
|
||||||
|
<cover-view class="side-btn-alipay locate-alipay" @tap="handleRelocate">
|
||||||
|
<cover-image
|
||||||
|
class="side-icon-alipay"
|
||||||
|
src="/static/location.png"
|
||||||
|
></cover-image>
|
||||||
|
</cover-view>
|
||||||
|
<cover-view class="side-btn-alipay search-alipay" @tap="handleSearch">
|
||||||
|
<cover-image
|
||||||
|
class="side-icon-alipay"
|
||||||
|
src="/static/other_device.png"
|
||||||
|
></cover-image>
|
||||||
|
</cover-view>
|
||||||
|
<cover-view class="side-btn-alipay service-alipay" @tap="handleService">
|
||||||
|
<cover-image
|
||||||
|
class="side-icon-alipay"
|
||||||
|
src="/static/customer-service.png"
|
||||||
|
></cover-image>
|
||||||
|
</cover-view>
|
||||||
|
</cover-view>
|
||||||
|
|
||||||
|
<!-- 地图加载状态 -->
|
||||||
|
<view class="map-loading" v-if="isLoading">
|
||||||
|
<view class="loading-content">
|
||||||
|
<view class="loading-spinner"></view>
|
||||||
|
<text>{{ $t('common.loadingMap') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
computed,
|
||||||
|
watch,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
nextTick,
|
||||||
|
getCurrentInstance
|
||||||
|
} from 'vue'
|
||||||
|
// 导入地图工具函数
|
||||||
|
import {
|
||||||
|
calculateDistanceSync
|
||||||
|
} from '../utils/mapUtils.js'
|
||||||
|
// 导入国际化
|
||||||
|
import { useI18n } from '../utils/i18n.js'
|
||||||
|
|
||||||
|
// 获取 i18n 实例
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// 引用折叠面板组件的ref
|
||||||
|
const collapseRef = ref(null)
|
||||||
|
|
||||||
|
// Props
|
||||||
|
const props = defineProps({
|
||||||
|
userLocation: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
positionList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
filteredPositions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
searchKeyword: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
noticeText: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
enableMarkers: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
customHeight: {
|
||||||
|
type: String,
|
||||||
|
default: '' // 自定义高度,如 '48vh', '400rpx' 等
|
||||||
|
},
|
||||||
|
hideControls: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false // 是否隐藏侧边控制按钮
|
||||||
|
},
|
||||||
|
fullWidth: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false // 是否全宽显示(去掉 margin 和固定宽度)
|
||||||
|
},
|
||||||
|
hideMapOverlays: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false // 是否隐藏地图上的覆盖层元素(如中心定位图标、轮播图等)
|
||||||
|
},
|
||||||
|
bannerImages: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [] // 广告图片列表
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits([
|
||||||
|
'relocate',
|
||||||
|
'scan',
|
||||||
|
'showList',
|
||||||
|
'markerTap',
|
||||||
|
'mapCenterChange',
|
||||||
|
'bannerClick',
|
||||||
|
'guide'
|
||||||
|
])
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const isLoading = ref(true)
|
||||||
|
const mapCenter = ref({
|
||||||
|
longitude: 116.397128,
|
||||||
|
latitude: 39.916527
|
||||||
|
})
|
||||||
|
const mapZoom = ref(17)
|
||||||
|
const mapMarkers = ref([]) // 用于地图组件的markers
|
||||||
|
const mapContext = ref(null) // 地图上下文
|
||||||
|
const currentBannerIndex = ref(0) // 当前显示的广告索引
|
||||||
|
let bannerTimer = null // 广告轮播定时器
|
||||||
|
|
||||||
|
// 计算当前显示的广告图片
|
||||||
|
const currentBannerImage = computed(() => {
|
||||||
|
if (props.bannerImages && props.bannerImages.length > 0) {
|
||||||
|
return props.bannerImages[currentBannerIndex.value]
|
||||||
|
}
|
||||||
|
// 降级:如果没有广告,显示默认图片
|
||||||
|
return '/static/index_swiper.png'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证坐标有效性
|
||||||
|
const isValidCoordinate = (lat, lng) => {
|
||||||
|
const latitude = parseFloat(lat)
|
||||||
|
const longitude = parseFloat(lng)
|
||||||
|
return !isNaN(latitude) && !isNaN(longitude) &&
|
||||||
|
latitude >= -90 && latitude <= 90 &&
|
||||||
|
longitude >= -180 && longitude <= 180 &&
|
||||||
|
!(latitude === 0 && longitude === 0) // 排除 0,0 这种无效坐标
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防抖定时器
|
||||||
|
let regionChangeTimer = null
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
const updateMapMarkers = () => {
|
||||||
|
const markers = []
|
||||||
|
|
||||||
|
// 只添加周边场地位置点,中心定位图标改用固定的cover-view显示
|
||||||
|
if (props.enableMarkers && props.filteredPositions && props.filteredPositions.length > 0) {
|
||||||
|
props.filteredPositions.forEach((pos, index) => {
|
||||||
|
if (pos.longitude && pos.latitude && isValidCoordinate(pos.latitude, pos.longitude)) {
|
||||||
|
const lat = parseFloat(pos.latitude)
|
||||||
|
const lng = parseFloat(pos.longitude)
|
||||||
|
markers.push({
|
||||||
|
id: index + 1,
|
||||||
|
latitude: lat,
|
||||||
|
longitude: lng,
|
||||||
|
iconPath: '/static/markes_fdz.png',
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
callout: {
|
||||||
|
content: pos.name,
|
||||||
|
fontSize: 12,
|
||||||
|
borderRadius: 8,
|
||||||
|
bgColor: '#ffffff',
|
||||||
|
padding: 8,
|
||||||
|
display: 'BYCLICK'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
mapMarkers.value = markers
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动地图到指定位置(直接更新地图中心坐标)
|
||||||
|
const moveToLocation = (location) => {
|
||||||
|
if (!location || !location.longitude || !location.latitude) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCenter = {
|
||||||
|
longitude: Number(location.longitude),
|
||||||
|
latitude: Number(location.latitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接更新地图中心,触发地图组件的视图更新
|
||||||
|
mapCenter.value = newCenter
|
||||||
|
|
||||||
|
// 更新标记点
|
||||||
|
updateMapMarkers()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听用户位置变化
|
||||||
|
watch(() => props.userLocation, (newLocation, oldLocation) => {
|
||||||
|
if (newLocation && newLocation.longitude && newLocation.latitude) {
|
||||||
|
// 检查位置是否真的变化了(避免重复更新)
|
||||||
|
const isChanged = !oldLocation ||
|
||||||
|
oldLocation.longitude !== newLocation.longitude ||
|
||||||
|
oldLocation.latitude !== newLocation.latitude
|
||||||
|
|
||||||
|
if (isChanged) {
|
||||||
|
mapCenter.value = {
|
||||||
|
longitude: newLocation.longitude,
|
||||||
|
latitude: newLocation.latitude
|
||||||
|
}
|
||||||
|
updateMapMarkers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听位置列表变化
|
||||||
|
watch(() => props.filteredPositions, (newPositions) => {
|
||||||
|
updateMapMarkers()
|
||||||
|
}, {
|
||||||
|
deep: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听广告图片变化,启动或停止轮播
|
||||||
|
watch(() => props.bannerImages, (newImages, oldImages) => {
|
||||||
|
// 先停止旧的轮播
|
||||||
|
stopBannerRotation()
|
||||||
|
currentBannerIndex.value = 0
|
||||||
|
|
||||||
|
// 如果有多张图片,启动新的轮播
|
||||||
|
if (newImages && newImages.length > 1) {
|
||||||
|
// 使用 nextTick 确保 DOM 已更新
|
||||||
|
nextTick(() => {
|
||||||
|
startBannerRotation()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// 地图加载完成事件
|
||||||
|
const onMapUpdated = () => {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地图区域变化事件(带防抖优化)
|
||||||
|
const onMapRegionChange = (e) => {
|
||||||
|
|
||||||
|
// 只处理结束事件
|
||||||
|
if (!e || (e.type !== 'end' && e.type !== 'regionchange')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const causedBy = e.causedBy || e.detail?.causedBy
|
||||||
|
|
||||||
|
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(() => {
|
||||||
|
const newCenter = {
|
||||||
|
longitude: Number(centerLocation.longitude),
|
||||||
|
latitude: Number(centerLocation.latitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
mapCenter.value = newCenter;
|
||||||
|
|
||||||
|
|
||||||
|
// 触发父组件查询新位置的场地
|
||||||
|
emit('mapCenterChange', newCenter)
|
||||||
|
}, 500)
|
||||||
|
} else {
|
||||||
|
// 兜底方案:如果事件中没有centerLocation,才使用API获取
|
||||||
|
regionChangeTimer = setTimeout(() => {
|
||||||
|
if (mapContext.value) {
|
||||||
|
mapContext.value.getCenterLocation({
|
||||||
|
success: (res) => {
|
||||||
|
if (res && res.longitude && res.latitude) {
|
||||||
|
const newCenter = {
|
||||||
|
longitude: res.longitude,
|
||||||
|
latitude: res.latitude
|
||||||
|
}
|
||||||
|
mapCenter.value = newCenter
|
||||||
|
emit('mapCenterChange', newCenter)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('获取地图中心失败:', err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记点点击事件
|
||||||
|
const onMapMarkerTap = (e) => {
|
||||||
|
const markerId = e.detail?.markerId || e.markerId
|
||||||
|
|
||||||
|
// 查找对应的场地位置信息
|
||||||
|
if (props.filteredPositions && props.filteredPositions.length > 0) {
|
||||||
|
const position = props.filteredPositions[markerId - 1]
|
||||||
|
if (position) {
|
||||||
|
emit('markerTap', position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记点气泡点击事件
|
||||||
|
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 onMapError = (error) => {
|
||||||
|
console.error('地图加载失败:', error)
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRelocate = () => {
|
||||||
|
// 直接委托父级处理定位并移动地图,避免内部重复弹 loading
|
||||||
|
try {
|
||||||
|
emit('relocate')
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
try {
|
||||||
|
uni.navigateTo({ url: '/pages/search/index' })
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleService = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/subPackages/service/help/index'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGuide = () => {
|
||||||
|
emit('guide')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理广告点击
|
||||||
|
const handleBannerTap = () => {
|
||||||
|
// 触发父组件处理点击事件
|
||||||
|
emit('bannerClick', currentBannerIndex.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动广告轮播
|
||||||
|
const startBannerRotation = () => {
|
||||||
|
// 如果只有一张或没有图片,不需要轮播
|
||||||
|
if (!props.bannerImages || props.bannerImages.length <= 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除旧的定时器
|
||||||
|
stopBannerRotation()
|
||||||
|
|
||||||
|
// 每3秒切换一次
|
||||||
|
bannerTimer = setInterval(() => {
|
||||||
|
const nextIndex = (currentBannerIndex.value + 1) % props.bannerImages.length
|
||||||
|
currentBannerIndex.value = nextIndex
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止广告轮播
|
||||||
|
const stopBannerRotation = () => {
|
||||||
|
if (bannerTimer) {
|
||||||
|
clearInterval(bannerTimer)
|
||||||
|
bannerTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生命周期钩子
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始化地图上下文
|
||||||
|
nextTick(() => {
|
||||||
|
// 需要使用nextTick确保地图组件已经渲染
|
||||||
|
const inst = getCurrentInstance()
|
||||||
|
const vm = (inst && (inst.proxy || inst)) || undefined
|
||||||
|
try {
|
||||||
|
mapContext.value = uni.createMapContext('map', vm)
|
||||||
|
} catch (e) {
|
||||||
|
// 兼容:如果第二参不被支持,退回单参
|
||||||
|
mapContext.value = uni.createMapContext('map')
|
||||||
|
}
|
||||||
|
updateMapMarkers()
|
||||||
|
|
||||||
|
// 初始化折叠面板
|
||||||
|
if (collapseRef.value) {
|
||||||
|
collapseRef.value.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化广告轮播
|
||||||
|
if (props.bannerImages && props.bannerImages.length > 1) {
|
||||||
|
startBannerRotation()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 清理工作
|
||||||
|
if (regionChangeTimer) {
|
||||||
|
clearTimeout(regionChangeTimer)
|
||||||
|
regionChangeTimer = null
|
||||||
|
}
|
||||||
|
// 停止广告轮播
|
||||||
|
stopBannerRotation()
|
||||||
|
mapContext.value = null
|
||||||
|
})
|
||||||
|
|
||||||
|
// 暴露给父组件的方法
|
||||||
|
defineExpose({
|
||||||
|
mapCenter: computed(() => mapCenter.value),
|
||||||
|
moveToLocation,
|
||||||
|
updateMapMarkers,
|
||||||
|
initCollapse: () => {
|
||||||
|
if (collapseRef.value) {
|
||||||
|
collapseRef.value.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 地图容器 */
|
||||||
|
.map-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 94vw;
|
||||||
|
margin: 20rpx;
|
||||||
|
margin-bottom: 0; /* 底部不需要边距 */
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 72vh;
|
||||||
|
|
||||||
|
&.full-width {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: visible; /* 支付宝小程序:允许覆盖层显示在地图外部 */
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
.native-map {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-loading {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.loading-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
border: 8rpx solid #f3f3f3;
|
||||||
|
border-top: 8rpx solid #3498db;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 支付宝小程序专用样式 - 所有覆盖层元素相对于 map-wrapper 定位 */
|
||||||
|
/* 广告图片样式 */
|
||||||
|
.index-swiper-img-alipay {
|
||||||
|
position: absolute;
|
||||||
|
top: 20rpx;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 90vw;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
z-index: 100; /* 确保在地图之上 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 轮播指示器独立定位 */
|
||||||
|
.indicator-dot-alipay {
|
||||||
|
position: absolute;
|
||||||
|
top: 130rpx; /* 广告图片 top(20rpx) + height(120rpx) - 10rpx = 130rpx */
|
||||||
|
width: 12rpx;
|
||||||
|
height: 12rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 101; /* 确保在广告图片之上 */
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
width: 24rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 地图中心定位图标样式 */
|
||||||
|
.center-location-marker-alipay {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
z-index: 99; /* 确保在地图之上 */
|
||||||
|
width: 60rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
margin-left: -30rpx;
|
||||||
|
margin-top: -80rpx;
|
||||||
|
pointer-events: none;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
.center-marker-icon-alipay {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 侧边控制按钮容器 */
|
||||||
|
.map-side-controls-alipay {
|
||||||
|
position: absolute;
|
||||||
|
right: 20rpx;
|
||||||
|
bottom: 160rpx; /* 向上移动,避免被底部按钮遮挡 */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 300rpx;
|
||||||
|
margin: auto;
|
||||||
|
gap: 12rpx;
|
||||||
|
z-index: 102; /* 确保在最上层 */
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
.side-btn-alipay {
|
||||||
|
margin: auto;
|
||||||
|
background: rgba(255, 255, 255, 0.96);
|
||||||
|
border-radius: 24rpx;
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 13rpx;
|
||||||
|
border: 2rpx solid #e0e0e0;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-icon-alipay {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
z-index:1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -22,6 +22,12 @@
|
|||||||
<view class="payment-badge member" v-else-if="order.payWay == 'wx_member_pay'">
|
<view class="payment-badge member" v-else-if="order.payWay == 'wx_member_pay'">
|
||||||
<text class="badge-text">{{ $t('order.memberOrder') }}</text>
|
<text class="badge-text">{{ $t('order.memberOrder') }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="payment-badge member" v-else-if="order.payWay == 'ali_pay'">
|
||||||
|
<text class="badge-text">{{ $t('order.aliPay') }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="payment-badge member" v-else-if="order.payWay == 'antom_pay'">
|
||||||
|
<text class="badge-text">{{ $t('order.antomPay') }}</text>
|
||||||
|
</view>
|
||||||
<view class="payment-badge deposit" v-else>
|
<view class="payment-badge deposit" v-else>
|
||||||
<text class="badge-text">{{ $t('order.wxPay') }}</text>
|
<text class="badge-text">{{ $t('order.wxPay') }}</text>
|
||||||
<text class="divider">|</text>
|
<text class="divider">|</text>
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ export const getCouponsByPosition = (positionId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建优惠券支付订单
|
// 创建优惠券支付订单
|
||||||
export const createCouponPayment = (couponId) => {
|
export const createCouponPayment = (couponId, paymentPlatform) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/app/coupon/pay',
|
url: '/app/coupon/pay',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: {
|
data: {
|
||||||
couponId
|
couponId,
|
||||||
|
// 支付平台类型:WECHAT / ALIPAY / ANTOM(不传则后端默认 WECHAT)
|
||||||
|
...(paymentPlatform ? { paymentPlatform } : {})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import request from '../http'
|
import request from '../http'
|
||||||
|
|
||||||
// 创建会员卡支付订单
|
// 创建会员卡支付订单
|
||||||
export const createMemberCardPayment = (memberCardId) => {
|
export const createMemberCardPayment = (memberCardId, paymentPlatform) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/app/member/pay',
|
url: '/app/member/pay',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: {
|
data: {
|
||||||
memberCardId
|
memberCardId,
|
||||||
|
// 支付平台类型:WECHAT / ALIPAY / ANTOM(不传则后端默认 WECHAT)
|
||||||
|
...(paymentPlatform ? { paymentPlatform } : {})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,15 @@ export const createWxPayment = (orderNo) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建支付宝支付订单(租借押金 H5 支付)
|
||||||
|
// 对应文档《支付宝接口文档》:GET /app/ali-payment/create/{orderNo}
|
||||||
|
export const createAliPayment = (orderNo) => {
|
||||||
|
return request({
|
||||||
|
url: `/app/ali-payment/create/${orderNo}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 获取正在使用中的订单
|
// 获取正在使用中的订单
|
||||||
export const getInUseOrder = () => {
|
export const getInUseOrder = () => {
|
||||||
return request({
|
return request({
|
||||||
@@ -150,6 +159,15 @@ export const getWxPaymentStatus = (orderNo) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询支付宝支付状态
|
||||||
|
// 对应文档:GET /app/ali-payment/status/{orderNo}
|
||||||
|
export const getAliPaymentStatus = (orderNo) => {
|
||||||
|
return request({
|
||||||
|
url: `/app/ali-payment/status/${orderNo}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== Antom 支付相关接口 ====================
|
// ==================== Antom 支付相关接口 ====================
|
||||||
|
|
||||||
// 创建 Antom H5 支付订单
|
// 创建 Antom H5 支付订单
|
||||||
|
|||||||
@@ -33,14 +33,10 @@ export const getProductDetail = (id) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建商品支付订单
|
* 创建商品支付订单(多支付平台)
|
||||||
* @param {Object} data - 订单数据
|
* 对应《商品购买多支付平台方案》:
|
||||||
* @param {Array} data.items - 订单项列表 [{skuId, quantity}]
|
* paymentPlatform: WECHAT / ALIPAY / ANTOM
|
||||||
* @param {string} data.receiverName - 收件人姓名
|
* 其他字段见文档
|
||||||
* @param {string} data.receiverPhone - 收件人手机号
|
|
||||||
* @param {string} data.receiverAddress - 收件人详细地址
|
|
||||||
* @param {string} data.remark - 用户备注(可选)
|
|
||||||
* @returns {Promise} 微信支付参数
|
|
||||||
*/
|
*/
|
||||||
export const createProductOrder = (data) => {
|
export const createProductOrder = (data) => {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
+27
-1
@@ -1,7 +1,7 @@
|
|||||||
import request from '../http'
|
import request from '../http'
|
||||||
import { URL, appid } from '../url'
|
import { URL, appid } from '../url'
|
||||||
|
|
||||||
// 用户登录
|
// 旧登录接口(兼容保留,后端将逐步废弃)
|
||||||
export const login = (data) => {
|
export const login = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/app/user/login',
|
url: '/app/user/login',
|
||||||
@@ -10,6 +10,22 @@ export const login = (data) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 统一快捷登录接口 /app/user/quickLogin
|
||||||
|
// 对应文档《快捷登录最终方案》中的 QuickLoginDto:
|
||||||
|
// loginType: WECHAT / ALIPAY / SMS
|
||||||
|
// appid: 平台应用ID
|
||||||
|
// openId: 第三方 openId(微信必传)
|
||||||
|
// code: 授权码(微信手机号授权码 / 支付宝 authCode)
|
||||||
|
// phonenumber: 短信登录手机号
|
||||||
|
// smsCode: 短信验证码
|
||||||
|
export const quickLogin = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/app/user/quickLogin',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 发送验证码
|
// 发送验证码
|
||||||
export const sendVerifyCode = (phonenumber) => {
|
export const sendVerifyCode = (phonenumber) => {
|
||||||
return request({
|
return request({
|
||||||
@@ -144,3 +160,13 @@ export const getWxUserPhoneNumber = (data) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取支付宝用户手机号(复用同一后端接口,由后端按 appid / 参数结构区分平台)
|
||||||
|
// 期望后端返回:{ code:200, data:{ phoneNumber: 'xxx' } }
|
||||||
|
export const getAliUserPhoneNumber = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/app/user/alipay/getPhone',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
// 配置项:true 表示打印日志,false 表示不打印日志
|
// 配置项:true 表示打印日志,false 表示不打印日志
|
||||||
export const CONSOLE_CONFIG = {
|
export const CONSOLE_CONFIG = {
|
||||||
// 是否启用 console.log
|
// 是否启用 console.log
|
||||||
enableLog: false,
|
enableLog: true,
|
||||||
// 是否启用 console.warn
|
// 是否启用 console.warn
|
||||||
enableWarn: false,
|
enableWarn: false,
|
||||||
// 是否启用 console.error
|
// 是否启用 console.error
|
||||||
|
|||||||
+9
-2
@@ -1,8 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
URL,
|
URL,
|
||||||
appid
|
appid,
|
||||||
|
ZFBappid
|
||||||
} from './url'
|
} from './url'
|
||||||
|
|
||||||
|
// 根据运行平台选择正确的小程序 appid(后端通常依赖该 header 做平台识别)
|
||||||
|
let platformAppid = appid
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
platformAppid = ZFBappid
|
||||||
|
// #endif
|
||||||
|
|
||||||
// 获取多语言翻译文本
|
// 获取多语言翻译文本
|
||||||
const getLoadingText = () => {
|
const getLoadingText = () => {
|
||||||
try {
|
try {
|
||||||
@@ -31,7 +38,7 @@ const request = (option) => {
|
|||||||
header: {
|
header: {
|
||||||
"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'),
|
"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,
|
...option.headers,
|
||||||
'appid': appid,
|
'appid': platformAppid,
|
||||||
'Authorization': "Bearer " + uni.getStorageSync('token'),
|
'Authorization': "Bearer " + uni.getStorageSync('token'),
|
||||||
'Clientid': uni.getStorageSync('client_id'),
|
'Clientid': uni.getStorageSync('client_id'),
|
||||||
'Content-Language': (uni.getStorageSync('language') || 'zh-CN').replace(/-/g, '_')
|
'Content-Language': (uni.getStorageSync('language') || 'zh-CN').replace(/-/g, '_')
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
// export const URL = "https://my.gxfs123.com/api" //正式服务器-弃用
|
// export const URL = "https://my.gxfs123.com/api" //正式服务器-弃用
|
||||||
// export const URL = "https://manager.fdzpower.com/api" //正式服务器
|
// export const URL = "https://manager.fdzpower.com/api" //正式服务器
|
||||||
export const URL = "https://fansdev.gxfs123.com/api" //测试服务器
|
export const URL = "https://fansdev.gxfs123.com/api" //测试服务器
|
||||||
// export const URL = "http://192.168.5.123:8080" //本地调试
|
// export const URL = "http://192.168.5.64:8080" //本地调试
|
||||||
// export const URL = "http://127.0.0.1:8080" //本地调试
|
// export const URL = "http://127.0.0.1:8080" //本地调试
|
||||||
|
|
||||||
export const appid = "wx2165f0be356ae7a9" //微信小程序appid
|
export const appid = "wx2165f0be356ae7a9" //微信小程序appid
|
||||||
|
|||||||
@@ -211,6 +211,8 @@ export default {
|
|||||||
depositFree: 'Deposit-free',
|
depositFree: 'Deposit-free',
|
||||||
whitelistOrder: 'Whitelist Order',
|
whitelistOrder: 'Whitelist Order',
|
||||||
memberOrder: 'Member Order',
|
memberOrder: 'Member Order',
|
||||||
|
aliPay: 'AliPay',
|
||||||
|
antomPay: 'Antom Pay',
|
||||||
wxPay: 'WeChat Pay',
|
wxPay: 'WeChat Pay',
|
||||||
depositPay: 'Deposit Pay',
|
depositPay: 'Deposit Pay',
|
||||||
paymentInProgress: 'Payment in Progress',
|
paymentInProgress: 'Payment in Progress',
|
||||||
@@ -307,6 +309,8 @@ export default {
|
|||||||
cooperation: 'Partner',
|
cooperation: 'Partner',
|
||||||
settings: 'Settings',
|
settings: 'Settings',
|
||||||
userAgreement: 'Terms',
|
userAgreement: 'Terms',
|
||||||
|
settinguserAgreement:'Terms',
|
||||||
|
settinguserprivacyPolicy:'Privacy',
|
||||||
privacyPolicy: 'Privacy',
|
privacyPolicy: 'Privacy',
|
||||||
version: 'v',
|
version: 'v',
|
||||||
logout: 'Logout',
|
logout: 'Logout',
|
||||||
|
|||||||
@@ -211,6 +211,8 @@ export default {
|
|||||||
depositFree: 'Sewa Tanpa Deposit',
|
depositFree: 'Sewa Tanpa Deposit',
|
||||||
whitelistOrder: 'Pesanan Whitelist',
|
whitelistOrder: 'Pesanan Whitelist',
|
||||||
memberOrder: 'Pesanan Anggota',
|
memberOrder: 'Pesanan Anggota',
|
||||||
|
aliPay: 'Alipay',
|
||||||
|
antomPay: 'Antom Pay',
|
||||||
wxPay: 'Pembayaran WeChat',
|
wxPay: 'Pembayaran WeChat',
|
||||||
depositPay: 'Sewa dengan Deposit',
|
depositPay: 'Sewa dengan Deposit',
|
||||||
paymentInProgress: 'Sedang membayar',
|
paymentInProgress: 'Sedang membayar',
|
||||||
@@ -307,6 +309,8 @@ export default {
|
|||||||
cooperation: 'Kerja Sama dan Keanggotaan',
|
cooperation: 'Kerja Sama dan Keanggotaan',
|
||||||
settings: 'Pengaturan',
|
settings: 'Pengaturan',
|
||||||
userAgreement: '《Perjanjian Pengguna》',
|
userAgreement: '《Perjanjian Pengguna》',
|
||||||
|
settinguserAgreement:'Perjanjian Pengguna',
|
||||||
|
settinguserprivacyPolicy:'Kebijakan Privasi',
|
||||||
privacyPolicy: '《Kebijakan Privasi》',
|
privacyPolicy: '《Kebijakan Privasi》',
|
||||||
version: 'v',
|
version: 'v',
|
||||||
logout: 'Keluar',
|
logout: 'Keluar',
|
||||||
|
|||||||
+5
-1
@@ -84,7 +84,7 @@ export default {
|
|||||||
scanToUse: '扫码使用',
|
scanToUse: '扫码使用',
|
||||||
personalCenter: '个人中心',
|
personalCenter: '个人中心',
|
||||||
useGuide: '使用指南',
|
useGuide: '使用指南',
|
||||||
buyDevice: '充电宝定制',
|
buyDevice: '产品定制',
|
||||||
navigate: '导航',
|
navigate: '导航',
|
||||||
relocate: '重新定位',
|
relocate: '重新定位',
|
||||||
search: '搜索',
|
search: '搜索',
|
||||||
@@ -210,6 +210,8 @@ export default {
|
|||||||
depositFree: '免押租借',
|
depositFree: '免押租借',
|
||||||
whitelistOrder: '白名单订单',
|
whitelistOrder: '白名单订单',
|
||||||
memberOrder: '会员订单',
|
memberOrder: '会员订单',
|
||||||
|
aliPay: '支付宝支付',
|
||||||
|
antomPay: '海外支付',
|
||||||
wxPay: '微信支付',
|
wxPay: '微信支付',
|
||||||
depositPay: '押金租借',
|
depositPay: '押金租借',
|
||||||
paymentInProgress: '支付中',
|
paymentInProgress: '支付中',
|
||||||
@@ -306,7 +308,9 @@ export default {
|
|||||||
cooperation: '合作加盟',
|
cooperation: '合作加盟',
|
||||||
settings: '设置',
|
settings: '设置',
|
||||||
userAgreement: '《用户协议》',
|
userAgreement: '《用户协议》',
|
||||||
|
settinguserAgreement: '用户协议',
|
||||||
privacyPolicy: '《隐私政策》',
|
privacyPolicy: '《隐私政策》',
|
||||||
|
settinguserprivacyPolicy: '隐私政策',
|
||||||
version: 'v',
|
version: 'v',
|
||||||
logout: '退出登录',
|
logout: '退出登录',
|
||||||
confirmLogout: '确认退出登录?',
|
confirmLogout: '确认退出登录?',
|
||||||
|
|||||||
+3
-6
@@ -69,12 +69,9 @@
|
|||||||
"requiredPrivateInfos" : [ "getLocation", "chooseLocation" ]
|
"requiredPrivateInfos" : [ "getLocation", "chooseLocation" ]
|
||||||
},
|
},
|
||||||
"mp-alipay" : {
|
"mp-alipay" : {
|
||||||
"component2":true,
|
"component2" : true,
|
||||||
"transpile":[
|
"transpile" : [ "uview-ui", "vue-i18n" ],
|
||||||
"uview-ui",
|
"skia" : true,
|
||||||
"vue-i18n"
|
|
||||||
],
|
|
||||||
"skia":true,
|
|
||||||
"usingComponents" : true,
|
"usingComponents" : true,
|
||||||
"appid" : "2021006117693332",
|
"appid" : "2021006117693332",
|
||||||
"unipush" : {
|
"unipush" : {
|
||||||
|
|||||||
+36
-7
@@ -91,7 +91,7 @@
|
|||||||
<!-- 底部操作区 -->
|
<!-- 底部操作区 -->
|
||||||
<view class="footer">
|
<view class="footer">
|
||||||
<view class="rent-button" :class="{ 'return-button': hasActiveOrder }"
|
<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>
|
<text>{{ hasActiveOrder ? $t('order.returnDevice') : getRentButtonText() }}</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 微信支付分标识仅在微信小程序环境显示 -->
|
<!-- 微信支付分标识仅在微信小程序环境显示 -->
|
||||||
@@ -176,6 +176,8 @@
|
|||||||
const phoneNumber = ref('')
|
const phoneNumber = ref('')
|
||||||
const showPhoneAuthPopup = ref(false)
|
const showPhoneAuthPopup = ref(false)
|
||||||
const isWechatMiniProgram = ref(false)
|
const isWechatMiniProgram = ref(false)
|
||||||
|
const isAlipayMiniProgram = ref(false)
|
||||||
|
const isH5 = ref(false)
|
||||||
|
|
||||||
// 生命周期 onLoad 钩子
|
// 生命周期 onLoad 钩子
|
||||||
onLoad(async (options) => {
|
onLoad(async (options) => {
|
||||||
@@ -193,13 +195,27 @@
|
|||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('device.deviceInfo')
|
title: t('device.deviceInfo')
|
||||||
})
|
})
|
||||||
// 检测当前运行环境
|
// 检测当前运行环境:微信小程序 / 支付宝小程序 / H5
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
isWechatMiniProgram.value = true
|
isWechatMiniProgram.value = true
|
||||||
|
isAlipayMiniProgram.value = false
|
||||||
|
isH5.value = false
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
isWechatMiniProgram.value = false
|
||||||
|
isAlipayMiniProgram.value = true
|
||||||
|
isH5.value = false
|
||||||
// #endif
|
// #endif
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
isWechatMiniProgram.value = false
|
isWechatMiniProgram.value = false
|
||||||
|
isAlipayMiniProgram.value = false
|
||||||
|
isH5.value = true
|
||||||
// #endif
|
// #endif
|
||||||
|
console.log('当前运行环境:', {
|
||||||
|
isWechatMiniProgram: isWechatMiniProgram.value,
|
||||||
|
isAlipayMiniProgram: isAlipayMiniProgram.value,
|
||||||
|
isH5: isH5.value
|
||||||
|
})
|
||||||
await checkUserPhone()
|
await checkUserPhone()
|
||||||
await fetchDeviceInfo()
|
await fetchDeviceInfo()
|
||||||
})
|
})
|
||||||
@@ -437,7 +453,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理租借操作
|
// 处理租借操作
|
||||||
const handleRent = (payWay) => {
|
const handleRent = () => {
|
||||||
if (!isLoggedIn.value) {
|
if (!isLoggedIn.value) {
|
||||||
showLoginTip()
|
showLoginTip()
|
||||||
return
|
return
|
||||||
@@ -448,9 +464,22 @@
|
|||||||
showPhoneAuthPopup.value = true
|
showPhoneAuthPopup.value = true
|
||||||
return
|
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({
|
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') {
|
} else if (payWay == 'wx-score-pay') {
|
||||||
|
|||||||
+192
-13
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="container fullscreen">
|
<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' }">
|
<view class="navbar-content" :style="{ height: navBarHeight + 'px' }">
|
||||||
<text class="navbar-title">{{ $t('home.title') }}</text>
|
<text class="navbar-title">{{ $t('home.title') }}</text>
|
||||||
</view>
|
</view>
|
||||||
</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">
|
<view class="notice-wrapper" v-if="noticeText" @click="openNoticePopup">
|
||||||
<uv-notice-bar :text="noticeText" :speed="50" :show-icon="true" color="#07c160" bg-color="#E8F8EF"
|
<uv-notice-bar :text="noticeText" :speed="50" :show-icon="true" color="#07c160" bg-color="#E8F8EF"
|
||||||
@@ -17,14 +17,31 @@
|
|||||||
</view>
|
</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' }">
|
<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"
|
<MapComponent v-if="!isLoading && userLocation" ref="mapRef" :userLocation="userLocation"
|
||||||
:positionList="positionList" :filteredPositions="filteredPositions" :searchKeyword="searchKeyword"
|
:positionList="positionList" :filteredPositions="filteredPositions" :searchKeyword="searchKeyword"
|
||||||
:enableMarkers="true" :bannerImages="bannerImages"
|
:enableMarkers="true" :bannerImages="bannerImages"
|
||||||
:hideMapOverlays="showGuidePopup || showNoticePopup || showActivityPopup" @relocate="handleRelocate"
|
:hideMapOverlays="showGuidePopup || showNoticePopup || showActivityPopup" @relocate="handleRelocate"
|
||||||
@scan="handleScan" @showList="showLocationList" @markerTap="selectPosition"
|
@scan="handleScan" @showList="showLocationList" @markerTap="selectPosition"
|
||||||
@mapCenterChange="onMapCenterChange" @bannerClick="handleBannerClick" @guide="openGuidePopup" />
|
@mapCenterChange="onMapCenterChange" @bannerClick="handleBannerClick" @guide="openGuidePopup" />
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
<!-- 地图加载状态 -->
|
<!-- 地图加载状态 -->
|
||||||
<view v-if="isLoading || !userLocation" class="map-loading-placeholder">
|
<view v-if="isLoading || !userLocation" class="map-loading-placeholder">
|
||||||
@@ -79,7 +96,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view> -->
|
</view> -->
|
||||||
|
|
||||||
<!-- 手机号授权弹窗 -->
|
<!-- 手机号校验/授权弹窗(扫码前校验) -->
|
||||||
<view class="phone-auth-popup" v-if="showPhoneAuthPopup">
|
<view class="phone-auth-popup" v-if="showPhoneAuthPopup">
|
||||||
<view class="popup-mask" @click.stop="showPhoneAuthPopup = false"></view>
|
<view class="popup-mask" @click.stop="showPhoneAuthPopup = false"></view>
|
||||||
<view class="popup-content">
|
<view class="popup-content">
|
||||||
@@ -90,9 +107,25 @@
|
|||||||
<view class="auth-desc">
|
<view class="auth-desc">
|
||||||
<text>{{ $t('auth.authDesc') }}</text>
|
<text>{{ $t('auth.authDesc') }}</text>
|
||||||
</view>
|
</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>
|
<text>{{ $t('auth.getPhoneNumber') }}</text>
|
||||||
</button>
|
</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">
|
<view class="auth-cancel" @click="showPhoneAuthPopup = false">
|
||||||
<text>{{ $t('auth.notNow') }}</text>
|
<text>{{ $t('auth.notNow') }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -187,7 +220,10 @@
|
|||||||
} from 'vue'
|
} from 'vue'
|
||||||
import {
|
import {
|
||||||
getQueryString,
|
getQueryString,
|
||||||
wxLogin
|
wxLogin,
|
||||||
|
getUserInfo,
|
||||||
|
getUserPhoneNumber,
|
||||||
|
getAlipayUserPhoneNumber
|
||||||
} from '../../util/index.js'
|
} from '../../util/index.js'
|
||||||
import {
|
import {
|
||||||
URL
|
URL
|
||||||
@@ -219,6 +255,9 @@
|
|||||||
// 同样需要使用相对路径引入组件
|
// 同样需要使用相对路径引入组件
|
||||||
// 注意:从 pages/index/ 目录访问 components/ 需要使用 ../../components/ 路径
|
// 注意:从 pages/index/ 目录访问 components/ 需要使用 ../../components/ 路径
|
||||||
import MapComponent from '../../components/MapComponent.vue'
|
import MapComponent from '../../components/MapComponent.vue'
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
import MapComponentAlipay from '../../components/MapComponentAlipay.vue'
|
||||||
|
// #endif
|
||||||
import LocationListSheet from '../../components/LocationListSheet.vue'
|
import LocationListSheet from '../../components/LocationListSheet.vue'
|
||||||
import {
|
import {
|
||||||
useI18n
|
useI18n
|
||||||
@@ -244,6 +283,8 @@
|
|||||||
const isExpanded = ref(false)
|
const isExpanded = ref(false)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const showPhoneAuthPopup = ref(false)
|
const showPhoneAuthPopup = ref(false)
|
||||||
|
const pendingScan = ref(false) // 是否是扫码流程触发的手机号校验
|
||||||
|
const expectedUserPhone = ref('') // 当前登录态下绑定的手机号(用于支付宝一致性校验)
|
||||||
const isLocationInitialized = ref(false)
|
const isLocationInitialized = ref(false)
|
||||||
const showLocationPopup = ref(false)
|
const showLocationPopup = ref(false)
|
||||||
const isRelocating = ref(false) // 防抖标志:是否正在重新定位
|
const isRelocating = ref(false) // 防抖标志:是否正在重新定位
|
||||||
@@ -259,6 +300,30 @@
|
|||||||
const navBarHeight = ref(44) // 默认导航栏内容高度
|
const navBarHeight = ref(44) // 默认导航栏内容高度
|
||||||
const noticeHeight = ref(0) // 通知栏高度
|
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([{
|
const guideSteps = ref([{
|
||||||
title: '扫码使用',
|
title: '扫码使用',
|
||||||
@@ -474,11 +539,13 @@
|
|||||||
|
|
||||||
// 距离格式化函数
|
// 距离格式化函数
|
||||||
const formatDistance = (distanceInMeters) => {
|
const formatDistance = (distanceInMeters) => {
|
||||||
if (distanceInMeters < 1000) {
|
// 支付宝小程序等环境下,可能传入 String/BigInt/异常对象,导致 toFixed 不存在
|
||||||
return `${Math.round(distanceInMeters)}m`
|
let meters = distanceInMeters
|
||||||
} else {
|
if (typeof meters === 'bigint') meters = Number(meters)
|
||||||
return `${(distanceInMeters / 1000).toFixed(1)}km`
|
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
|
// #ifdef H5
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/scan/index'
|
url: '/pages/scan/index'
|
||||||
@@ -987,6 +1105,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleScan = async () => {
|
||||||
|
const ok = await precheckBeforeScan()
|
||||||
|
if (!ok) return
|
||||||
|
await doScan()
|
||||||
|
}
|
||||||
|
|
||||||
const processScanResult = async (scanResult) => {
|
const processScanResult = async (scanResult) => {
|
||||||
try {
|
try {
|
||||||
console.log('===== 处理扫码结果 =====');
|
console.log('===== 处理扫码结果 =====');
|
||||||
@@ -1162,12 +1286,67 @@
|
|||||||
showLocationPopup.value = false
|
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
|
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 = () => {
|
const openPopup = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
|
|||||||
@@ -134,8 +134,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formatDistance = (meters) => {
|
const formatDistance = (meters) => {
|
||||||
if (meters < 1000) return `${Math.round(meters)}m`
|
// 兼容支付宝小程序等环境:保证始终对 Number 调用 toFixed
|
||||||
return `${(meters / 1000).toFixed(1)}km`
|
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) => {
|
const setTab = (name) => {
|
||||||
|
|||||||
@@ -176,6 +176,8 @@
|
|||||||
|
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="form-label">收货地区</text>
|
<text class="form-label">收货地区</text>
|
||||||
|
<!-- 非支付宝小程序:使用多列 picker -->
|
||||||
|
<!-- #ifndef MP-ALIPAY -->
|
||||||
<picker mode="multiSelector" :range="regionColumns" range-key="name" :value="regionIndexes"
|
<picker mode="multiSelector" :range="regionColumns" range-key="name" :value="regionIndexes"
|
||||||
@change="onRegionChange" @columnchange="onRegionColumnChange">
|
@change="onRegionChange" @columnchange="onRegionColumnChange">
|
||||||
<view class="form-input region-selector">
|
<view class="form-input region-selector">
|
||||||
@@ -187,6 +189,19 @@
|
|||||||
<text class="arrow-icon">›</text>
|
<text class="arrow-icon">›</text>
|
||||||
</view>
|
</view>
|
||||||
</picker>
|
</picker>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- 支付宝小程序:使用自定义弹窗 + picker-view -->
|
||||||
|
<!-- #ifdef MP-ALIPAY -->
|
||||||
|
<view class="form-input region-selector" @click="showRegionPicker = true">
|
||||||
|
<text v-if="addressForm.province && addressForm.city && addressForm.district"
|
||||||
|
class="region-text">
|
||||||
|
{{ addressForm.province }} {{ addressForm.city }} {{ addressForm.district }}
|
||||||
|
</text>
|
||||||
|
<text v-else class="input-placeholder">请选择省市区</text>
|
||||||
|
<text class="arrow-icon">›</text>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
@@ -254,6 +269,49 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 支付宝小程序地区选择弹窗 -->
|
||||||
|
<!-- #ifdef MP-ALIPAY -->
|
||||||
|
<view class="popup-mask" v-if="showRegionPicker" @click="closeRegionPicker">
|
||||||
|
<view class="popup-container" @click.stop>
|
||||||
|
<view class="address-popup">
|
||||||
|
<view class="popup-header">
|
||||||
|
<text class="popup-title">选择省市区</text>
|
||||||
|
<view class="close-btn" @click="closeRegionPicker">
|
||||||
|
<text class="close-icon">✕</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-section">
|
||||||
|
<picker-view :value="regionIndexes" @change="onAliRegionChange"
|
||||||
|
indicator-style="height: 50px;">
|
||||||
|
<picker-view-column>
|
||||||
|
<view v-for="item in regionColumns[0]" :key="item.code">
|
||||||
|
{{ item.name }}
|
||||||
|
</view>
|
||||||
|
</picker-view-column>
|
||||||
|
<picker-view-column>
|
||||||
|
<view v-for="item in regionColumns[1]" :key="item.code">
|
||||||
|
{{ item.name }}
|
||||||
|
</view>
|
||||||
|
</picker-view-column>
|
||||||
|
<picker-view-column>
|
||||||
|
<view v-for="item in regionColumns[2]" :key="item.code">
|
||||||
|
{{ item.name }}
|
||||||
|
</view>
|
||||||
|
</picker-view-column>
|
||||||
|
</picker-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="popup-footer">
|
||||||
|
<view class="confirm-btn" @click="confirmAliRegion">
|
||||||
|
<text>确定</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -340,6 +398,7 @@
|
|||||||
const showSkuPopup = ref(false)
|
const showSkuPopup = ref(false)
|
||||||
const showAddressPopup = ref(false)
|
const showAddressPopup = ref(false)
|
||||||
const showAddressDisplay = ref(false) // 地址展示弹窗
|
const showAddressDisplay = ref(false) // 地址展示弹窗
|
||||||
|
const showRegionPicker = ref(false) // 支付宝地区选择弹窗
|
||||||
|
|
||||||
// 计算是否已有地址
|
// 计算是否已有地址
|
||||||
const hasAddress = computed(() => {
|
const hasAddress = computed(() => {
|
||||||
@@ -489,6 +548,45 @@
|
|||||||
addressForm.value.districtCode = district.code
|
addressForm.value.districtCode = district.code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 支付宝小程序 picker-view 列变化
|
||||||
|
const onAliRegionChange = (e) => {
|
||||||
|
const newVal = e.detail.value || []
|
||||||
|
const oldVal = regionIndexes.value || []
|
||||||
|
|
||||||
|
// 找出发生变化的列
|
||||||
|
let column = -1
|
||||||
|
for (let i = 0; i < newVal.length; i++) {
|
||||||
|
if (newVal[i] !== oldVal[i]) {
|
||||||
|
column = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column !== -1) {
|
||||||
|
onRegionColumnChange({
|
||||||
|
detail: {
|
||||||
|
column,
|
||||||
|
value: newVal[column]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭支付宝地区弹窗
|
||||||
|
const closeRegionPicker = () => {
|
||||||
|
showRegionPicker.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支付宝地区选择“确定”
|
||||||
|
const confirmAliRegion = () => {
|
||||||
|
onRegionChange({
|
||||||
|
detail: {
|
||||||
|
value: regionIndexes.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
closeRegionPicker()
|
||||||
|
}
|
||||||
|
|
||||||
// 获取用户收货地址
|
// 获取用户收货地址
|
||||||
const fetchUserAddress = async () => {
|
const fetchUserAddress = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -767,7 +865,7 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建订单并支付
|
// 创建订单并支付(接入商品多支付平台方案:微信 / 支付宝,小程序端)
|
||||||
const createOrder = async () => {
|
const createOrder = async () => {
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
@@ -782,13 +880,24 @@
|
|||||||
savedAddress.value.receiverAddress :
|
savedAddress.value.receiverAddress :
|
||||||
`${addressForm.value.province}${addressForm.value.city}${addressForm.value.district}${addressForm.value.receiverAddress}`
|
`${addressForm.value.province}${addressForm.value.city}${addressForm.value.district}${addressForm.value.receiverAddress}`
|
||||||
|
|
||||||
|
// 根据当前运行环境确定支付平台
|
||||||
|
let paymentPlatform = 'WECHAT' // 默认微信
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
paymentPlatform = 'ALIPAY'
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
// H5 预留 Antom 支付,这里暂时仍按微信处理,如需接入可改为 ANTOM 并补充 paymentType / osType
|
||||||
|
paymentPlatform = 'WECHAT'
|
||||||
|
// #endif
|
||||||
|
|
||||||
const orderData = {
|
const orderData = {
|
||||||
skuId: selectedSku.value.skuId,
|
skuId: selectedSku.value.skuId,
|
||||||
quantity:quantity.value,
|
quantity: quantity.value,
|
||||||
receiverName: addressData.receiverName,
|
receiverName: addressData.receiverName,
|
||||||
receiverPhone: addressData.receiverPhone,
|
receiverPhone: addressData.receiverPhone,
|
||||||
receiverAddress: fullAddress, // 传递完整地址(省市区+详细地址)
|
receiverAddress: fullAddress, // 传递完整地址(省市区+详细地址)
|
||||||
remark: addressForm.value.remark || ''
|
remark: addressForm.value.remark || '',
|
||||||
|
paymentPlatform // WECHAT / ALIPAY /(预留)ANTOM
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('创建订单数据:', orderData)
|
console.log('创建订单数据:', orderData)
|
||||||
@@ -800,11 +909,12 @@
|
|||||||
if (res && res.code === 200 && res.data) {
|
if (res && res.code === 200 && res.data) {
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
|
|
||||||
// 调用微信支付
|
// 统一获取平台订单号(商品统一支付订单号)
|
||||||
|
const outOrderNo = res.data.OutOrderNo || res.data.outOrderNo
|
||||||
|
|
||||||
|
// ====================== 微信小程序支付 ======================
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
const payParams = res.data
|
const payParams = res.data
|
||||||
// 保存订单ID,用于取消订单
|
|
||||||
const orderId = payParams.OutOrderNo || res.data.OutOrderNo
|
|
||||||
|
|
||||||
uni.requestPayment({
|
uni.requestPayment({
|
||||||
timeStamp: payParams.timeStamp,
|
timeStamp: payParams.timeStamp,
|
||||||
nonceStr: payParams.nonceStr,
|
nonceStr: payParams.nonceStr,
|
||||||
@@ -831,26 +941,18 @@
|
|||||||
},
|
},
|
||||||
fail: async (payErr) => {
|
fail: async (payErr) => {
|
||||||
console.error('支付失败:', payErr)
|
console.error('支付失败:', payErr)
|
||||||
|
|
||||||
// 判断是用户取消还是支付失败
|
// 判断是用户取消还是支付失败
|
||||||
if (payErr.errMsg && payErr.errMsg.includes('cancel')) {
|
if (payErr.errMsg && payErr.errMsg.includes('cancel')) {
|
||||||
// 用户取消支付,调用取消订单接口
|
// 用户取消支付,这里预留调用取消订单接口
|
||||||
try {
|
try {
|
||||||
// uni.showLoading({
|
// await cancelProductOrder(outOrderNo)
|
||||||
// title: '正在取消订单...',
|
|
||||||
// mask: true
|
|
||||||
// })
|
|
||||||
|
|
||||||
// await cancelProductOrder(orderId)
|
|
||||||
|
|
||||||
uni.hideLoading()
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '支付已取消',
|
title: '支付已取消',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
} catch (cancelError) {
|
} catch (cancelError) {
|
||||||
console.error('取消订单失败:', cancelError)
|
console.error('取消订单失败:', cancelError)
|
||||||
uni.hideLoading()
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '支付已取消',
|
title: '支付已取消',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
@@ -865,6 +967,62 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// ====================== 支付宝小程序支付 ======================
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
console.log(res.data,'支付宝支付参数');
|
||||||
|
|
||||||
|
const tradeNO = res.data.tradeNo
|
||||||
|
if (!tradeNO) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '未获取到支付宝支付参数',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
my.tradePay({
|
||||||
|
tradeNO,
|
||||||
|
success: (payRes) => {
|
||||||
|
console.log('支付宝支付结果:', payRes)
|
||||||
|
if (payRes.resultCode === '9000') {
|
||||||
|
uni.showToast({
|
||||||
|
title: '支付成功',
|
||||||
|
icon: 'success',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
|
||||||
|
resetForm()
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.switchTab({
|
||||||
|
url: '/subPackages/business/device-orderList'
|
||||||
|
})
|
||||||
|
}, 2000)
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: '支付失败,请重试',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
uni.showToast({
|
||||||
|
title: '支付失败,请重试',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// ====================== H5 环境(预留 Antom 支付) ======================
|
||||||
|
// #ifdef H5
|
||||||
|
uni.showToast({
|
||||||
|
title: '当前环境暂不支持购买,请使用微信或支付宝小程序',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
} else {
|
} else {
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
|
|||||||
@@ -335,25 +335,40 @@
|
|||||||
navigateToOrderDetail(order);
|
navigateToOrderDetail(order);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 立即支付
|
// 立即支付(对齐 device-goods.vue,多平台支付)
|
||||||
const handlePayment = async (order) => {
|
const handlePayment = async (order) => {
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: '正在创建支付...',
|
title: '正在创建支付...',
|
||||||
mask: true
|
mask: true
|
||||||
});
|
});
|
||||||
console.log(order);
|
console.log('订单列表立即支付,订单信息:', order);
|
||||||
|
|
||||||
// 调用后端创建微信支付订单接口(使用订单号)
|
// 根据当前运行环境确定支付平台(与 device-goods.vue 保持一致)
|
||||||
// const res = await createWxPayment(order.orderNo);
|
let paymentPlatform = 'WECHAT'; // 默认微信
|
||||||
const res = await createProductOrder({orderNo:order.orderNo});
|
// #ifdef MP-ALIPAY
|
||||||
|
paymentPlatform = 'ALIPAY';
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
// H5 预留 Antom 支付,这里暂时仍按微信处理,如需接入可改为 ANTOM 并补充 paymentType / osType
|
||||||
|
paymentPlatform = 'WECHAT';
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// 使用商品多支付平台统一下单接口,对已有订单进行支付
|
||||||
|
const res = await createProductOrder({
|
||||||
|
orderNo: order.orderNo,
|
||||||
|
paymentPlatform
|
||||||
|
});
|
||||||
|
|
||||||
if (res && res.code === 200 && res.data) {
|
if (res && res.code === 200 && res.data) {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
|
|
||||||
|
// 统一获取平台订单号(商品统一支付订单号)
|
||||||
|
const outOrderNo = res.data.OutOrderNo || res.data.outOrderNo;
|
||||||
|
|
||||||
|
// ====================== 微信小程序支付 ======================
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
const payParams = res.data;
|
const payParams = res.data;
|
||||||
|
|
||||||
// 调用微信支付
|
|
||||||
uni.requestPayment({
|
uni.requestPayment({
|
||||||
timeStamp: payParams.timeStamp,
|
timeStamp: payParams.timeStamp,
|
||||||
nonceStr: payParams.nonceStr,
|
nonceStr: payParams.nonceStr,
|
||||||
@@ -367,25 +382,25 @@
|
|||||||
icon: 'success',
|
icon: 'success',
|
||||||
duration: 2000
|
duration: 2000
|
||||||
});
|
});
|
||||||
|
|
||||||
// 刷新订单列表
|
// 刷新订单列表
|
||||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
const statusArray = orderStatusTabs[currentTab.value].status;
|
||||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
||||||
await loadOrderList(statusValue);
|
await loadOrderList(statusValue);
|
||||||
},
|
},
|
||||||
fail: async (err) => {
|
fail: async (payErr) => {
|
||||||
console.error('支付失败:', err);
|
console.error('支付失败:', payErr);
|
||||||
|
|
||||||
// 判断是用户取消还是支付失败
|
// 判断是用户取消还是支付失败
|
||||||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
if (payErr.errMsg && payErr.errMsg.includes('cancel')) {
|
||||||
// 用户取消支付,调用取消订单接口
|
// 用户取消支付,这里预留调用取消订单接口
|
||||||
try {
|
try {
|
||||||
// await cancelProductOrder(order.id || order.orderId);
|
// await cancelProductOrder(outOrderNo || order.orderNo);
|
||||||
// uni.showToast({
|
uni.showToast({
|
||||||
// title: '支付已取消',
|
title: '支付已取消',
|
||||||
// icon: 'none'
|
icon: 'none'
|
||||||
// });
|
});
|
||||||
|
|
||||||
// 刷新订单列表
|
// 刷新订单列表
|
||||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
const statusArray = orderStatusTabs[currentTab.value].status;
|
||||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
||||||
@@ -406,6 +421,59 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// ====================== 支付宝小程序支付 ======================
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
console.log(res.data, '支付宝支付参数');
|
||||||
|
|
||||||
|
const tradeNO = res.data.tradeNo || res.data.tradeNO;
|
||||||
|
if (!tradeNO) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '未获取到支付宝支付参数',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my.tradePay({
|
||||||
|
tradeNO,
|
||||||
|
success: async (payRes) => {
|
||||||
|
console.log('支付宝支付结果:', payRes);
|
||||||
|
if (payRes.resultCode === '9000') {
|
||||||
|
uni.showToast({
|
||||||
|
title: '支付成功',
|
||||||
|
icon: 'success',
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
|
||||||
|
// 支付成功后刷新订单列表
|
||||||
|
const statusArray = orderStatusTabs[currentTab.value].status;
|
||||||
|
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
||||||
|
await loadOrderList(statusValue);
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: '支付失败,请重试',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
uni.showToast({
|
||||||
|
title: '支付失败,请重试',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// ====================== H5 环境(预留 Antom 支付) ======================
|
||||||
|
// #ifdef H5
|
||||||
|
uni.showToast({
|
||||||
|
title: '当前环境暂不支持购买,请使用微信或支付宝小程序',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
} else {
|
} else {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
|
|||||||
@@ -176,9 +176,6 @@ import {
|
|||||||
createCouponPayment,
|
createCouponPayment,
|
||||||
cancelCouponPayment
|
cancelCouponPayment
|
||||||
} from '@/config/api/coupon.js'
|
} from '@/config/api/coupon.js'
|
||||||
// import {
|
|
||||||
// cancelMemberCardPayment
|
|
||||||
// } from '@/config/api/member.js'
|
|
||||||
import {
|
import {
|
||||||
createMemberCardPayment,
|
createMemberCardPayment,
|
||||||
getMemberCardsByPosition,
|
getMemberCardsByPosition,
|
||||||
@@ -353,7 +350,48 @@ const selectProduct = (product) => {
|
|||||||
|
|
||||||
const orderNo = ref('')
|
const orderNo = ref('')
|
||||||
|
|
||||||
// 处理购买
|
// 获取当前支付平台(前端维度)
|
||||||
|
const getPaymentPlatform = () => {
|
||||||
|
// 小程序环境
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
return 'WECHAT'
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
return 'ALIPAY'
|
||||||
|
// #endif
|
||||||
|
// H5 默认使用 ANTOM
|
||||||
|
// #ifdef H5
|
||||||
|
return 'ANTOM'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
return 'WECHAT'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支付宝 tradePay 兼容:优先 tradeNO,其次 orderStr
|
||||||
|
const alipayTradePay = ({ tradeNo, orderStr, onSuccess, onFail }) => {
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
const tradeNO = tradeNo
|
||||||
|
if (tradeNO) {
|
||||||
|
my.tradePay({
|
||||||
|
tradeNO,
|
||||||
|
success: onSuccess,
|
||||||
|
fail: onFail
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (orderStr) {
|
||||||
|
my.tradePay({
|
||||||
|
orderStr,
|
||||||
|
success: onSuccess,
|
||||||
|
fail: onFail
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
onFail && onFail(new Error('未获取到支付宝支付参数'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理购买(会员卡 / 优惠券),内部使用商品多支付平台方案下的统一思路
|
||||||
const handleBuy = async () => {
|
const handleBuy = async () => {
|
||||||
if (!selectedProduct.value) {
|
if (!selectedProduct.value) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
@@ -363,63 +401,89 @@ const handleBuy = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 会员卡购买
|
const paymentPlatform = getPaymentPlatform()
|
||||||
|
|
||||||
|
// 会员卡购买(按接口文档:POST /app/member/pay)
|
||||||
if (currentTab.value === 'card') {
|
if (currentTab.value === 'card') {
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: '正在创建订单...'
|
title: '正在创建订单...'
|
||||||
})
|
})
|
||||||
|
|
||||||
const res = await createMemberCardPayment(selectedProduct.value.id)
|
const res = await createMemberCardPayment(selectedProduct.value.id, paymentPlatform)
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
|
|
||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
|
// 不同平台分别发起支付(字段按文档)
|
||||||
orderNo.value = res.data.OutOrderNo;
|
// 微信小程序
|
||||||
// 调起微信支付
|
// #ifdef MP-WEIXIN
|
||||||
uni.requestPayment({
|
if (paymentPlatform === 'WECHAT') {
|
||||||
timeStamp: res.data.timeStamp,
|
// 会员卡订单号:OutOrderNo(文档)
|
||||||
nonceStr: res.data.nonceStr,
|
orderNo.value = res.data.OutOrderNo
|
||||||
package: res.data.packageValue || res.data.package,
|
uni.requestPayment({
|
||||||
signType: res.data.signType || 'MD5',
|
timeStamp: res.data.timeStamp,
|
||||||
paySign: res.data.paySign,
|
nonceStr: res.data.nonceStr,
|
||||||
success: (payRes) => {
|
package: res.data.package,
|
||||||
uni.showToast({
|
signType: res.data.signType || 'MD5',
|
||||||
title: '支付成功',
|
paySign: res.data.paySign,
|
||||||
icon: 'success'
|
success: () => {
|
||||||
})
|
uni.showToast({ title: '支付成功', icon: 'success' })
|
||||||
// 支付成功后,跳转到我的会员卡页面
|
setTimeout(() => {
|
||||||
setTimeout(() => {
|
uni.navigateTo({ url: '/subPackages/business/my-card' })
|
||||||
uni.navigateTo({
|
}, 1500)
|
||||||
url: '/subPackages/business/my-card'
|
},
|
||||||
})
|
fail: (err) => {
|
||||||
}, 1500)
|
console.error('支付失败:', err)
|
||||||
},
|
if (err.errMsg && err.errMsg.includes('cancel')) {
|
||||||
fail: (err) => {
|
// 取消支付(本项目取消接口走 device 侧 cancel)
|
||||||
console.error('支付失败:', err)
|
orderNo.value && cancelMemberCardPayment(orderNo.value).catch(() => {})
|
||||||
console.log('支付失败详细信息:', err.errMsg.includes('cancel'));
|
uni.showToast({ title: '已取消支付', icon: 'none' })
|
||||||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
} else {
|
||||||
if (orderNo.value) {
|
uni.showToast({ title: '支付失败', icon: 'none' })
|
||||||
cancelMemberCardPayment(orderNo.value)
|
|
||||||
.then(cancelRes => {
|
|
||||||
console.log('取消支付订单成功:', cancelRes);
|
|
||||||
})
|
|
||||||
.catch(cancelErr => {
|
|
||||||
console.error('取消支付订单失败:', cancelErr);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
uni.showToast({
|
|
||||||
title: '已取消支付',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付失败',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// 支付宝小程序
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
if (paymentPlatform === 'ALIPAY') {
|
||||||
|
// 文档返回:tradeNo / outTradeNo;也兼容 orderStr
|
||||||
|
const tradeNo = res.data.tradeNo || res.data.tradeNO
|
||||||
|
const payForm = res.data.payForm || res.data.orderStr
|
||||||
|
alipayTradePay({
|
||||||
|
tradeNo,
|
||||||
|
orderStr: payForm,
|
||||||
|
onSuccess: (payRes) => {
|
||||||
|
if (payRes.resultCode === '9000') {
|
||||||
|
uni.showToast({ title: '支付成功', icon: 'success' })
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateTo({ url: '/subPackages/business/my-card' })
|
||||||
|
}, 1500)
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '支付失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFail: () => {
|
||||||
|
uni.showToast({ title: '支付失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// H5 + Antom(文档里 Antom 预留,当前后端可能返回 cashierUrl / h5Url 之一)
|
||||||
|
// #ifdef H5
|
||||||
|
if (paymentPlatform === 'ANTOM') {
|
||||||
|
const cashierUrl = res.data.cashierUrl || res.data.h5Url
|
||||||
|
if (cashierUrl) {
|
||||||
|
window.location.href = cashierUrl
|
||||||
|
return
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
// #endif
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: res.msg || '创建订单失败',
|
title: res.msg || '创建订单失败',
|
||||||
@@ -437,62 +501,83 @@ const handleBuy = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优惠券购买
|
// 优惠券购买(按接口文档:POST /app/coupon/pay)
|
||||||
if (currentTab.value === 'coupon') {
|
if (currentTab.value === 'coupon') {
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: '正在创建订单...'
|
title: '正在创建订单...'
|
||||||
})
|
})
|
||||||
|
|
||||||
const res = await createCouponPayment(selectedProduct.value.couponId)
|
const res = await createCouponPayment(selectedProduct.value.couponId, paymentPlatform)
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
|
|
||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
// 调起微信支付
|
// 微信小程序
|
||||||
uni.requestPayment({
|
// #ifdef MP-WEIXIN
|
||||||
timeStamp: res.data.timeStamp,
|
if (paymentPlatform === 'WECHAT') {
|
||||||
nonceStr: res.data.nonceStr,
|
uni.requestPayment({
|
||||||
package: res.data.packageValue || res.data.package,
|
timeStamp: res.data.timeStamp,
|
||||||
signType: res.data.signType || 'MD5',
|
nonceStr: res.data.nonceStr,
|
||||||
paySign: res.data.paySign,
|
package: res.data.package,
|
||||||
success: (payRes) => {
|
signType: res.data.signType || 'MD5',
|
||||||
uni.showToast({
|
paySign: res.data.paySign,
|
||||||
title: '支付成功',
|
success: () => {
|
||||||
icon: 'success'
|
uni.showToast({ title: '支付成功', icon: 'success' })
|
||||||
})
|
setTimeout(() => {
|
||||||
// 支付成功后,跳转到我的优惠券页面
|
uni.navigateTo({ url: '/subPackages/business/my-coupon' })
|
||||||
setTimeout(() => {
|
}, 1500)
|
||||||
uni.navigateTo({
|
},
|
||||||
url: '/subPackages/business/my-coupon'
|
fail: (err) => {
|
||||||
})
|
console.error('支付失败:', err)
|
||||||
}, 1500)
|
if (err.errMsg && err.errMsg.includes('cancel')) {
|
||||||
},
|
const outOrderNo = res.data.OutOrderNo || res.data.outOrderNo
|
||||||
fail: (err) => {
|
outOrderNo && cancelCouponPayment(outOrderNo).catch(() => {})
|
||||||
console.error('支付失败:', err)
|
uni.showToast({ title: '已取消支付', icon: 'none' })
|
||||||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
} else {
|
||||||
// 用户取消支付,调用取消接口
|
uni.showToast({ title: '支付失败', icon: 'none' })
|
||||||
const orderNo = res.data.OutOrderNo;
|
|
||||||
if (orderNo) {
|
|
||||||
cancelCouponPayment(orderNo)
|
|
||||||
.then(cancelRes => {
|
|
||||||
console.log('取消支付订单成功:', cancelRes);
|
|
||||||
})
|
|
||||||
.catch(cancelErr => {
|
|
||||||
console.error('取消支付订单失败:', cancelErr);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
uni.showToast({
|
|
||||||
title: '已取消支付',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付失败',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// 支付宝小程序
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
if (paymentPlatform === 'ALIPAY') {
|
||||||
|
const tradeNo = res.data.tradeNo || res.data.tradeNO
|
||||||
|
const payForm = res.data.payForm || res.data.orderStr
|
||||||
|
alipayTradePay({
|
||||||
|
tradeNo,
|
||||||
|
orderStr: payForm,
|
||||||
|
onSuccess: (payRes) => {
|
||||||
|
if (payRes.resultCode === '9000') {
|
||||||
|
uni.showToast({ title: '支付成功', icon: 'success' })
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateTo({ url: '/subPackages/business/my-coupon' })
|
||||||
|
}, 1500)
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '支付失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFail: () => {
|
||||||
|
uni.showToast({ title: '支付失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// H5 + Antom
|
||||||
|
// #ifdef H5
|
||||||
|
if (paymentPlatform === 'ANTOM') {
|
||||||
|
const cashierUrl = res.data.cashierUrl || res.data.h5Url
|
||||||
|
if (cashierUrl) {
|
||||||
|
window.location.href = cashierUrl
|
||||||
|
return
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
// #endif
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: res.msg || '创建订单失败',
|
title: res.msg || '创建订单失败',
|
||||||
|
|||||||
+183
-51
@@ -78,6 +78,10 @@
|
|||||||
} from '@dcloudio/uni-app'
|
} from '@dcloudio/uni-app'
|
||||||
import {
|
import {
|
||||||
queryById,
|
queryById,
|
||||||
|
createWxPayment,
|
||||||
|
getWxPaymentStatus,
|
||||||
|
createAliPayment,
|
||||||
|
getAliPaymentStatus,
|
||||||
createAntomPayment,
|
createAntomPayment,
|
||||||
getAntomPaymentMethods,
|
getAntomPaymentMethods,
|
||||||
getAntomPaymentStatus
|
getAntomPaymentStatus
|
||||||
@@ -107,9 +111,9 @@
|
|||||||
const countdown = ref(15 * 60) // 15分钟 = 900秒
|
const countdown = ref(15 * 60) // 15分钟 = 900秒
|
||||||
let countdownTimer = null
|
let countdownTimer = null
|
||||||
|
|
||||||
// 支付方式相关
|
// 支付方式相关(微信/支付宝/H5-Antom 多平台)
|
||||||
const paymentMethods = ref([])
|
const paymentMethods = ref([])
|
||||||
const selectedPaymentMethod = ref('ALIPAY') // 默认选择支付宝
|
const selectedPaymentMethod = ref('WECHAT') // 默认微信
|
||||||
|
|
||||||
// 地点名称(可以从设备信息中获取,这里先用默认值)
|
// 地点名称(可以从设备信息中获取,这里先用默认值)
|
||||||
const locationName = ref('澎创办公室')
|
const locationName = ref('澎创办公室')
|
||||||
@@ -197,11 +201,11 @@
|
|||||||
deviceNo.value = orderData.deviceNo;
|
deviceNo.value = orderData.deviceNo;
|
||||||
await loadDeviceInfo();
|
await loadDeviceInfo();
|
||||||
await loadPaymentMethods();
|
await loadPaymentMethods();
|
||||||
// #ifdef H5
|
// 如果订单状态是等待支付,启动相应的支付状态轮询
|
||||||
if(orderInfo.value.orderStatus=='waiting_for_payment'){
|
if(orderInfo.value.orderStatus=='waiting_for_payment'){
|
||||||
|
// 使用当前选中的支付方式类型进行轮询
|
||||||
startPaymentStatusPolling();
|
startPaymentStatusPolling();
|
||||||
}
|
}
|
||||||
// #endif
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(t('order.getOrderFailed'))
|
throw new Error(t('order.getOrderFailed'))
|
||||||
}
|
}
|
||||||
@@ -253,35 +257,46 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载支付方式列表
|
// 加载支付方式列表(根据平台决定可选项)
|
||||||
const loadPaymentMethods = async () => {
|
const loadPaymentMethods = async () => {
|
||||||
if (!orderInfo.value.orderNo) return;
|
const methods = []
|
||||||
|
|
||||||
try {
|
// 小程序环境下:微信 / 支付宝
|
||||||
const osType = getOsType();
|
// #ifdef MP-WEIXIN
|
||||||
console.log('当前系统类型:', osType);
|
methods.push({ paymentMethodType: 'WECHAT', paymentMethodName: '微信支付' })
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
methods.push({ paymentMethodType: 'ALIPAY', paymentMethodName: '支付宝支付' })
|
||||||
|
// #endif
|
||||||
|
|
||||||
const res = await getAntomPaymentMethods(orderInfo.value.orderNo, osType);
|
// H5 环境:使用 Antom 聚合支付(多通道)
|
||||||
if (res.code === 200 && res.data && res.data.paymentOptions) {
|
// #ifdef H5
|
||||||
paymentMethods.value = res.data.paymentOptions;
|
if (orderInfo.value.orderNo) {
|
||||||
console.log('支付方式列表:', paymentMethods.value);
|
try {
|
||||||
// 如果有支付方式,默认选择第一个
|
const osType = getOsType();
|
||||||
if (paymentMethods.value.length > 0) {
|
const res = await getAntomPaymentMethods(orderInfo.value.orderNo, osType);
|
||||||
selectedPaymentMethod.value = paymentMethods.value[0].paymentMethodType;
|
if (res.code === 200 && res.data && res.data.paymentOptions) {
|
||||||
|
res.data.paymentOptions.forEach(item => {
|
||||||
|
methods.push({
|
||||||
|
paymentMethodType: item.paymentMethodType,
|
||||||
|
paymentMethodName: item.paymentMethodName || item.paymentMethodType
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取 Antom 支付方式失败:', error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
}
|
||||||
console.error('获取支付方式失败:', error);
|
// #endif
|
||||||
// 如果获取失败,使用默认支付方式
|
|
||||||
paymentMethods.value = [{
|
// 兜底:至少保留一个微信
|
||||||
paymentMethodType: 'ALIPAY',
|
if (!methods.length) {
|
||||||
paymentMethodName: '支付宝'
|
methods.push({ paymentMethodType: 'WECHAT', paymentMethodName: '微信支付' })
|
||||||
},
|
}
|
||||||
{
|
|
||||||
paymentMethodType: 'WECHATPAY',
|
paymentMethods.value = methods
|
||||||
paymentMethodName: '微信支付'
|
if (paymentMethods.value.length > 0) {
|
||||||
}
|
selectedPaymentMethod.value = paymentMethods.value[0].paymentMethodType
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,8 +330,85 @@
|
|||||||
title: t('common.processing')
|
title: t('common.processing')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const method = selectedPaymentMethod.value
|
||||||
|
|
||||||
|
// 微信小程序支付押金
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
if (method === 'WECHAT') {
|
||||||
|
const wxRes = await createWxPayment(orderInfo.value.orderNo)
|
||||||
|
if (wxRes.code === 200 && wxRes.data) {
|
||||||
|
const payData = wxRes.data
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
wx.requestPayment({
|
||||||
|
...payData,
|
||||||
|
success: resolve,
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// 支付成功后轮询微信支付状态
|
||||||
|
startPaymentStatusPolling('WECHAT')
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
throw new Error(wxRes.msg || t('payment.createPayOrderFailed'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// 支付宝小程序支付押金
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
if (method === 'ALIPAY') {
|
||||||
|
const aliRes = await createAliPayment(orderInfo.value.orderNo)
|
||||||
|
if (aliRes.code === 200 && aliRes.data) {
|
||||||
|
// 后端当前实际返回结构示例:
|
||||||
|
// { code:200, msg:'操作成功', data:{ tradeNo:'xxx', outTradeNo:'yyy' } }
|
||||||
|
const tradeNO = aliRes.data.tradeNo || aliRes.data.outTradeNo
|
||||||
|
const payForm = aliRes.data.payForm || aliRes.data.orderStr
|
||||||
|
|
||||||
|
if (!tradeNO && !payForm) {
|
||||||
|
throw new Error('未获取到支付宝支付参数')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先使用 tradeNO 方式,其次兼容老的 orderStr 方式
|
||||||
|
if (tradeNO) {
|
||||||
|
my.tradePay({
|
||||||
|
tradeNO,
|
||||||
|
success: (res) => {
|
||||||
|
if (res.resultCode === '9000') {
|
||||||
|
startPaymentStatusPolling('ALIPAY')
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: t('payment.paymentFailed'), icon: 'none' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
uni.showToast({ title: t('payment.paymentFailed'), icon: 'none' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
my.tradePay({
|
||||||
|
orderStr: payForm,
|
||||||
|
success: (res) => {
|
||||||
|
if (res.resultCode === '9000') {
|
||||||
|
startPaymentStatusPolling('ALIPAY')
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: t('payment.paymentFailed'), icon: 'none' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
uni.showToast({ title: t('payment.paymentFailed'), icon: 'none' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
throw new Error(aliRes.msg || t('payment.createPayOrderFailed'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// H5 + Antom 聚合支付
|
||||||
|
// #ifdef H5
|
||||||
const osType = getOsType();
|
const osType = getOsType();
|
||||||
const res = await createAntomPayment(orderInfo.value.orderNo, selectedPaymentMethod.value, osType)
|
const res = await createAntomPayment(orderInfo.value.orderNo, method, osType)
|
||||||
|
|
||||||
if (res && res.code === 200 && res.data) {
|
if (res && res.code === 200 && res.data) {
|
||||||
const paymentUrl = res.data.h5Url;
|
const paymentUrl = res.data.h5Url;
|
||||||
@@ -326,21 +418,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
// #ifdef H5
|
|
||||||
uni.setStorageSync('pendingPaymentNo', orderId.value);
|
uni.setStorageSync('pendingPaymentNo', orderId.value);
|
||||||
// 跳转到支付页面
|
|
||||||
// uni.navigateTo({
|
|
||||||
// url: `/pages/webview/index?url=${encodeURIComponent(paymentUrl)}&title=支付`
|
|
||||||
// });
|
|
||||||
window.open(paymentUrl);
|
window.open(paymentUrl);
|
||||||
// #endif
|
|
||||||
|
|
||||||
// 开始轮询支付状态
|
|
||||||
startPaymentStatusPolling();
|
|
||||||
|
|
||||||
|
// 开始轮询支付状态(传入当前选中的支付方式类型)
|
||||||
|
startPaymentStatusPolling(method);
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res?.msg || t('payment.createPayOrderFailed'))
|
throw new Error(res?.msg || t('payment.createPayOrderFailed'))
|
||||||
}
|
}
|
||||||
|
// #endif
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('支付失败:', error)
|
console.error('支付失败:', error)
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
@@ -352,14 +439,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 轮询支付状态
|
// 轮询定时器
|
||||||
let pollingTimer = null;
|
let pollingTimer = null;
|
||||||
const startPaymentStatusPolling = () => {
|
|
||||||
|
/**
|
||||||
|
* 统一的支付状态轮询方法
|
||||||
|
* @param {string} paymentMethodType - 支付方式类型,如 'WECHAT' | 'ALIPAY' | 'WECHATPAY' 等,与 selectedPaymentMethod 保持一致
|
||||||
|
*/
|
||||||
|
const startPaymentStatusPolling = (paymentMethodType = null) => {
|
||||||
// 清除之前的定时器
|
// 清除之前的定时器
|
||||||
if (pollingTimer) {
|
if (pollingTimer) {
|
||||||
clearInterval(pollingTimer);
|
clearInterval(pollingTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果没有传入支付方式类型,使用当前选中的支付方式
|
||||||
|
const methodType = paymentMethodType || selectedPaymentMethod.value;
|
||||||
|
|
||||||
let pollCount = 0;
|
let pollCount = 0;
|
||||||
const maxPollCount = 60; // 最多轮询60次(5分钟)
|
const maxPollCount = 60; // 最多轮询60次(5分钟)
|
||||||
|
|
||||||
@@ -376,32 +471,64 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let res;
|
||||||
|
let status, successStatus, failStatuses;
|
||||||
|
|
||||||
|
// #ifdef H5
|
||||||
|
// H5 环境统一使用 Antom 聚合支付 API
|
||||||
const osType = getOsType();
|
const osType = getOsType();
|
||||||
const res = await getAntomPaymentStatus(orderInfo.value.orderNo, osType);
|
res = await getAntomPaymentStatus(orderInfo.value.orderNo, osType);
|
||||||
if (res && res.code === 200 && res.data) {
|
if (res && res.code === 200 && res.data) {
|
||||||
const paymentStatus = res.data.paymentStatus;
|
status = res.data.paymentStatus;
|
||||||
|
successStatus = 'SUCCESS';
|
||||||
|
failStatuses = ['FAIL', 'CANCELLED'];
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
// 微信小程序:根据支付方式类型判断
|
||||||
|
if (methodType === 'WECHAT' || methodType === 'WECHATPAY') {
|
||||||
|
res = await getWxPaymentStatus(orderInfo.value.orderNo);
|
||||||
|
if (res && res.code === 200 && res.data) {
|
||||||
|
status = res.data.tradeStatus;
|
||||||
|
successStatus = 'SUCCESS';
|
||||||
|
failStatuses = ['FAIL', 'CANCELLED'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
// 支付宝小程序
|
||||||
|
if (methodType === 'ALIPAY') {
|
||||||
|
res = await getAliPaymentStatus(orderInfo.value.orderNo);
|
||||||
|
if (res && res.code === 200 && res.data) {
|
||||||
|
status = res.data.tradeStatus;
|
||||||
|
successStatus = 'TRADE_SUCCESS';
|
||||||
|
failStatuses = ['TRADE_FAIL', 'TRADE_CANCELLED'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
if (paymentStatus === 'SUCCESS') {
|
// 处理支付状态结果
|
||||||
|
if (res && res.code === 200 && res.data && status) {
|
||||||
|
// 支付成功
|
||||||
|
if (status === successStatus) {
|
||||||
clearInterval(pollingTimer);
|
clearInterval(pollingTimer);
|
||||||
|
|
||||||
// uni.showToast({
|
|
||||||
// title: t('payment.paymentSuccess'),
|
|
||||||
// icon: 'success'
|
|
||||||
// });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateUserBalance(orderId.value);
|
await updateUserBalance(orderId.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('更新用户余额失败:', error);
|
console.warn('更新用户余额失败:', error);
|
||||||
}
|
}
|
||||||
console.log(orderInfo);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: `/pages/order/success?orderId=${orderId.value}&deviceId=${orderInfo.value.deviceNo}`
|
url: `/pages/order/success?orderId=${orderId.value}&deviceId=${orderInfo.value.deviceNo}`
|
||||||
});
|
});
|
||||||
}, 1500);
|
}, 1500);
|
||||||
} else if (paymentStatus === 'FAIL' || paymentStatus === 'CANCELLED') {
|
}
|
||||||
|
// 支付失败
|
||||||
|
else if (failStatuses && failStatuses.includes(status)) {
|
||||||
clearInterval(pollingTimer);
|
clearInterval(pollingTimer);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '支付失败,请重新支付',
|
title: '支付失败,请重新支付',
|
||||||
@@ -410,11 +537,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('查询支付状态失败:', error);
|
const errorMsg = methodType === 'ALIPAY' ? '支付宝' : (methodType === 'WECHAT' || methodType === 'WECHATPAY') ? '微信' : '支付';
|
||||||
|
console.error(`查询${errorMsg}支付状态失败:`, error);
|
||||||
}
|
}
|
||||||
}, 5000); // 每5秒查询一次
|
}, 5000); // 每5秒查询一次
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 兼容性方法:保持原有函数名,内部调用统一方法
|
||||||
|
const startWxPaymentStatusPolling = () => startPaymentStatusPolling('WECHAT');
|
||||||
|
const startAliPaymentStatusPolling = () => startPaymentStatusPolling('ALIPAY');
|
||||||
|
|
||||||
// 更新导航栏倒计时
|
// 更新导航栏倒计时
|
||||||
const updateNavBarCountdown = () => {
|
const updateNavBarCountdown = () => {
|
||||||
const minutes = Math.floor(countdown.value / 60).toString().padStart(2, '0')
|
const minutes = Math.floor(countdown.value / 60).toString().padStart(2, '0')
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
// 跳转到投诉记录列表
|
// 跳转到投诉记录列表
|
||||||
const navigateToRecord = () => {
|
const navigateToRecord = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/feedback/list'
|
url: '/subPackages/service/feedback/list'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -270,7 +270,7 @@
|
|||||||
// 跳转到详情页
|
// 跳转到详情页
|
||||||
const navigateToDetail = (item) => {
|
const navigateToDetail = (item) => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/feedback/detail?id=${item.id || item.feedbackId}`
|
url: `/subPackages/service/feedback/detail?id=${item.id || item.feedbackId}`
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,18 +2,27 @@
|
|||||||
<view class="help-container">
|
<view class="help-container">
|
||||||
<!-- 常见问题 -->
|
<!-- 常见问题 -->
|
||||||
<view class="faq-section">
|
<view class="faq-section">
|
||||||
<uv-collapse :border="false">
|
<view
|
||||||
<uv-collapse-item
|
v-for="(item, index) in faqList"
|
||||||
v-for="(item, index) in faqList"
|
:key="index"
|
||||||
:key="index"
|
class="collapse-item"
|
||||||
:title="$t(item.question)"
|
>
|
||||||
:name="index"
|
<view
|
||||||
|
class="collapse-header"
|
||||||
|
@click="toggleCollapse(index)"
|
||||||
|
>
|
||||||
|
<text class="collapse-title">{{ $t(item.question) }}</text>
|
||||||
|
<text class="collapse-icon" :class="{ 'active': activeIndex === index }">▼</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="collapse-content"
|
||||||
|
:class="{ 'show': activeIndex === index }"
|
||||||
>
|
>
|
||||||
<view class="answer-content">
|
<view class="answer-content">
|
||||||
<text class="answer-text">{{ $t(item.answer) }}</text>
|
<text class="answer-text">{{ $t(item.answer) }}</text>
|
||||||
</view>
|
</view>
|
||||||
</uv-collapse-item>
|
</view>
|
||||||
</uv-collapse>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 联系客服 -->
|
<!-- 联系客服 -->
|
||||||
@@ -34,7 +43,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import { HELP_CONTENT } from '@/constants/help'
|
import { HELP_CONTENT } from '@/constants/help'
|
||||||
import { getCustomerPhone } from '@/util/index.js'
|
import { getCustomerPhone } from '@/util/index.js'
|
||||||
@@ -44,6 +53,7 @@ const { t } = useI18n()
|
|||||||
|
|
||||||
const faqList = ref(HELP_CONTENT.FAQ_LIST)
|
const faqList = ref(HELP_CONTENT.FAQ_LIST)
|
||||||
const customerPhone = ref(HELP_CONTENT.CONTACT.PHONE.VALUE)
|
const customerPhone = ref(HELP_CONTENT.CONTACT.PHONE.VALUE)
|
||||||
|
const activeIndex = ref(null)
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
@@ -52,6 +62,14 @@ onLoad(() => {
|
|||||||
customerPhone.value = getCustomerPhone()
|
customerPhone.value = getCustomerPhone()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const toggleCollapse = (index) => {
|
||||||
|
if (activeIndex.value === index) {
|
||||||
|
activeIndex.value = null
|
||||||
|
} else {
|
||||||
|
activeIndex.value = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const makePhoneCall = () => {
|
const makePhoneCall = () => {
|
||||||
uni.makePhoneCall({
|
uni.makePhoneCall({
|
||||||
phoneNumber: customerPhone.value
|
phoneNumber: customerPhone.value
|
||||||
@@ -72,15 +90,67 @@ const makePhoneCall = () => {
|
|||||||
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.answer-content {
|
.collapse-item {
|
||||||
padding: 20rpx 30rpx 30rpx;
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
background: #f9f9f9;
|
|
||||||
|
|
||||||
.answer-text {
|
&:last-child {
|
||||||
font-size: 28rpx;
|
border-bottom: none;
|
||||||
color: #666;
|
}
|
||||||
line-height: 1.8;
|
|
||||||
display: block;
|
.collapse-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 30rpx;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-title {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-icon {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
transform: rotate(0deg);
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-content {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.3s ease-out;
|
||||||
|
|
||||||
|
&.show {
|
||||||
|
max-height: 2000rpx;
|
||||||
|
transition: max-height 0.3s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-content {
|
||||||
|
padding: 20rpx 30rpx 30rpx;
|
||||||
|
background: #f9f9f9;
|
||||||
|
|
||||||
|
.answer-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.8;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,27 @@
|
|||||||
<view class="title">{{ $t('auth.loginTitle') }}</view>
|
<view class="title">{{ $t('auth.loginTitle') }}</view>
|
||||||
<view class="subtitle">{{ $t('auth.loginDesc') }}</view>
|
<view class="subtitle">{{ $t('auth.loginDesc') }}</view>
|
||||||
|
|
||||||
<!-- 微信一键手机号快捷登录(推荐) -->
|
<!-- 微信小程序:一键手机号快捷登录 -->
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
<button v-if="!isAgreed" class="btn primary" @click="handleLoginClick">
|
<button v-if="!isAgreed" class="btn primary" @click="handleLoginClick">
|
||||||
{{ $t('auth.getPhoneNumber') }}
|
{{ $t('auth.getPhoneNumber') }}
|
||||||
</button>
|
</button>
|
||||||
<button v-else class="btn primary" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
|
<button v-else class="btn primary" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
|
||||||
{{ $t('auth.getPhoneNumber') }}
|
{{ $t('auth.getPhoneNumber') }}
|
||||||
</button>
|
</button>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- 支付宝小程序:授权码快捷登录(不支持 open-type=getPhoneNumber) -->
|
||||||
|
<!-- #ifdef MP-ALIPAY -->
|
||||||
|
<button v-if="!isAgreed" class="btn primary" @click="handleLoginClick">
|
||||||
|
{{ $t('auth.loginBtn') }}
|
||||||
|
</button>
|
||||||
|
<button v-else class="btn primary" @click="onAlipayLogin">
|
||||||
|
{{ $t('auth.loginBtn') }}
|
||||||
|
</button>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- H5:不显示小程序快捷登录按钮 -->
|
||||||
|
|
||||||
<!-- 手机号验证码登录 -->
|
<!-- 手机号验证码登录 -->
|
||||||
<button class="btn outline" @click="goToPhoneLogin" v-if="isHTML5">
|
<button class="btn outline" @click="goToPhoneLogin" v-if="isHTML5">
|
||||||
@@ -40,7 +54,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import { wxLogin, getUserPhoneNumber, getUserInfo } from '@/util/index.js'
|
import { wxLogin, alipayLogin, getUserPhoneNumber, getUserInfo } from '@/util/index.js'
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -116,13 +130,12 @@
|
|||||||
uni.reLaunch({ url: target })
|
uni.reLaunch({ url: target })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 微信快捷登录入口(备用,目前主流程使用一键手机号登录)
|
||||||
const onWeChatLogin = async () => {
|
const onWeChatLogin = async () => {
|
||||||
try {
|
try {
|
||||||
// 先检查是否同意协议
|
|
||||||
await checkAgreement()
|
await checkAgreement()
|
||||||
|
await wxLogin()
|
||||||
await wxLogin()
|
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
|
||||||
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
|
|
||||||
await navigateAfterLogin()
|
await navigateAfterLogin()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message !== t('auth.pleaseAgreeToTerms')) {
|
if (error.message !== t('auth.pleaseAgreeToTerms')) {
|
||||||
@@ -131,6 +144,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 支付宝快捷登录入口(支付宝小程序)
|
||||||
|
const onAlipayLogin = async () => {
|
||||||
|
try {
|
||||||
|
await checkAgreement()
|
||||||
|
await alipayLogin()
|
||||||
|
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' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意:手机号一致性校验不在登录页做;扫码/租借前校验(支付宝 my.getPhoneNumber)
|
||||||
|
|
||||||
const onGetPhoneNumber = async (e) => {
|
const onGetPhoneNumber = async (e) => {
|
||||||
if (!e || e.detail.errMsg !== 'getPhoneNumber:ok') {
|
if (!e || e.detail.errMsg !== 'getPhoneNumber:ok') {
|
||||||
uni.showToast({ title: t('auth.phoneCancelled'), icon: 'none' })
|
uni.showToast({ title: t('auth.phoneCancelled'), icon: 'none' })
|
||||||
@@ -138,9 +167,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 先微信登录,获取 token
|
// 先完成微信登录(/app/user/quickLogin,后端建立/查询 WECHAT_MINI 用户)
|
||||||
await wxLogin()
|
await wxLogin()
|
||||||
// 再用微信返回的临时 code 换取手机号
|
// 再用微信返回的临时 code 换取手机号(后端按文档更新 phone 字段)
|
||||||
await getUserPhoneNumber(e.detail.code)
|
await getUserPhoneNumber(e.detail.code)
|
||||||
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
|
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
|
||||||
await navigateAfterLogin()
|
await navigateAfterLogin()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="my-page">
|
<view class="my-page">
|
||||||
<view class="user-card" @click="navigateTo('/pages/userProfile/index')">
|
<view class="user-card" @click="navigateTo('/subPackages/user/userProfile/index')">
|
||||||
<view class="avatar-box">
|
<view class="avatar-box">
|
||||||
<image class="avatar" v-if="userInfo.avatar" :src="userInfo.avatar" mode="aspectFill" lazy-load="true"></image>
|
<image class="avatar" v-if="userInfo.avatar" :src="userInfo.avatar" mode="aspectFill" lazy-load="true"></image>
|
||||||
<image v-else class="avatar" src="@/static/head.png" mode="aspectFill" lazy-load="true"></image>
|
<image v-else class="avatar" src="@/static/head.png" mode="aspectFill" lazy-load="true"></image>
|
||||||
|
|||||||
@@ -11,11 +11,11 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="group">
|
<view class="group">
|
||||||
<view class="item" @click="navigateTo('/subPackages/other/legal/agreement')">
|
<view class="item" @click="navigateTo('/subPackages/other/legal/agreement')">
|
||||||
<text class="label">{{ $t('user.userAgreement') }}</text>
|
<text class="label">{{ $t('user.settinguserAgreement') }}</text>
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
||||||
</view>
|
</view>
|
||||||
<view class="item" @click="navigateTo('/subPackages/other/legal/privacy')">
|
<view class="item" @click="navigateTo('/subPackages/other/legal/privacy')">
|
||||||
<text class="label">{{ $t('user.privacyPolicy') }}</text>
|
<text class="label">{{ $t('user.settinguserprivacyPolicy') }}</text>
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
||||||
</view>
|
</view>
|
||||||
<view class="item" @click="navigateTo('/subPackages/other/legal/terms')">
|
<view class="item" @click="navigateTo('/subPackages/other/legal/terms')">
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
<!-- #ifdef MP-WEIXIN -->
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
<button class="avatar-choose-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"></button>
|
<button class="avatar-choose-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"></button>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef MP-ALIPAY -->
|
||||||
|
<!-- <button class="avatar-choose-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"></button> -->
|
||||||
|
<!-- #endif -->
|
||||||
</view>
|
</view>
|
||||||
<view class="avatar-tip">{{ $t('userProfile.clickToChange') }}</view>
|
<view class="avatar-tip">{{ $t('userProfile.clickToChange') }}</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -129,6 +132,8 @@ const redirectToLogin = () => {
|
|||||||
|
|
||||||
// 小程序原生选择头像回调
|
// 小程序原生选择头像回调
|
||||||
const onChooseAvatar = async (e) => {
|
const onChooseAvatar = async (e) => {
|
||||||
|
console.log(e.detail.avatarUrl,'获取头像详情');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = uni.getStorageSync('token');
|
const token = uni.getStorageSync('token');
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@@ -282,7 +287,7 @@ function maskPhone(phone) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 仅小程序端存在,此按钮覆盖在头像上捕获点击以触发选择头像 */
|
/* 仅小程序端存在,此按钮覆盖在头像上捕获点击以触发选择头像 */
|
||||||
/* #ifdef MP-WEIXIN */
|
/* #ifdef MP-WEIXIN || MP-ALIPAY */
|
||||||
.avatar-choose-btn {
|
.avatar-choose-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
+150
-18
@@ -1,37 +1,48 @@
|
|||||||
import {
|
import {
|
||||||
login,
|
login,
|
||||||
|
quickLogin,
|
||||||
getMyIndexInfo,
|
getMyIndexInfo,
|
||||||
getWxUserPhoneNumber
|
getWxUserPhoneNumber,
|
||||||
|
getAliUserPhoneNumber
|
||||||
} from "../config/api/user"
|
} from "../config/api/user"
|
||||||
import {
|
import {
|
||||||
URL,
|
URL,
|
||||||
appid
|
appid,
|
||||||
|
ZFBappid
|
||||||
} from "@/config/url.js"
|
} from "@/config/url.js"
|
||||||
import { getCommonByBrand } from "@/config/api/system"
|
import { getCommonByBrand } from "@/config/api/system"
|
||||||
import { HELP_CONTENT } from "@/constants/help"
|
import { HELP_CONTENT } from "@/constants/help"
|
||||||
// import { GET_PHONE_NUMBER_URL } from "../config/url"
|
// import { GET_PHONE_NUMBER_URL } from "../config/url"
|
||||||
|
|
||||||
// 微信登录方法
|
// 统一快捷登录 - 微信小程序登录(使用 /app/user/quickLogin)
|
||||||
export const wxLogin = () => {
|
export const wxLogin = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// 1. 获取微信登录凭证
|
// 1. 获取微信登录凭证(code -> 后端换 openId)
|
||||||
uni.login({
|
uni.login({
|
||||||
provider: 'weixin',
|
provider: 'weixin',
|
||||||
success: async (loginRes) => {
|
success: async (loginRes) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (loginRes.code) {
|
if (!loginRes.code) {
|
||||||
// 2. 发送 code 到后端换取 token
|
throw new Error('获取微信登录凭证失败')
|
||||||
const result = await login({
|
}
|
||||||
code: loginRes.code,
|
|
||||||
appid: "wx2165f0be356ae7a9"
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result.code === 200) {
|
// 2. 调用统一快捷登录接口,后端根据 code 获取 openId,并按文档用 openId+userSource 处理
|
||||||
// 3. 保存token和用户信息
|
const result = await quickLogin({
|
||||||
|
loginType: 'WECHAT',
|
||||||
|
appid: appid,
|
||||||
|
code: loginRes.code
|
||||||
|
})
|
||||||
|
|
||||||
uni.setStorageSync('token', result.data.LoginWxVo.access_token)
|
if (result.code === 200 && result.data) {
|
||||||
uni.setStorageSync('client_id', result.data.LoginWxVo.client_id)
|
// 3. 兼容多种返回 token 结构
|
||||||
|
const token = result.data.access_token || result.data.token || result.token
|
||||||
|
const clientId = result.data.client_id || result.data.clientId || result.client_id
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
uni.setStorageSync('token', token)
|
||||||
|
if (clientId) {
|
||||||
|
uni.setStorageSync('client_id', clientId)
|
||||||
|
}
|
||||||
|
|
||||||
// 4. 登录成功后获取并缓存客服电话
|
// 4. 登录成功后获取并缓存客服电话
|
||||||
fetchAndCacheCustomerPhone().catch(err => {
|
fetchAndCacheCustomerPhone().catch(err => {
|
||||||
@@ -40,13 +51,12 @@ export const wxLogin = () => {
|
|||||||
|
|
||||||
resolve(result.data)
|
resolve(result.data)
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.message || '登录失败')
|
throw new Error(result.msg || '登录失败,未返回令牌')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('获取微信登录凭证失败')
|
throw new Error(result.msg || result.message || '登录失败')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: error.message || '登录失败',
|
title: error.message || '登录失败',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
@@ -65,6 +75,79 @@ export const wxLogin = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 统一快捷登录 - 支付宝小程序登录
|
||||||
|
export const alipayLogin = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 仅在支付宝小程序环境可用
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
my.getAuthCode({
|
||||||
|
scopes: ['auth_user'],
|
||||||
|
success: async (res) => {
|
||||||
|
try {
|
||||||
|
if (!res.authCode) {
|
||||||
|
throw new Error('获取支付宝授权码失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await quickLogin({
|
||||||
|
loginType: 'ALIPAY',
|
||||||
|
appid: ZFBappid,
|
||||||
|
code: res.authCode
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.code === 200 && result.data) {
|
||||||
|
const token = result.data.access_token || result.data.token || result.token
|
||||||
|
const clientId = result.data.client_id || result.data.clientId || result.client_id
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
uni.setStorageSync('token', token)
|
||||||
|
if (clientId) {
|
||||||
|
uni.setStorageSync('client_id', clientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchAndCacheCustomerPhone().catch(err => {
|
||||||
|
console.error('获取客服电话失败,但不影响登录', err)
|
||||||
|
})
|
||||||
|
|
||||||
|
resolve(result.data)
|
||||||
|
} else {
|
||||||
|
throw new Error(result.msg || '登录失败,未返回令牌')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(result.msg || result.message || '登录失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || '登录失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
uni.showToast({
|
||||||
|
title: '支付宝登录失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-ALIPAY
|
||||||
|
reject(new Error('当前环境不支持支付宝登录'))
|
||||||
|
// #endif
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统一快捷登录 - 短信验证码登录(主要用于 H5/海外)
|
||||||
|
export const smsQuickLogin = (phonenumber, smsCode) => {
|
||||||
|
return quickLogin({
|
||||||
|
loginType: 'SMS',
|
||||||
|
phonenumber,
|
||||||
|
smsCode
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 检查登录状态
|
// 检查登录状态
|
||||||
// export const checkLogin = () => {
|
// export const checkLogin = () => {
|
||||||
// const token = uni.getStorageSync('token')
|
// const token = uni.getStorageSync('token')
|
||||||
@@ -106,6 +189,55 @@ export const getUserPhoneNumber = (code) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 支付宝:在已授权 phoneNumber 的前提下调用 my.getPhoneNumber,再提交后端解密/绑定,返回手机号
|
||||||
|
// 官方 API: https://opendocs.alipay.com/mini/01f46f19_my.getPhoneNumber?pathHash=a67c2790
|
||||||
|
export const getAlipayUserPhoneNumber = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
if (typeof my === 'undefined' || typeof my.getPhoneNumber !== 'function') {
|
||||||
|
reject(new Error('当前环境不支持支付宝获取手机号'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
my.getPhoneNumber({
|
||||||
|
success: async (res) => {
|
||||||
|
try {
|
||||||
|
// res.response 实际是一个 JSON 字符串,形如:
|
||||||
|
// {"response":"...","sign":"...","sign_type":"RSA2","encrypt_type":"AES","charset":"UTF-8"}
|
||||||
|
// 这里先做 JSON 解析,再把字段按 JSON 结构传给后端
|
||||||
|
let parsed = {}
|
||||||
|
if (res && res.response) {
|
||||||
|
try {
|
||||||
|
parsed = typeof res.response === 'string' ? JSON.parse(res.response) : res.response
|
||||||
|
} catch (parseErr) {
|
||||||
|
console.error('解析支付宝手机号报文失败:', parseErr, res.response)
|
||||||
|
throw parseErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
appid: ZFBappid,
|
||||||
|
...parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await getAliUserPhoneNumber(payload)
|
||||||
|
resolve(result)
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
reject(new Error(err?.errorMessage || err?.message || '支付宝获取手机号失败'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-ALIPAY
|
||||||
|
reject(new Error('当前环境不支持支付宝获取手机号'))
|
||||||
|
// #endif
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 调用微信支付分接口
|
// 调用微信支付分接口
|
||||||
export const initiateWeChatScorePayment = (paymentData) => {
|
export const initiateWeChatScorePayment = (paymentData) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
+386
-6
@@ -36,6 +36,206 @@ const getPermissionText = (key) => {
|
|||||||
return texts[key] || permissionTexts[DEFAULT_LOCALE][key] || ''
|
return texts[key] || permissionTexts[DEFAULT_LOCALE][key] || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 兼容多端:部分平台(如支付宝小程序)经纬度可能返回字符串
|
||||||
|
// 统一先转为 Number 再做 toFixed,避免 "toFixed is not a function"
|
||||||
|
const toFixedNumber = (value, digits = 5) => {
|
||||||
|
const n = Number(value)
|
||||||
|
if (!Number.isFinite(n)) return null
|
||||||
|
return Number(n.toFixed(digits))
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// 支付宝小程序:高德地图 WebService(需要 key + 安全密钥签名)
|
||||||
|
// 说明:支付宝小程序下仅使用高德 key(你提供的 key/secret),避免腾讯地图 key 不可用的问题
|
||||||
|
// =============================
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
const AMAP_KEY = '1c6df40606891377b33576e7876af6ac'
|
||||||
|
const AMAP_SECRET = '00ea790d0b24190174c598199b183750'
|
||||||
|
const AMAP_BASE_URL = 'https://restapi.amap.com/v3/'
|
||||||
|
|
||||||
|
// 轻量 MD5(RFC1321)实现:用于高德 WebService 的 sig 参数
|
||||||
|
// 仅在 MP-ALIPAY 编译进包,避免影响其它端体积
|
||||||
|
function md5(str) {
|
||||||
|
/* eslint-disable */
|
||||||
|
function cmn(q, a, b, x, s, t) {
|
||||||
|
a = add32(add32(a, q), add32(x, t));
|
||||||
|
return add32((a << s) | (a >>> (32 - s)), b);
|
||||||
|
}
|
||||||
|
function ff(a, b, c, d, x, s, t) { return cmn((b & c) | ((~b) & d), a, b, x, s, t); }
|
||||||
|
function gg(a, b, c, d, x, s, t) { return cmn((b & d) | (c & (~d)), a, b, x, s, t); }
|
||||||
|
function hh(a, b, c, d, x, s, t) { return cmn(b ^ c ^ d, a, b, x, s, t); }
|
||||||
|
function ii(a, b, c, d, x, s, t) { return cmn(c ^ (b | (~d)), a, b, x, s, t); }
|
||||||
|
function md5cycle(x, k) {
|
||||||
|
let a = x[0], b = x[1], c = x[2], d = x[3];
|
||||||
|
|
||||||
|
a = ff(a, b, c, d, k[0], 7, -680876936);
|
||||||
|
d = ff(d, a, b, c, k[1], 12, -389564586);
|
||||||
|
c = ff(c, d, a, b, k[2], 17, 606105819);
|
||||||
|
b = ff(b, c, d, a, k[3], 22, -1044525330);
|
||||||
|
a = ff(a, b, c, d, k[4], 7, -176418897);
|
||||||
|
d = ff(d, a, b, c, k[5], 12, 1200080426);
|
||||||
|
c = ff(c, d, a, b, k[6], 17, -1473231341);
|
||||||
|
b = ff(b, c, d, a, k[7], 22, -45705983);
|
||||||
|
a = ff(a, b, c, d, k[8], 7, 1770035416);
|
||||||
|
d = ff(d, a, b, c, k[9], 12, -1958414417);
|
||||||
|
c = ff(c, d, a, b, k[10], 17, -42063);
|
||||||
|
b = ff(b, c, d, a, k[11], 22, -1990404162);
|
||||||
|
a = ff(a, b, c, d, k[12], 7, 1804603682);
|
||||||
|
d = ff(d, a, b, c, k[13], 12, -40341101);
|
||||||
|
c = ff(c, d, a, b, k[14], 17, -1502002290);
|
||||||
|
b = ff(b, c, d, a, k[15], 22, 1236535329);
|
||||||
|
|
||||||
|
a = gg(a, b, c, d, k[1], 5, -165796510);
|
||||||
|
d = gg(d, a, b, c, k[6], 9, -1069501632);
|
||||||
|
c = gg(c, d, a, b, k[11], 14, 643717713);
|
||||||
|
b = gg(b, c, d, a, k[0], 20, -373897302);
|
||||||
|
a = gg(a, b, c, d, k[5], 5, -701558691);
|
||||||
|
d = gg(d, a, b, c, k[10], 9, 38016083);
|
||||||
|
c = gg(c, d, a, b, k[15], 14, -660478335);
|
||||||
|
b = gg(b, c, d, a, k[4], 20, -405537848);
|
||||||
|
a = gg(a, b, c, d, k[9], 5, 568446438);
|
||||||
|
d = gg(d, a, b, c, k[14], 9, -1019803690);
|
||||||
|
c = gg(c, d, a, b, k[3], 14, -187363961);
|
||||||
|
b = gg(b, c, d, a, k[8], 20, 1163531501);
|
||||||
|
a = gg(a, b, c, d, k[13], 5, -1444681467);
|
||||||
|
d = gg(d, a, b, c, k[2], 9, -51403784);
|
||||||
|
c = gg(c, d, a, b, k[7], 14, 1735328473);
|
||||||
|
b = gg(b, c, d, a, k[12], 20, -1926607734);
|
||||||
|
|
||||||
|
a = hh(a, b, c, d, k[5], 4, -378558);
|
||||||
|
d = hh(d, a, b, c, k[8], 11, -2022574463);
|
||||||
|
c = hh(c, d, a, b, k[11], 16, 1839030562);
|
||||||
|
b = hh(b, c, d, a, k[14], 23, -35309556);
|
||||||
|
a = hh(a, b, c, d, k[1], 4, -1530992060);
|
||||||
|
d = hh(d, a, b, c, k[4], 11, 1272893353);
|
||||||
|
c = hh(c, d, a, b, k[7], 16, -155497632);
|
||||||
|
b = hh(b, c, d, a, k[10], 23, -1094730640);
|
||||||
|
a = hh(a, b, c, d, k[13], 4, 681279174);
|
||||||
|
d = hh(d, a, b, c, k[0], 11, -358537222);
|
||||||
|
c = hh(c, d, a, b, k[3], 16, -722521979);
|
||||||
|
b = hh(b, c, d, a, k[6], 23, 76029189);
|
||||||
|
a = hh(a, b, c, d, k[9], 4, -640364487);
|
||||||
|
d = hh(d, a, b, c, k[12], 11, -421815835);
|
||||||
|
c = hh(c, d, a, b, k[15], 16, 530742520);
|
||||||
|
b = hh(b, c, d, a, k[2], 23, -995338651);
|
||||||
|
|
||||||
|
a = ii(a, b, c, d, k[0], 6, -198630844);
|
||||||
|
d = ii(d, a, b, c, k[7], 10, 1126891415);
|
||||||
|
c = ii(c, d, a, b, k[14], 15, -1416354905);
|
||||||
|
b = ii(b, c, d, a, k[5], 21, -57434055);
|
||||||
|
a = ii(a, b, c, d, k[12], 6, 1700485571);
|
||||||
|
d = ii(d, a, b, c, k[3], 10, -1894986606);
|
||||||
|
c = ii(c, d, a, b, k[10], 15, -1051523);
|
||||||
|
b = ii(b, c, d, a, k[1], 21, -2054922799);
|
||||||
|
a = ii(a, b, c, d, k[8], 6, 1873313359);
|
||||||
|
d = ii(d, a, b, c, k[15], 10, -30611744);
|
||||||
|
c = ii(c, d, a, b, k[6], 15, -1560198380);
|
||||||
|
b = ii(b, c, d, a, k[13], 21, 1309151649);
|
||||||
|
a = ii(a, b, c, d, k[4], 6, -145523070);
|
||||||
|
d = ii(d, a, b, c, k[11], 10, -1120210379);
|
||||||
|
c = ii(c, d, a, b, k[2], 15, 718787259);
|
||||||
|
b = ii(b, c, d, a, k[9], 21, -343485551);
|
||||||
|
|
||||||
|
x[0] = add32(a, x[0]);
|
||||||
|
x[1] = add32(b, x[1]);
|
||||||
|
x[2] = add32(c, x[2]);
|
||||||
|
x[3] = add32(d, x[3]);
|
||||||
|
}
|
||||||
|
function md5blk(s) {
|
||||||
|
const md5blks = [];
|
||||||
|
for (let i = 0; i < 64; i += 4) {
|
||||||
|
md5blks[i >> 2] = s.charCodeAt(i) +
|
||||||
|
(s.charCodeAt(i + 1) << 8) +
|
||||||
|
(s.charCodeAt(i + 2) << 16) +
|
||||||
|
(s.charCodeAt(i + 3) << 24);
|
||||||
|
}
|
||||||
|
return md5blks;
|
||||||
|
}
|
||||||
|
function md5blk_array(a) {
|
||||||
|
const md5blks = [];
|
||||||
|
for (let i = 0; i < 64; i += 4) {
|
||||||
|
md5blks[i >> 2] = a[i] +
|
||||||
|
(a[i + 1] << 8) +
|
||||||
|
(a[i + 2] << 16) +
|
||||||
|
(a[i + 3] << 24);
|
||||||
|
}
|
||||||
|
return md5blks;
|
||||||
|
}
|
||||||
|
function md51(s) {
|
||||||
|
let n = s.length;
|
||||||
|
let state = [1732584193, -271733879, -1732584194, 271733878];
|
||||||
|
let i;
|
||||||
|
for (i = 64; i <= n; i += 64) {
|
||||||
|
md5cycle(state, md5blk(s.substring(i - 64, i)));
|
||||||
|
}
|
||||||
|
s = s.substring(i - 64);
|
||||||
|
const tail = new Array(64).fill(0);
|
||||||
|
for (i = 0; i < s.length; i++) tail[i] = s.charCodeAt(i);
|
||||||
|
tail[i] = 0x80;
|
||||||
|
if (i > 55) {
|
||||||
|
md5cycle(state, md5blk_array(tail));
|
||||||
|
for (i = 0; i < 64; i++) tail[i] = 0;
|
||||||
|
}
|
||||||
|
const tmp = n * 8;
|
||||||
|
tail[56] = tmp & 0xFF;
|
||||||
|
tail[57] = (tmp >>> 8) & 0xFF;
|
||||||
|
tail[58] = (tmp >>> 16) & 0xFF;
|
||||||
|
tail[59] = (tmp >>> 24) & 0xFF;
|
||||||
|
md5cycle(state, md5blk_array(tail));
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
function rhex(n) {
|
||||||
|
const s = '0123456789abcdef';
|
||||||
|
let j, out = '';
|
||||||
|
for (j = 0; j < 4; j++) {
|
||||||
|
out += s.charAt((n >> (j * 8 + 4)) & 0x0F) + s.charAt((n >> (j * 8)) & 0x0F);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
function hex(x) { return x.map(rhex).join(''); }
|
||||||
|
function add32(a, b) { return (a + b) & 0xFFFFFFFF; }
|
||||||
|
return hex(md51(str));
|
||||||
|
/* eslint-enable */
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAmapQuery(params) {
|
||||||
|
const clean = {}
|
||||||
|
Object.keys(params || {}).forEach((k) => {
|
||||||
|
const v = params[k]
|
||||||
|
if (v === undefined || v === null || v === '') return
|
||||||
|
clean[k] = String(v)
|
||||||
|
})
|
||||||
|
// 必须包含 key
|
||||||
|
if (!clean.key) clean.key = AMAP_KEY
|
||||||
|
|
||||||
|
const keys = Object.keys(clean).sort()
|
||||||
|
const query = keys.map((k) => `${k}=${encodeURIComponent(clean[k])}`).join('&')
|
||||||
|
const sig = md5(query + AMAP_SECRET)
|
||||||
|
return `${query}&sig=${sig}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function amapGet(path, params) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const query = buildAmapQuery(params)
|
||||||
|
uni.request({
|
||||||
|
url: `${AMAP_BASE_URL}${path}?${query}`,
|
||||||
|
method: 'GET',
|
||||||
|
header: { 'content-type': 'application/json' },
|
||||||
|
success: (res) => {
|
||||||
|
const data = res && res.data
|
||||||
|
// 高德 WebService:status '1' 表示成功
|
||||||
|
if (data && String(data.status) === '1') {
|
||||||
|
resolve(data)
|
||||||
|
} else {
|
||||||
|
reject(data || { status: '0', info: '请求失败' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
// 腾讯地图Key
|
// 腾讯地图Key
|
||||||
const QQMAP_KEY =
|
const QQMAP_KEY =
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
@@ -566,8 +766,15 @@ function getUserLocation() {
|
|||||||
wx.getLocation({
|
wx.getLocation({
|
||||||
type: 'gcj02',
|
type: 'gcj02',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
const longitude = parseFloat(res.longitude.toFixed(5));
|
const longitude = toFixedNumber(res.longitude, 5)
|
||||||
const latitude = parseFloat(res.latitude.toFixed(5));
|
const latitude = toFixedNumber(res.latitude, 5)
|
||||||
|
if (longitude === null || latitude === null) {
|
||||||
|
reject({
|
||||||
|
code: 'INVALID_COORD',
|
||||||
|
errMsg: 'invalid longitude/latitude from getLocation'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
console.log('地址获取成功');
|
console.log('地址获取成功');
|
||||||
resolve({
|
resolve({
|
||||||
longitude,
|
longitude,
|
||||||
@@ -612,8 +819,15 @@ function getUserLocation() {
|
|||||||
wx.getLocation({
|
wx.getLocation({
|
||||||
type: 'gcj02',
|
type: 'gcj02',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
const longitude = parseFloat(res.longitude.toFixed(5));
|
const longitude = toFixedNumber(res.longitude, 5)
|
||||||
const latitude = parseFloat(res.latitude.toFixed(5));
|
const latitude = toFixedNumber(res.latitude, 5)
|
||||||
|
if (longitude === null || latitude === null) {
|
||||||
|
reject({
|
||||||
|
code: 'INVALID_COORD',
|
||||||
|
errMsg: 'invalid longitude/latitude from getLocation'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
console.log('地址获取成功');
|
console.log('地址获取成功');
|
||||||
resolve({
|
resolve({
|
||||||
longitude,
|
longitude,
|
||||||
@@ -635,8 +849,15 @@ function getUserLocation() {
|
|||||||
uni.getLocation({
|
uni.getLocation({
|
||||||
type: 'gcj02',
|
type: 'gcj02',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
const longitude = parseFloat(res.longitude.toFixed(5));
|
const longitude = toFixedNumber(res.longitude, 5)
|
||||||
const latitude = parseFloat(res.latitude.toFixed(5));
|
const latitude = toFixedNumber(res.latitude, 5)
|
||||||
|
if (longitude === null || latitude === null) {
|
||||||
|
reject({
|
||||||
|
code: 'INVALID_COORD',
|
||||||
|
errMsg: 'invalid longitude/latitude from getLocation'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
console.log('地址获取成功');
|
console.log('地址获取成功');
|
||||||
resolve({
|
resolve({
|
||||||
longitude,
|
longitude,
|
||||||
@@ -665,6 +886,41 @@ function getUserLocation() {
|
|||||||
// 逆地理编码 - 根据经纬度获取地址信息
|
// 逆地理编码 - 根据经纬度获取地址信息
|
||||||
function getRegeo(longitude, latitude) {
|
function getRegeo(longitude, latitude) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
// 支付宝小程序:高德逆地理编码
|
||||||
|
const lng = toFixedNumber(longitude, 6)
|
||||||
|
const lat = toFixedNumber(latitude, 6)
|
||||||
|
if (lng === null || lat === null) {
|
||||||
|
reject({ success: false, message: '无效经纬度' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
amapGet('geocode/regeo', {
|
||||||
|
location: `${lng},${lat}`,
|
||||||
|
radius: 1000,
|
||||||
|
extensions: 'base'
|
||||||
|
}).then((data) => {
|
||||||
|
const regeocode = data.regeocode || {}
|
||||||
|
const ac = regeocode.addressComponent || {}
|
||||||
|
resolve({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
formatted_address: regeocode.formatted_address || '',
|
||||||
|
addressComponent: {
|
||||||
|
city: Array.isArray(ac.city) ? '' : (ac.city || ''),
|
||||||
|
district: ac.district || '',
|
||||||
|
province: ac.province || '',
|
||||||
|
street: (ac.streetNumber && ac.streetNumber.street) || '',
|
||||||
|
street_number: (ac.streetNumber && ac.streetNumber.number) || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('支付宝-高德逆地理编码失败:', err)
|
||||||
|
reject({ success: false, message: err.info || err.message || '逆地理编码失败' })
|
||||||
|
})
|
||||||
|
return
|
||||||
|
// #endif
|
||||||
|
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
// H5环境:使用JSONP方式调用腾讯地图API,避免跨域问题
|
// H5环境:使用JSONP方式调用腾讯地图API,避免跨域问题
|
||||||
const callbackName = `qqmap_geocoder_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
const callbackName = `qqmap_geocoder_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||||
@@ -756,6 +1012,50 @@ function getRegeo(longitude, latitude) {
|
|||||||
// 搜索周边POI
|
// 搜索周边POI
|
||||||
function getPoiAround(longitude, latitude, keyword = '', radius = 1000) {
|
function getPoiAround(longitude, latitude, keyword = '', radius = 1000) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
// 支付宝小程序:高德周边检索
|
||||||
|
const lng = toFixedNumber(longitude, 6)
|
||||||
|
const lat = toFixedNumber(latitude, 6)
|
||||||
|
if (lng === null || lat === null) {
|
||||||
|
reject({ success: false, message: '无效经纬度' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
amapGet('place/around', {
|
||||||
|
location: `${lng},${lat}`,
|
||||||
|
keywords: keyword || '',
|
||||||
|
radius: radius || 1000,
|
||||||
|
sortrule: 'distance',
|
||||||
|
offset: 10,
|
||||||
|
page: 1,
|
||||||
|
extensions: 'base'
|
||||||
|
}).then((data) => {
|
||||||
|
const pois = Array.isArray(data.pois) ? data.pois : []
|
||||||
|
const list = pois.map((p) => {
|
||||||
|
const loc = (p.location || '').split(',')
|
||||||
|
const plng = toFixedNumber(loc[0], 6)
|
||||||
|
const plat = toFixedNumber(loc[1], 6)
|
||||||
|
return {
|
||||||
|
id: p.id || null,
|
||||||
|
title: p.name || null,
|
||||||
|
latitude: plat,
|
||||||
|
longitude: plng,
|
||||||
|
address: p.address || null,
|
||||||
|
category: p.type || null,
|
||||||
|
tel: p.tel || null,
|
||||||
|
adcode: p.adcode || null,
|
||||||
|
city: p.cityname || null,
|
||||||
|
district: p.adname || null,
|
||||||
|
province: p.pname || null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
resolve({ success: true, data: list })
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('支付宝-高德搜索POI失败:', err)
|
||||||
|
reject({ success: false, message: err.info || err.message || '搜索POI失败' })
|
||||||
|
})
|
||||||
|
return
|
||||||
|
// #endif
|
||||||
|
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
// H5环境:使用JSONP方式调用腾讯地图API
|
// H5环境:使用JSONP方式调用腾讯地图API
|
||||||
const callbackName = `qqmap_search_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
const callbackName = `qqmap_search_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||||
@@ -843,6 +1143,48 @@ function getPoiAround(longitude, latitude, keyword = '', radius = 1000) {
|
|||||||
// 计算距离(异步)
|
// 计算距离(异步)
|
||||||
function calculateDistance(from, to) {
|
function calculateDistance(from, to) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
// 支付宝小程序:高德距离计算(支持多个目的地)
|
||||||
|
if (!from || !to) {
|
||||||
|
reject({ success: false, message: '参数缺失' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const fromLng = toFixedNumber((from && (from.longitude !== undefined ? from.longitude : from.lng)), 6)
|
||||||
|
const fromLat = toFixedNumber((from && (from.latitude !== undefined ? from.latitude : from.lat)), 6)
|
||||||
|
if (fromLng === null || fromLat === null) {
|
||||||
|
reject({ success: false, message: '无效起点坐标' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const toArr = Array.isArray(to) ? to : [to]
|
||||||
|
const origins = toArr.map((p) => {
|
||||||
|
const lng = toFixedNumber((p && (p.longitude !== undefined ? p.longitude : p.lng)), 6)
|
||||||
|
const lat = toFixedNumber((p && (p.latitude !== undefined ? p.latitude : p.lat)), 6)
|
||||||
|
return (lng === null || lat === null) ? null : `${lng},${lat}`
|
||||||
|
}).filter(Boolean)
|
||||||
|
|
||||||
|
if (!origins.length) {
|
||||||
|
reject({ success: false, message: '无效终点坐标' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 高德接口:origins(多个) + destination(单个)
|
||||||
|
amapGet('distance', {
|
||||||
|
origins: origins.join('|'),
|
||||||
|
destination: `${fromLng},${fromLat}`,
|
||||||
|
type: 0 // 0:驾车距离;步行/骑行需要其它接口,这里保持与原来“直线/近似”用途一致
|
||||||
|
}).then((data) => {
|
||||||
|
const results = Array.isArray(data.results) ? data.results : []
|
||||||
|
const distances = results.map((r) => Number(r.distance)).filter((n) => Number.isFinite(n))
|
||||||
|
// 保持与原实现一致:始终返回数组(即使只传了一个目的地)
|
||||||
|
resolve({ success: true, data: distances })
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('支付宝-高德计算距离失败:', err)
|
||||||
|
reject({ success: false, message: err.info || err.message || '计算距离失败' })
|
||||||
|
})
|
||||||
|
return
|
||||||
|
// #endif
|
||||||
|
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
// H5环境:使用JSONP方式调用腾讯地图API
|
// H5环境:使用JSONP方式调用腾讯地图API
|
||||||
const callbackName = `qqmap_distance_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
const callbackName = `qqmap_distance_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||||
@@ -935,6 +1277,44 @@ function calculateDistanceSync(lat1, lng1, lat2, lng2) {
|
|||||||
// 关键词提示
|
// 关键词提示
|
||||||
function getSuggestion(keyword, region = '全国') {
|
function getSuggestion(keyword, region = '全国') {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
// 支付宝小程序:高德输入提示
|
||||||
|
if (!keyword) {
|
||||||
|
resolve({ success: true, data: [] })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
amapGet('assistant/inputtips', {
|
||||||
|
keywords: keyword,
|
||||||
|
city: region && region !== '全国' ? region : '',
|
||||||
|
citylimit: region && region !== '全国' ? 1 : 0
|
||||||
|
}).then((data) => {
|
||||||
|
const tips = Array.isArray(data.tips) ? data.tips : []
|
||||||
|
const list = tips.map((t) => {
|
||||||
|
const loc = (t.location || '').split(',')
|
||||||
|
const lng = toFixedNumber(loc[0], 6)
|
||||||
|
const lat = toFixedNumber(loc[1], 6)
|
||||||
|
return {
|
||||||
|
adcode: t.adcode || null,
|
||||||
|
address: t.address || null,
|
||||||
|
category: t.type || null,
|
||||||
|
city: t.cityname || null,
|
||||||
|
district: t.district || null,
|
||||||
|
id: t.id || null,
|
||||||
|
latitude: lat,
|
||||||
|
longitude: lng,
|
||||||
|
province: t.pname || null,
|
||||||
|
title: t.name || null,
|
||||||
|
type: t.type || null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
resolve({ success: true, data: list })
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('支付宝-高德关键词提示失败:', err)
|
||||||
|
reject({ success: false, message: err.info || err.message || '关键词提示失败' })
|
||||||
|
})
|
||||||
|
return
|
||||||
|
// #endif
|
||||||
|
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
// H5环境:使用JSONP方式调用腾讯地图API
|
// H5环境:使用JSONP方式调用腾讯地图API
|
||||||
const callbackName = `qqmap_suggestion_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
const callbackName = `qqmap_suggestion_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user