支付宝兼容
This commit is contained in:
+73
-23
@@ -3,40 +3,88 @@
|
||||
<!-- 地图容器 -->
|
||||
<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">
|
||||
<!-- 覆盖在地图上的广告轮播(使用 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 }">
|
||||
<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"
|
||||
>
|
||||
<!-- 覆盖在地图上的广告轮播(使用 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 class="center-location-marker" v-if="!props.hideMapOverlays">
|
||||
<cover-image src="/static/location-icon.png" class="center-marker-icon"></cover-image>
|
||||
<cover-view
|
||||
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 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-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 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 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 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>
|
||||
</map>
|
||||
@@ -508,15 +556,16 @@ const handleSearch = () => {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
|
||||
// #ifdef H5
|
||||
height:78vh;
|
||||
height: 78vh;
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
height: 72vh;
|
||||
// #endif
|
||||
|
||||
|
||||
&.full-width {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
@@ -682,6 +731,7 @@ const handleSearch = () => {
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
|
||||
.indicator-dot {
|
||||
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'">
|
||||
<text class="badge-text">{{ $t('order.memberOrder') }}</text>
|
||||
</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>
|
||||
<text class="badge-text">{{ $t('order.wxPay') }}</text>
|
||||
<text class="divider">|</text>
|
||||
|
||||
@@ -9,12 +9,14 @@ export const getCouponsByPosition = (positionId) => {
|
||||
}
|
||||
|
||||
// 创建优惠券支付订单
|
||||
export const createCouponPayment = (couponId) => {
|
||||
export const createCouponPayment = (couponId, paymentPlatform) => {
|
||||
return request({
|
||||
url: '/app/coupon/pay',
|
||||
method: 'post',
|
||||
data: {
|
||||
couponId
|
||||
couponId,
|
||||
// 支付平台类型:WECHAT / ALIPAY / ANTOM(不传则后端默认 WECHAT)
|
||||
...(paymentPlatform ? { paymentPlatform } : {})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import request from '../http'
|
||||
|
||||
// 创建会员卡支付订单
|
||||
export const createMemberCardPayment = (memberCardId) => {
|
||||
export const createMemberCardPayment = (memberCardId, paymentPlatform) => {
|
||||
return request({
|
||||
url: '/app/member/pay',
|
||||
method: 'post',
|
||||
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 = () => {
|
||||
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 H5 支付订单
|
||||
|
||||
@@ -33,14 +33,10 @@ export const getProductDetail = (id) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建商品支付订单
|
||||
* @param {Object} data - 订单数据
|
||||
* @param {Array} data.items - 订单项列表 [{skuId, quantity}]
|
||||
* @param {string} data.receiverName - 收件人姓名
|
||||
* @param {string} data.receiverPhone - 收件人手机号
|
||||
* @param {string} data.receiverAddress - 收件人详细地址
|
||||
* @param {string} data.remark - 用户备注(可选)
|
||||
* @returns {Promise} 微信支付参数
|
||||
* 创建商品支付订单(多支付平台)
|
||||
* 对应《商品购买多支付平台方案》:
|
||||
* paymentPlatform: WECHAT / ALIPAY / ANTOM
|
||||
* 其他字段见文档
|
||||
*/
|
||||
export const createProductOrder = (data) => {
|
||||
return request({
|
||||
|
||||
+27
-1
@@ -1,7 +1,7 @@
|
||||
import request from '../http'
|
||||
import { URL, appid } from '../url'
|
||||
|
||||
// 用户登录
|
||||
// 旧登录接口(兼容保留,后端将逐步废弃)
|
||||
export const login = (data) => {
|
||||
return request({
|
||||
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) => {
|
||||
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 表示不打印日志
|
||||
export const CONSOLE_CONFIG = {
|
||||
// 是否启用 console.log
|
||||
enableLog: false,
|
||||
enableLog: true,
|
||||
// 是否启用 console.warn
|
||||
enableWarn: false,
|
||||
// 是否启用 console.error
|
||||
|
||||
+9
-2
@@ -1,8 +1,15 @@
|
||||
import {
|
||||
URL,
|
||||
appid
|
||||
appid,
|
||||
ZFBappid
|
||||
} from './url'
|
||||
|
||||
// 根据运行平台选择正确的小程序 appid(后端通常依赖该 header 做平台识别)
|
||||
let platformAppid = appid
|
||||
// #ifdef MP-ALIPAY
|
||||
platformAppid = ZFBappid
|
||||
// #endif
|
||||
|
||||
// 获取多语言翻译文本
|
||||
const getLoadingText = () => {
|
||||
try {
|
||||
@@ -31,7 +38,7 @@ const request = (option) => {
|
||||
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'),
|
||||
...option.headers,
|
||||
'appid': appid,
|
||||
'appid': platformAppid,
|
||||
'Authorization': "Bearer " + uni.getStorageSync('token'),
|
||||
'Clientid': uni.getStorageSync('client_id'),
|
||||
'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://manager.fdzpower.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 appid = "wx2165f0be356ae7a9" //微信小程序appid
|
||||
|
||||
@@ -211,6 +211,8 @@ export default {
|
||||
depositFree: 'Deposit-free',
|
||||
whitelistOrder: 'Whitelist Order',
|
||||
memberOrder: 'Member Order',
|
||||
aliPay: 'AliPay',
|
||||
antomPay: 'Antom Pay',
|
||||
wxPay: 'WeChat Pay',
|
||||
depositPay: 'Deposit Pay',
|
||||
paymentInProgress: 'Payment in Progress',
|
||||
@@ -307,6 +309,8 @@ export default {
|
||||
cooperation: 'Partner',
|
||||
settings: 'Settings',
|
||||
userAgreement: 'Terms',
|
||||
settinguserAgreement:'Terms',
|
||||
settinguserprivacyPolicy:'Privacy',
|
||||
privacyPolicy: 'Privacy',
|
||||
version: 'v',
|
||||
logout: 'Logout',
|
||||
|
||||
@@ -211,6 +211,8 @@ export default {
|
||||
depositFree: 'Sewa Tanpa Deposit',
|
||||
whitelistOrder: 'Pesanan Whitelist',
|
||||
memberOrder: 'Pesanan Anggota',
|
||||
aliPay: 'Alipay',
|
||||
antomPay: 'Antom Pay',
|
||||
wxPay: 'Pembayaran WeChat',
|
||||
depositPay: 'Sewa dengan Deposit',
|
||||
paymentInProgress: 'Sedang membayar',
|
||||
@@ -307,6 +309,8 @@ export default {
|
||||
cooperation: 'Kerja Sama dan Keanggotaan',
|
||||
settings: 'Pengaturan',
|
||||
userAgreement: '《Perjanjian Pengguna》',
|
||||
settinguserAgreement:'Perjanjian Pengguna',
|
||||
settinguserprivacyPolicy:'Kebijakan Privasi',
|
||||
privacyPolicy: '《Kebijakan Privasi》',
|
||||
version: 'v',
|
||||
logout: 'Keluar',
|
||||
|
||||
+5
-1
@@ -84,7 +84,7 @@ export default {
|
||||
scanToUse: '扫码使用',
|
||||
personalCenter: '个人中心',
|
||||
useGuide: '使用指南',
|
||||
buyDevice: '充电宝定制',
|
||||
buyDevice: '产品定制',
|
||||
navigate: '导航',
|
||||
relocate: '重新定位',
|
||||
search: '搜索',
|
||||
@@ -210,6 +210,8 @@ export default {
|
||||
depositFree: '免押租借',
|
||||
whitelistOrder: '白名单订单',
|
||||
memberOrder: '会员订单',
|
||||
aliPay: '支付宝支付',
|
||||
antomPay: '海外支付',
|
||||
wxPay: '微信支付',
|
||||
depositPay: '押金租借',
|
||||
paymentInProgress: '支付中',
|
||||
@@ -306,7 +308,9 @@ export default {
|
||||
cooperation: '合作加盟',
|
||||
settings: '设置',
|
||||
userAgreement: '《用户协议》',
|
||||
settinguserAgreement: '用户协议',
|
||||
privacyPolicy: '《隐私政策》',
|
||||
settinguserprivacyPolicy: '隐私政策',
|
||||
version: 'v',
|
||||
logout: '退出登录',
|
||||
confirmLogout: '确认退出登录?',
|
||||
|
||||
+3
-6
@@ -69,12 +69,9 @@
|
||||
"requiredPrivateInfos" : [ "getLocation", "chooseLocation" ]
|
||||
},
|
||||
"mp-alipay" : {
|
||||
"component2":true,
|
||||
"transpile":[
|
||||
"uview-ui",
|
||||
"vue-i18n"
|
||||
],
|
||||
"skia":true,
|
||||
"component2" : true,
|
||||
"transpile" : [ "uview-ui", "vue-i18n" ],
|
||||
"skia" : true,
|
||||
"usingComponents" : true,
|
||||
"appid" : "2021006117693332",
|
||||
"unipush" : {
|
||||
|
||||
+36
-7
@@ -91,7 +91,7 @@
|
||||
<!-- 底部操作区 -->
|
||||
<view class="footer">
|
||||
<view class="rent-button" :class="{ 'return-button': hasActiveOrder }"
|
||||
@click="handleRent(isWechatMiniProgram ? 'wx-score-pay' : 'wx-pay')">
|
||||
@click="handleRent">
|
||||
<text>{{ hasActiveOrder ? $t('order.returnDevice') : getRentButtonText() }}</text>
|
||||
</view>
|
||||
<!-- 微信支付分标识仅在微信小程序环境显示 -->
|
||||
@@ -176,6 +176,8 @@
|
||||
const phoneNumber = ref('')
|
||||
const showPhoneAuthPopup = ref(false)
|
||||
const isWechatMiniProgram = ref(false)
|
||||
const isAlipayMiniProgram = ref(false)
|
||||
const isH5 = ref(false)
|
||||
|
||||
// 生命周期 onLoad 钩子
|
||||
onLoad(async (options) => {
|
||||
@@ -193,13 +195,27 @@
|
||||
uni.setNavigationBarTitle({
|
||||
title: t('device.deviceInfo')
|
||||
})
|
||||
// 检测当前运行环境
|
||||
// 检测当前运行环境:微信小程序 / 支付宝小程序 / H5
|
||||
// #ifdef MP-WEIXIN
|
||||
isWechatMiniProgram.value = true
|
||||
isAlipayMiniProgram.value = false
|
||||
isH5.value = false
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
isWechatMiniProgram.value = false
|
||||
isAlipayMiniProgram.value = true
|
||||
isH5.value = false
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
isWechatMiniProgram.value = false
|
||||
isAlipayMiniProgram.value = false
|
||||
isH5.value = true
|
||||
// #endif
|
||||
console.log('当前运行环境:', {
|
||||
isWechatMiniProgram: isWechatMiniProgram.value,
|
||||
isAlipayMiniProgram: isAlipayMiniProgram.value,
|
||||
isH5: isH5.value
|
||||
})
|
||||
await checkUserPhone()
|
||||
await fetchDeviceInfo()
|
||||
})
|
||||
@@ -437,7 +453,7 @@
|
||||
}
|
||||
|
||||
// 处理租借操作
|
||||
const handleRent = (payWay) => {
|
||||
const handleRent = () => {
|
||||
if (!isLoggedIn.value) {
|
||||
showLoginTip()
|
||||
return
|
||||
@@ -448,9 +464,22 @@
|
||||
showPhoneAuthPopup.value = true
|
||||
return
|
||||
}
|
||||
|
||||
// 提交订单
|
||||
submitRentOrder(payWay)
|
||||
|
||||
// 根据运行环境选择不同的租借/支付流程
|
||||
// 微信小程序:走微信支付分免押租借
|
||||
if (isWechatMiniProgram.value) {
|
||||
submitRentOrder('wx-score-pay')
|
||||
return
|
||||
}
|
||||
|
||||
// 支付宝小程序:走押金租借,后续在支付页内调起支付宝支付
|
||||
if (isAlipayMiniProgram.value) {
|
||||
submitRentOrder('wx-pay')
|
||||
return
|
||||
}
|
||||
|
||||
// H5 等其他环境:统一走押金租借,支付页内根据平台选择支付方式(Antom 等)
|
||||
submitRentOrder('wx-pay')
|
||||
}
|
||||
|
||||
// 获取价格单位文本
|
||||
@@ -603,7 +632,7 @@
|
||||
|
||||
// 跳转到订单支付页面
|
||||
uni.redirectTo({
|
||||
url: `/pages/order/payment?orderId=${order.orderId}&packagePrice=${packagePrice}&totalAmount=${totalAmount}&depositAmount=${deposit}${deviceInfo.value && deviceInfo.value.feeConfig ? '&feeConfig=' + encodeURIComponent(deviceInfo.value.feeConfig) : ''}`
|
||||
url: `/subPackages/order/payment?orderId=${order.orderId}&packagePrice=${packagePrice}&totalAmount=${totalAmount}&depositAmount=${deposit}${deviceInfo.value && deviceInfo.value.feeConfig ? '&feeConfig=' + encodeURIComponent(deviceInfo.value.feeConfig) : ''}`
|
||||
})
|
||||
|
||||
} else if (payWay == 'wx-score-pay') {
|
||||
|
||||
+192
-13
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<view class="container fullscreen">
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="custom-navbar" :style="navbarStyle">
|
||||
<view class="navbar-content" :style="{ height: navBarHeight + 'px' }">
|
||||
<text class="navbar-title">{{ $t('home.title') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 顶部信息区域(通知、招商等) -->
|
||||
<view class="top-info-section" :style="{ top: (statusBarHeight + navBarHeight) + 'px' }">
|
||||
<view class="top-info-section" :style="topInfoSectionStyle">
|
||||
<!-- 通知栏 -->
|
||||
<view class="notice-wrapper" v-if="noticeText" @click="openNoticePopup">
|
||||
<uv-notice-bar :text="noticeText" :speed="50" :show-icon="true" color="#07c160" bg-color="#E8F8EF"
|
||||
@@ -17,14 +17,31 @@
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<view class="main-content" :style="{ paddingTop: (navBarHeight + noticeHeight) + 'px' }">
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<view class="main-content" :style="{ paddingTop: (statusBarHeight + navBarHeight + noticeHeight) + 'px' }">
|
||||
<!-- #endif -->
|
||||
<!-- 全屏地图组件 -->
|
||||
<!-- 支付宝小程序使用专用组件 -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<MapComponentAlipay v-if="!isLoading && userLocation" ref="mapRef" :userLocation="userLocation"
|
||||
:positionList="positionList" :filteredPositions="filteredPositions" :searchKeyword="searchKeyword"
|
||||
:enableMarkers="true" :bannerImages="bannerImages"
|
||||
:hideMapOverlays="showGuidePopup || showNoticePopup || showActivityPopup" @relocate="handleRelocate"
|
||||
@scan="handleScan" @showList="showLocationList" @markerTap="selectPosition"
|
||||
@mapCenterChange="onMapCenterChange" @bannerClick="handleBannerClick" @guide="openGuidePopup" />
|
||||
<!-- #endif -->
|
||||
<!-- 非支付宝小程序使用通用组件 -->
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<MapComponent v-if="!isLoading && userLocation" ref="mapRef" :userLocation="userLocation"
|
||||
:positionList="positionList" :filteredPositions="filteredPositions" :searchKeyword="searchKeyword"
|
||||
:enableMarkers="true" :bannerImages="bannerImages"
|
||||
:hideMapOverlays="showGuidePopup || showNoticePopup || showActivityPopup" @relocate="handleRelocate"
|
||||
@scan="handleScan" @showList="showLocationList" @markerTap="selectPosition"
|
||||
@mapCenterChange="onMapCenterChange" @bannerClick="handleBannerClick" @guide="openGuidePopup" />
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 地图加载状态 -->
|
||||
<view v-if="isLoading || !userLocation" class="map-loading-placeholder">
|
||||
@@ -79,7 +96,7 @@
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 手机号授权弹窗 -->
|
||||
<!-- 手机号校验/授权弹窗(扫码前校验) -->
|
||||
<view class="phone-auth-popup" v-if="showPhoneAuthPopup">
|
||||
<view class="popup-mask" @click.stop="showPhoneAuthPopup = false"></view>
|
||||
<view class="popup-content">
|
||||
@@ -90,9 +107,25 @@
|
||||
<view class="auth-desc">
|
||||
<text>{{ $t('auth.authDesc') }}</text>
|
||||
</view>
|
||||
<button class="auth-btn" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
|
||||
<!-- 微信:获取手机号 code 并上报后端绑定 -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button class="auth-btn" open-type="getPhoneNumber" @getphonenumber="onWxGetPhoneNumber">
|
||||
<text>{{ $t('auth.getPhoneNumber') }}</text>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 支付宝:先授权 phoneNumber,再调用 my.getPhoneNumber,上报后端解密并校验一致性 -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<button
|
||||
class="auth-btn"
|
||||
open-type="getAuthorize"
|
||||
scope="phoneNumber"
|
||||
@getAuthorize="onAliAuthorizePhoneNumber"
|
||||
@error="onAliAuthorizePhoneNumberError"
|
||||
>
|
||||
<text>{{ $t('auth.getPhoneNumber') }}</text>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<view class="auth-cancel" @click="showPhoneAuthPopup = false">
|
||||
<text>{{ $t('auth.notNow') }}</text>
|
||||
</view>
|
||||
@@ -187,7 +220,10 @@
|
||||
} from 'vue'
|
||||
import {
|
||||
getQueryString,
|
||||
wxLogin
|
||||
wxLogin,
|
||||
getUserInfo,
|
||||
getUserPhoneNumber,
|
||||
getAlipayUserPhoneNumber
|
||||
} from '../../util/index.js'
|
||||
import {
|
||||
URL
|
||||
@@ -219,6 +255,9 @@
|
||||
// 同样需要使用相对路径引入组件
|
||||
// 注意:从 pages/index/ 目录访问 components/ 需要使用 ../../components/ 路径
|
||||
import MapComponent from '../../components/MapComponent.vue'
|
||||
// #ifdef MP-ALIPAY
|
||||
import MapComponentAlipay from '../../components/MapComponentAlipay.vue'
|
||||
// #endif
|
||||
import LocationListSheet from '../../components/LocationListSheet.vue'
|
||||
import {
|
||||
useI18n
|
||||
@@ -244,6 +283,8 @@
|
||||
const isExpanded = ref(false)
|
||||
const isLoading = ref(false)
|
||||
const showPhoneAuthPopup = ref(false)
|
||||
const pendingScan = ref(false) // 是否是扫码流程触发的手机号校验
|
||||
const expectedUserPhone = ref('') // 当前登录态下绑定的手机号(用于支付宝一致性校验)
|
||||
const isLocationInitialized = ref(false)
|
||||
const showLocationPopup = ref(false)
|
||||
const isRelocating = ref(false) // 防抖标志:是否正在重新定位
|
||||
@@ -259,6 +300,30 @@
|
||||
const navBarHeight = ref(44) // 默认导航栏内容高度
|
||||
const noticeHeight = ref(0) // 通知栏高度
|
||||
|
||||
// 导航栏样式:支付宝小程序不设置 paddingTop
|
||||
const navbarStyle = computed(() => {
|
||||
// #ifdef MP-ALIPAY
|
||||
// 支付宝小程序:不设置 paddingTop
|
||||
return { paddingTop: '0px' }
|
||||
// #endif
|
||||
// #ifndef MP-ALIPAY
|
||||
// 非支付宝小程序:设置 statusBarHeight
|
||||
return { paddingTop: statusBarHeight.value + 'px' }
|
||||
// #endif
|
||||
})
|
||||
|
||||
// 顶部信息区域样式:支付宝小程序使用 paddingTop,非支付宝小程序使用 top
|
||||
const topInfoSectionStyle = computed(() => {
|
||||
// #ifdef MP-ALIPAY
|
||||
// 支付宝小程序:使用 paddingTop
|
||||
return { top: ( navBarHeight.value) + 'px' }
|
||||
// #endif
|
||||
// #ifndef MP-ALIPAY
|
||||
// 非支付宝小程序:使用 top
|
||||
return { top: (statusBarHeight.value + navBarHeight.value) + 'px' }
|
||||
// #endif
|
||||
})
|
||||
|
||||
// 使用指南步骤
|
||||
const guideSteps = ref([{
|
||||
title: '扫码使用',
|
||||
@@ -474,11 +539,13 @@
|
||||
|
||||
// 距离格式化函数
|
||||
const formatDistance = (distanceInMeters) => {
|
||||
if (distanceInMeters < 1000) {
|
||||
return `${Math.round(distanceInMeters)}m`
|
||||
} else {
|
||||
return `${(distanceInMeters / 1000).toFixed(1)}km`
|
||||
}
|
||||
// 支付宝小程序等环境下,可能传入 String/BigInt/异常对象,导致 toFixed 不存在
|
||||
let meters = distanceInMeters
|
||||
if (typeof meters === 'bigint') meters = Number(meters)
|
||||
meters = Number(meters)
|
||||
if (!Number.isFinite(meters) || meters < 0) return ''
|
||||
if (meters < 1000) return `${Math.round(meters)}m`
|
||||
return `${(meters / 1000).toFixed(1)}km`
|
||||
}
|
||||
|
||||
|
||||
@@ -964,7 +1031,58 @@
|
||||
}
|
||||
}
|
||||
|
||||
const handleScan = async () => {
|
||||
const normalizePhone = (p) => {
|
||||
if (!p) return ''
|
||||
return String(p).replace(/\s+/g, '').replace(/^\+?86/, '')
|
||||
}
|
||||
|
||||
const isLoggedIn = () => {
|
||||
const token = uni.getStorageSync('token')
|
||||
return !!token
|
||||
}
|
||||
|
||||
const precheckBeforeScan = async () => {
|
||||
if (!isLoggedIn()) {
|
||||
redirectToLogin()
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const userInfoRes = await getUserInfo()
|
||||
console.log('userInfoRes', userInfoRes.data.phone);
|
||||
|
||||
const phone = userInfoRes.data.phone || ''
|
||||
expectedUserPhone.value = phone
|
||||
console.log('expectedUserPhone', expectedUserPhone.value);
|
||||
|
||||
|
||||
// 没有手机号:弹窗引导获取/绑定
|
||||
if (!phone) {
|
||||
pendingScan.value = true
|
||||
showPhoneAuthPopup.value = true
|
||||
return false
|
||||
}
|
||||
|
||||
// // 支付宝:即使已有手机号,也要求扫码前做一次一致性校验(或命中缓存)
|
||||
// // #ifdef MP-ALIPAY
|
||||
// const cached = uni.getStorageSync('alipay_phone_verified')
|
||||
// const cachedPhone = cached && cached.phone ? cached.phone : ''
|
||||
// if (cachedPhone && normalizePhone(cachedPhone) === normalizePhone(phone)) {
|
||||
// return true
|
||||
// }
|
||||
// pendingScan.value = true
|
||||
// showPhoneAuthPopup.value = true
|
||||
// return false
|
||||
// // #endif
|
||||
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('扫码前检查用户信息失败:', e)
|
||||
return true // 不阻断,避免影响主流程;后续租借仍会二次校验
|
||||
}
|
||||
}
|
||||
|
||||
const doScan = async () => {
|
||||
// #ifdef H5
|
||||
uni.navigateTo({
|
||||
url: '/pages/scan/index'
|
||||
@@ -987,6 +1105,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
const handleScan = async () => {
|
||||
const ok = await precheckBeforeScan()
|
||||
if (!ok) return
|
||||
await doScan()
|
||||
}
|
||||
|
||||
const processScanResult = async (scanResult) => {
|
||||
try {
|
||||
console.log('===== 处理扫码结果 =====');
|
||||
@@ -1162,12 +1286,67 @@
|
||||
showLocationPopup.value = false
|
||||
}
|
||||
|
||||
const onGetPhoneNumber = (e) => {
|
||||
if (e.detail.errMsg === 'getPhoneNumber:ok') {
|
||||
// 微信:绑定手机号后继续扫码
|
||||
const onWxGetPhoneNumber = async (e) => {
|
||||
try {
|
||||
if (!e || e.detail.errMsg !== 'getPhoneNumber:ok') {
|
||||
return
|
||||
}
|
||||
await getUserPhoneNumber(e.detail.code)
|
||||
showPhoneAuthPopup.value = false
|
||||
if (pendingScan.value) {
|
||||
pendingScan.value = false
|
||||
await doScan()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('微信绑定手机号失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 支付宝:获取手机号(后端解密)并与 userInfo.phone 对比,一致才允许继续扫码
|
||||
const onAliAuthorizePhoneNumber = async (e) => {
|
||||
// #ifdef MP-ALIPAY
|
||||
try {
|
||||
console.log('[ALIPAY] getAuthorize(phoneNumber) event:', e)
|
||||
uni.showLoading({ title: t('common.processing') })
|
||||
const res = await getAlipayUserPhoneNumber()
|
||||
const aliPhone = res?.data?.phoneNumber || res?.data?.phone || res?.phoneNumber || ''
|
||||
if (!aliPhone) {
|
||||
throw new Error('未获取到手机号')
|
||||
}
|
||||
|
||||
const expected = expectedUserPhone.value
|
||||
if (expected && normalizePhone(aliPhone) !== normalizePhone(expected)) {
|
||||
uni.showModal({
|
||||
title: t('common.tips'),
|
||||
content: '当前支付宝授权手机号与账号绑定手机号不一致,无法扫码租借,请更换账号或联系管理员。',
|
||||
showCancel: false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 缓存本次校验结果(避免每次扫码都弹)
|
||||
uni.setStorageSync('alipay_phone_verified', { phone: aliPhone, ts: Date.now() })
|
||||
|
||||
showPhoneAuthPopup.value = false
|
||||
if (pendingScan.value) {
|
||||
pendingScan.value = false
|
||||
await doScan()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('支付宝手机号校验失败:', err)
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
const onAliAuthorizePhoneNumberError = (e) => {
|
||||
// #ifdef MP-ALIPAY
|
||||
console.error('支付宝手机号授权失败:', e)
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 使用指南弹窗控制
|
||||
const openPopup = () => {
|
||||
uni.navigateTo({
|
||||
|
||||
@@ -134,8 +134,13 @@
|
||||
}
|
||||
|
||||
const formatDistance = (meters) => {
|
||||
if (meters < 1000) return `${Math.round(meters)}m`
|
||||
return `${(meters / 1000).toFixed(1)}km`
|
||||
// 兼容支付宝小程序等环境:保证始终对 Number 调用 toFixed
|
||||
let m = meters
|
||||
if (typeof m === 'bigint') m = Number(m)
|
||||
m = Number(m)
|
||||
if (!Number.isFinite(m) || m < 0) return ''
|
||||
if (m < 1000) return `${Math.round(m)}m`
|
||||
return `${(m / 1000).toFixed(1)}km`
|
||||
}
|
||||
|
||||
const setTab = (name) => {
|
||||
|
||||
@@ -176,6 +176,8 @@
|
||||
|
||||
<view class="form-item">
|
||||
<text class="form-label">收货地区</text>
|
||||
<!-- 非支付宝小程序:使用多列 picker -->
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<picker mode="multiSelector" :range="regionColumns" range-key="name" :value="regionIndexes"
|
||||
@change="onRegionChange" @columnchange="onRegionColumnChange">
|
||||
<view class="form-input region-selector">
|
||||
@@ -187,6 +189,19 @@
|
||||
<text class="arrow-icon">›</text>
|
||||
</view>
|
||||
</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 class="form-item">
|
||||
@@ -254,6 +269,49 @@
|
||||
</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>
|
||||
</template>
|
||||
@@ -340,6 +398,7 @@
|
||||
const showSkuPopup = ref(false)
|
||||
const showAddressPopup = ref(false)
|
||||
const showAddressDisplay = ref(false) // 地址展示弹窗
|
||||
const showRegionPicker = ref(false) // 支付宝地区选择弹窗
|
||||
|
||||
// 计算是否已有地址
|
||||
const hasAddress = computed(() => {
|
||||
@@ -489,6 +548,45 @@
|
||||
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 () => {
|
||||
try {
|
||||
@@ -767,7 +865,7 @@
|
||||
})
|
||||
}
|
||||
|
||||
// 创建订单并支付
|
||||
// 创建订单并支付(接入商品多支付平台方案:微信 / 支付宝,小程序端)
|
||||
const createOrder = async () => {
|
||||
try {
|
||||
uni.showLoading({
|
||||
@@ -782,13 +880,24 @@
|
||||
savedAddress.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 = {
|
||||
skuId: selectedSku.value.skuId,
|
||||
quantity:quantity.value,
|
||||
quantity: quantity.value,
|
||||
receiverName: addressData.receiverName,
|
||||
receiverPhone: addressData.receiverPhone,
|
||||
receiverAddress: fullAddress, // 传递完整地址(省市区+详细地址)
|
||||
remark: addressForm.value.remark || ''
|
||||
remark: addressForm.value.remark || '',
|
||||
paymentPlatform // WECHAT / ALIPAY /(预留)ANTOM
|
||||
}
|
||||
|
||||
console.log('创建订单数据:', orderData)
|
||||
@@ -800,11 +909,12 @@
|
||||
if (res && res.code === 200 && res.data) {
|
||||
uni.hideLoading()
|
||||
|
||||
// 调用微信支付
|
||||
// 统一获取平台订单号(商品统一支付订单号)
|
||||
const outOrderNo = res.data.OutOrderNo || res.data.outOrderNo
|
||||
|
||||
// ====================== 微信小程序支付 ======================
|
||||
// #ifdef MP-WEIXIN
|
||||
const payParams = res.data
|
||||
// 保存订单ID,用于取消订单
|
||||
const orderId = payParams.OutOrderNo || res.data.OutOrderNo
|
||||
|
||||
uni.requestPayment({
|
||||
timeStamp: payParams.timeStamp,
|
||||
nonceStr: payParams.nonceStr,
|
||||
@@ -831,26 +941,18 @@
|
||||
},
|
||||
fail: async (payErr) => {
|
||||
console.error('支付失败:', payErr)
|
||||
|
||||
|
||||
// 判断是用户取消还是支付失败
|
||||
if (payErr.errMsg && payErr.errMsg.includes('cancel')) {
|
||||
// 用户取消支付,调用取消订单接口
|
||||
// 用户取消支付,这里预留调用取消订单接口
|
||||
try {
|
||||
// uni.showLoading({
|
||||
// title: '正在取消订单...',
|
||||
// mask: true
|
||||
// })
|
||||
|
||||
// await cancelProductOrder(orderId)
|
||||
|
||||
uni.hideLoading()
|
||||
// await cancelProductOrder(outOrderNo)
|
||||
uni.showToast({
|
||||
title: '支付已取消',
|
||||
icon: 'none'
|
||||
})
|
||||
} catch (cancelError) {
|
||||
console.error('取消订单失败:', cancelError)
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '支付已取消',
|
||||
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 {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
|
||||
@@ -335,25 +335,40 @@
|
||||
navigateToOrderDetail(order);
|
||||
};
|
||||
|
||||
// 立即支付
|
||||
// 立即支付(对齐 device-goods.vue,多平台支付)
|
||||
const handlePayment = async (order) => {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '正在创建支付...',
|
||||
mask: true
|
||||
});
|
||||
console.log(order);
|
||||
|
||||
// 调用后端创建微信支付订单接口(使用订单号)
|
||||
// const res = await createWxPayment(order.orderNo);
|
||||
const res = await createProductOrder({orderNo:order.orderNo});
|
||||
console.log('订单列表立即支付,订单信息:', order);
|
||||
|
||||
// 根据当前运行环境确定支付平台(与 device-goods.vue 保持一致)
|
||||
let paymentPlatform = 'WECHAT'; // 默认微信
|
||||
// #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) {
|
||||
uni.hideLoading();
|
||||
|
||||
|
||||
// 统一获取平台订单号(商品统一支付订单号)
|
||||
const outOrderNo = res.data.OutOrderNo || res.data.outOrderNo;
|
||||
|
||||
// ====================== 微信小程序支付 ======================
|
||||
// #ifdef MP-WEIXIN
|
||||
const payParams = res.data;
|
||||
|
||||
// 调用微信支付
|
||||
uni.requestPayment({
|
||||
timeStamp: payParams.timeStamp,
|
||||
nonceStr: payParams.nonceStr,
|
||||
@@ -367,25 +382,25 @@
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
|
||||
// 刷新订单列表
|
||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
||||
await loadOrderList(statusValue);
|
||||
},
|
||||
fail: async (err) => {
|
||||
console.error('支付失败:', err);
|
||||
|
||||
fail: async (payErr) => {
|
||||
console.error('支付失败:', payErr);
|
||||
|
||||
// 判断是用户取消还是支付失败
|
||||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
||||
// 用户取消支付,调用取消订单接口
|
||||
if (payErr.errMsg && payErr.errMsg.includes('cancel')) {
|
||||
// 用户取消支付,这里预留调用取消订单接口
|
||||
try {
|
||||
// await cancelProductOrder(order.id || order.orderId);
|
||||
// uni.showToast({
|
||||
// title: '支付已取消',
|
||||
// icon: 'none'
|
||||
// });
|
||||
|
||||
// await cancelProductOrder(outOrderNo || order.orderNo);
|
||||
uni.showToast({
|
||||
title: '支付已取消',
|
||||
icon: 'none'
|
||||
});
|
||||
|
||||
// 刷新订单列表
|
||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
||||
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 {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
|
||||
@@ -176,9 +176,6 @@ import {
|
||||
createCouponPayment,
|
||||
cancelCouponPayment
|
||||
} from '@/config/api/coupon.js'
|
||||
// import {
|
||||
// cancelMemberCardPayment
|
||||
// } from '@/config/api/member.js'
|
||||
import {
|
||||
createMemberCardPayment,
|
||||
getMemberCardsByPosition,
|
||||
@@ -353,7 +350,48 @@ const selectProduct = (product) => {
|
||||
|
||||
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 () => {
|
||||
if (!selectedProduct.value) {
|
||||
uni.showToast({
|
||||
@@ -363,63 +401,89 @@ const handleBuy = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 会员卡购买
|
||||
const paymentPlatform = getPaymentPlatform()
|
||||
|
||||
// 会员卡购买(按接口文档:POST /app/member/pay)
|
||||
if (currentTab.value === 'card') {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '正在创建订单...'
|
||||
})
|
||||
|
||||
const res = await createMemberCardPayment(selectedProduct.value.id)
|
||||
const res = await createMemberCardPayment(selectedProduct.value.id, paymentPlatform)
|
||||
uni.hideLoading()
|
||||
|
||||
if (res.code === 200 && res.data) {
|
||||
|
||||
orderNo.value = res.data.OutOrderNo;
|
||||
// 调起微信支付
|
||||
uni.requestPayment({
|
||||
timeStamp: res.data.timeStamp,
|
||||
nonceStr: res.data.nonceStr,
|
||||
package: res.data.packageValue || res.data.package,
|
||||
signType: res.data.signType || 'MD5',
|
||||
paySign: res.data.paySign,
|
||||
success: (payRes) => {
|
||||
uni.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 支付成功后,跳转到我的会员卡页面
|
||||
setTimeout(() => {
|
||||
uni.navigateTo({
|
||||
url: '/subPackages/business/my-card'
|
||||
})
|
||||
}, 1500)
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('支付失败:', err)
|
||||
console.log('支付失败详细信息:', err.errMsg.includes('cancel'));
|
||||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
||||
if (orderNo.value) {
|
||||
cancelMemberCardPayment(orderNo.value)
|
||||
.then(cancelRes => {
|
||||
console.log('取消支付订单成功:', cancelRes);
|
||||
})
|
||||
.catch(cancelErr => {
|
||||
console.error('取消支付订单失败:', cancelErr);
|
||||
});
|
||||
// 不同平台分别发起支付(字段按文档)
|
||||
// 微信小程序
|
||||
// #ifdef MP-WEIXIN
|
||||
if (paymentPlatform === 'WECHAT') {
|
||||
// 会员卡订单号:OutOrderNo(文档)
|
||||
orderNo.value = res.data.OutOrderNo
|
||||
uni.requestPayment({
|
||||
timeStamp: res.data.timeStamp,
|
||||
nonceStr: res.data.nonceStr,
|
||||
package: res.data.package,
|
||||
signType: res.data.signType || 'MD5',
|
||||
paySign: res.data.paySign,
|
||||
success: () => {
|
||||
uni.showToast({ title: '支付成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.navigateTo({ url: '/subPackages/business/my-card' })
|
||||
}, 1500)
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('支付失败:', err)
|
||||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
||||
// 取消支付(本项目取消接口走 device 侧 cancel)
|
||||
orderNo.value && cancelMemberCardPayment(orderNo.value).catch(() => {})
|
||||
uni.showToast({ title: '已取消支付', icon: 'none' })
|
||||
} else {
|
||||
uni.showToast({ title: '支付失败', icon: 'none' })
|
||||
}
|
||||
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 {
|
||||
uni.showToast({
|
||||
title: res.msg || '创建订单失败',
|
||||
@@ -437,62 +501,83 @@ const handleBuy = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 优惠券购买
|
||||
// 优惠券购买(按接口文档:POST /app/coupon/pay)
|
||||
if (currentTab.value === 'coupon') {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '正在创建订单...'
|
||||
})
|
||||
|
||||
const res = await createCouponPayment(selectedProduct.value.couponId)
|
||||
const res = await createCouponPayment(selectedProduct.value.couponId, paymentPlatform)
|
||||
uni.hideLoading()
|
||||
|
||||
if (res.code === 200 && res.data) {
|
||||
// 调起微信支付
|
||||
uni.requestPayment({
|
||||
timeStamp: res.data.timeStamp,
|
||||
nonceStr: res.data.nonceStr,
|
||||
package: res.data.packageValue || res.data.package,
|
||||
signType: res.data.signType || 'MD5',
|
||||
paySign: res.data.paySign,
|
||||
success: (payRes) => {
|
||||
uni.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 支付成功后,跳转到我的优惠券页面
|
||||
setTimeout(() => {
|
||||
uni.navigateTo({
|
||||
url: '/subPackages/business/my-coupon'
|
||||
})
|
||||
}, 1500)
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('支付失败:', err)
|
||||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
||||
// 用户取消支付,调用取消接口
|
||||
const orderNo = res.data.OutOrderNo;
|
||||
if (orderNo) {
|
||||
cancelCouponPayment(orderNo)
|
||||
.then(cancelRes => {
|
||||
console.log('取消支付订单成功:', cancelRes);
|
||||
})
|
||||
.catch(cancelErr => {
|
||||
console.error('取消支付订单失败:', cancelErr);
|
||||
});
|
||||
// 微信小程序
|
||||
// #ifdef MP-WEIXIN
|
||||
if (paymentPlatform === 'WECHAT') {
|
||||
uni.requestPayment({
|
||||
timeStamp: res.data.timeStamp,
|
||||
nonceStr: res.data.nonceStr,
|
||||
package: res.data.package,
|
||||
signType: res.data.signType || 'MD5',
|
||||
paySign: res.data.paySign,
|
||||
success: () => {
|
||||
uni.showToast({ title: '支付成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.navigateTo({ url: '/subPackages/business/my-coupon' })
|
||||
}, 1500)
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('支付失败:', err)
|
||||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
||||
const outOrderNo = res.data.OutOrderNo || res.data.outOrderNo
|
||||
outOrderNo && cancelCouponPayment(outOrderNo).catch(() => {})
|
||||
uni.showToast({ title: '已取消支付', icon: 'none' })
|
||||
} else {
|
||||
uni.showToast({ title: '支付失败', icon: 'none' })
|
||||
}
|
||||
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 {
|
||||
uni.showToast({
|
||||
title: res.msg || '创建订单失败',
|
||||
|
||||
+183
-51
@@ -78,6 +78,10 @@
|
||||
} from '@dcloudio/uni-app'
|
||||
import {
|
||||
queryById,
|
||||
createWxPayment,
|
||||
getWxPaymentStatus,
|
||||
createAliPayment,
|
||||
getAliPaymentStatus,
|
||||
createAntomPayment,
|
||||
getAntomPaymentMethods,
|
||||
getAntomPaymentStatus
|
||||
@@ -107,9 +111,9 @@
|
||||
const countdown = ref(15 * 60) // 15分钟 = 900秒
|
||||
let countdownTimer = null
|
||||
|
||||
// 支付方式相关
|
||||
// 支付方式相关(微信/支付宝/H5-Antom 多平台)
|
||||
const paymentMethods = ref([])
|
||||
const selectedPaymentMethod = ref('ALIPAY') // 默认选择支付宝
|
||||
const selectedPaymentMethod = ref('WECHAT') // 默认微信
|
||||
|
||||
// 地点名称(可以从设备信息中获取,这里先用默认值)
|
||||
const locationName = ref('澎创办公室')
|
||||
@@ -197,11 +201,11 @@
|
||||
deviceNo.value = orderData.deviceNo;
|
||||
await loadDeviceInfo();
|
||||
await loadPaymentMethods();
|
||||
// #ifdef H5
|
||||
// 如果订单状态是等待支付,启动相应的支付状态轮询
|
||||
if(orderInfo.value.orderStatus=='waiting_for_payment'){
|
||||
// 使用当前选中的支付方式类型进行轮询
|
||||
startPaymentStatusPolling();
|
||||
}
|
||||
// #endif
|
||||
} else {
|
||||
throw new Error(t('order.getOrderFailed'))
|
||||
}
|
||||
@@ -253,35 +257,46 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 加载支付方式列表
|
||||
// 加载支付方式列表(根据平台决定可选项)
|
||||
const loadPaymentMethods = async () => {
|
||||
if (!orderInfo.value.orderNo) return;
|
||||
const methods = []
|
||||
|
||||
try {
|
||||
const osType = getOsType();
|
||||
console.log('当前系统类型:', osType);
|
||||
// 小程序环境下:微信 / 支付宝
|
||||
// #ifdef MP-WEIXIN
|
||||
methods.push({ paymentMethodType: 'WECHAT', paymentMethodName: '微信支付' })
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
methods.push({ paymentMethodType: 'ALIPAY', paymentMethodName: '支付宝支付' })
|
||||
// #endif
|
||||
|
||||
const res = await getAntomPaymentMethods(orderInfo.value.orderNo, osType);
|
||||
if (res.code === 200 && res.data && res.data.paymentOptions) {
|
||||
paymentMethods.value = res.data.paymentOptions;
|
||||
console.log('支付方式列表:', paymentMethods.value);
|
||||
// 如果有支付方式,默认选择第一个
|
||||
if (paymentMethods.value.length > 0) {
|
||||
selectedPaymentMethod.value = paymentMethods.value[0].paymentMethodType;
|
||||
// H5 环境:使用 Antom 聚合支付(多通道)
|
||||
// #ifdef H5
|
||||
if (orderInfo.value.orderNo) {
|
||||
try {
|
||||
const osType = getOsType();
|
||||
const res = await getAntomPaymentMethods(orderInfo.value.orderNo, osType);
|
||||
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);
|
||||
// 如果获取失败,使用默认支付方式
|
||||
paymentMethods.value = [{
|
||||
paymentMethodType: 'ALIPAY',
|
||||
paymentMethodName: '支付宝'
|
||||
},
|
||||
{
|
||||
paymentMethodType: 'WECHATPAY',
|
||||
paymentMethodName: '微信支付'
|
||||
}
|
||||
];
|
||||
}
|
||||
// #endif
|
||||
|
||||
// 兜底:至少保留一个微信
|
||||
if (!methods.length) {
|
||||
methods.push({ paymentMethodType: 'WECHAT', paymentMethodName: '微信支付' })
|
||||
}
|
||||
|
||||
paymentMethods.value = methods
|
||||
if (paymentMethods.value.length > 0) {
|
||||
selectedPaymentMethod.value = paymentMethods.value[0].paymentMethodType
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,8 +330,85 @@
|
||||
title: t('common.processing')
|
||||
})
|
||||
|
||||
const method = selectedPaymentMethod.value
|
||||
|
||||
// 微信小程序支付押金
|
||||
// #ifdef MP-WEIXIN
|
||||
if (method === 'WECHAT') {
|
||||
const wxRes = await createWxPayment(orderInfo.value.orderNo)
|
||||
if (wxRes.code === 200 && wxRes.data) {
|
||||
const payData = wxRes.data
|
||||
await new Promise((resolve, reject) => {
|
||||
wx.requestPayment({
|
||||
...payData,
|
||||
success: resolve,
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
// 支付成功后轮询微信支付状态
|
||||
startPaymentStatusPolling('WECHAT')
|
||||
return
|
||||
} else {
|
||||
throw new Error(wxRes.msg || t('payment.createPayOrderFailed'))
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// 支付宝小程序支付押金
|
||||
// #ifdef MP-ALIPAY
|
||||
if (method === 'ALIPAY') {
|
||||
const aliRes = await createAliPayment(orderInfo.value.orderNo)
|
||||
if (aliRes.code === 200 && aliRes.data) {
|
||||
// 后端当前实际返回结构示例:
|
||||
// { code:200, msg:'操作成功', data:{ tradeNo:'xxx', outTradeNo:'yyy' } }
|
||||
const tradeNO = aliRes.data.tradeNo || aliRes.data.outTradeNo
|
||||
const payForm = aliRes.data.payForm || aliRes.data.orderStr
|
||||
|
||||
if (!tradeNO && !payForm) {
|
||||
throw new Error('未获取到支付宝支付参数')
|
||||
}
|
||||
|
||||
// 优先使用 tradeNO 方式,其次兼容老的 orderStr 方式
|
||||
if (tradeNO) {
|
||||
my.tradePay({
|
||||
tradeNO,
|
||||
success: (res) => {
|
||||
if (res.resultCode === '9000') {
|
||||
startPaymentStatusPolling('ALIPAY')
|
||||
} else {
|
||||
uni.showToast({ title: t('payment.paymentFailed'), icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: t('payment.paymentFailed'), icon: 'none' })
|
||||
}
|
||||
})
|
||||
} else {
|
||||
my.tradePay({
|
||||
orderStr: payForm,
|
||||
success: (res) => {
|
||||
if (res.resultCode === '9000') {
|
||||
startPaymentStatusPolling('ALIPAY')
|
||||
} else {
|
||||
uni.showToast({ title: t('payment.paymentFailed'), icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: t('payment.paymentFailed'), icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
return
|
||||
} else {
|
||||
throw new Error(aliRes.msg || t('payment.createPayOrderFailed'))
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// H5 + Antom 聚合支付
|
||||
// #ifdef H5
|
||||
const osType = getOsType();
|
||||
const res = await createAntomPayment(orderInfo.value.orderNo, selectedPaymentMethod.value, osType)
|
||||
const res = await createAntomPayment(orderInfo.value.orderNo, method, osType)
|
||||
|
||||
if (res && res.code === 200 && res.data) {
|
||||
const paymentUrl = res.data.h5Url;
|
||||
@@ -326,21 +418,16 @@
|
||||
}
|
||||
|
||||
uni.hideLoading();
|
||||
// #ifdef H5
|
||||
uni.setStorageSync('pendingPaymentNo', orderId.value);
|
||||
// 跳转到支付页面
|
||||
// uni.navigateTo({
|
||||
// url: `/pages/webview/index?url=${encodeURIComponent(paymentUrl)}&title=支付`
|
||||
// });
|
||||
window.open(paymentUrl);
|
||||
// #endif
|
||||
|
||||
// 开始轮询支付状态
|
||||
startPaymentStatusPolling();
|
||||
|
||||
// 开始轮询支付状态(传入当前选中的支付方式类型)
|
||||
startPaymentStatusPolling(method);
|
||||
return
|
||||
} else {
|
||||
throw new Error(res?.msg || t('payment.createPayOrderFailed'))
|
||||
}
|
||||
// #endif
|
||||
} catch (error) {
|
||||
console.error('支付失败:', error)
|
||||
uni.showToast({
|
||||
@@ -352,14 +439,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 轮询支付状态
|
||||
// 轮询定时器
|
||||
let pollingTimer = null;
|
||||
const startPaymentStatusPolling = () => {
|
||||
|
||||
/**
|
||||
* 统一的支付状态轮询方法
|
||||
* @param {string} paymentMethodType - 支付方式类型,如 'WECHAT' | 'ALIPAY' | 'WECHATPAY' 等,与 selectedPaymentMethod 保持一致
|
||||
*/
|
||||
const startPaymentStatusPolling = (paymentMethodType = null) => {
|
||||
// 清除之前的定时器
|
||||
if (pollingTimer) {
|
||||
clearInterval(pollingTimer);
|
||||
}
|
||||
|
||||
// 如果没有传入支付方式类型,使用当前选中的支付方式
|
||||
const methodType = paymentMethodType || selectedPaymentMethod.value;
|
||||
|
||||
let pollCount = 0;
|
||||
const maxPollCount = 60; // 最多轮询60次(5分钟)
|
||||
|
||||
@@ -376,32 +471,64 @@
|
||||
}
|
||||
|
||||
try {
|
||||
let res;
|
||||
let status, successStatus, failStatuses;
|
||||
|
||||
// #ifdef H5
|
||||
// H5 环境统一使用 Antom 聚合支付 API
|
||||
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) {
|
||||
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);
|
||||
|
||||
// uni.showToast({
|
||||
// title: t('payment.paymentSuccess'),
|
||||
// icon: 'success'
|
||||
// });
|
||||
|
||||
try {
|
||||
await updateUserBalance(orderId.value);
|
||||
} catch (error) {
|
||||
console.warn('更新用户余额失败:', error);
|
||||
}
|
||||
console.log(orderInfo);
|
||||
|
||||
setTimeout(() => {
|
||||
uni.redirectTo({
|
||||
url: `/pages/order/success?orderId=${orderId.value}&deviceId=${orderInfo.value.deviceNo}`
|
||||
});
|
||||
}, 1500);
|
||||
} else if (paymentStatus === 'FAIL' || paymentStatus === 'CANCELLED') {
|
||||
}
|
||||
// 支付失败
|
||||
else if (failStatuses && failStatuses.includes(status)) {
|
||||
clearInterval(pollingTimer);
|
||||
uni.showToast({
|
||||
title: '支付失败,请重新支付',
|
||||
@@ -410,11 +537,16 @@
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询支付状态失败:', error);
|
||||
const errorMsg = methodType === 'ALIPAY' ? '支付宝' : (methodType === 'WECHAT' || methodType === 'WECHATPAY') ? '微信' : '支付';
|
||||
console.error(`查询${errorMsg}支付状态失败:`, error);
|
||||
}
|
||||
}, 5000); // 每5秒查询一次
|
||||
}
|
||||
|
||||
// 兼容性方法:保持原有函数名,内部调用统一方法
|
||||
const startWxPaymentStatusPolling = () => startPaymentStatusPolling('WECHAT');
|
||||
const startAliPaymentStatusPolling = () => startPaymentStatusPolling('ALIPAY');
|
||||
|
||||
// 更新导航栏倒计时
|
||||
const updateNavBarCountdown = () => {
|
||||
const minutes = Math.floor(countdown.value / 60).toString().padStart(2, '0')
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
// 跳转到投诉记录列表
|
||||
const navigateToRecord = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/feedback/list'
|
||||
url: '/subPackages/service/feedback/list'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@
|
||||
// 跳转到详情页
|
||||
const navigateToDetail = (item) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/feedback/detail?id=${item.id || item.feedbackId}`
|
||||
url: `/subPackages/service/feedback/detail?id=${item.id || item.feedbackId}`
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -2,18 +2,27 @@
|
||||
<view class="help-container">
|
||||
<!-- 常见问题 -->
|
||||
<view class="faq-section">
|
||||
<uv-collapse :border="false">
|
||||
<uv-collapse-item
|
||||
v-for="(item, index) in faqList"
|
||||
:key="index"
|
||||
:title="$t(item.question)"
|
||||
:name="index"
|
||||
<view
|
||||
v-for="(item, index) in faqList"
|
||||
:key="index"
|
||||
class="collapse-item"
|
||||
>
|
||||
<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">
|
||||
<text class="answer-text">{{ $t(item.answer) }}</text>
|
||||
</view>
|
||||
</uv-collapse-item>
|
||||
</uv-collapse>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系客服 -->
|
||||
@@ -34,7 +43,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { HELP_CONTENT } from '@/constants/help'
|
||||
import { getCustomerPhone } from '@/util/index.js'
|
||||
@@ -44,6 +53,7 @@ const { t } = useI18n()
|
||||
|
||||
const faqList = ref(HELP_CONTENT.FAQ_LIST)
|
||||
const customerPhone = ref(HELP_CONTENT.CONTACT.PHONE.VALUE)
|
||||
const activeIndex = ref(null)
|
||||
|
||||
onLoad(() => {
|
||||
uni.setNavigationBarTitle({
|
||||
@@ -52,6 +62,14 @@ onLoad(() => {
|
||||
customerPhone.value = getCustomerPhone()
|
||||
})
|
||||
|
||||
const toggleCollapse = (index) => {
|
||||
if (activeIndex.value === index) {
|
||||
activeIndex.value = null
|
||||
} else {
|
||||
activeIndex.value = index
|
||||
}
|
||||
}
|
||||
|
||||
const makePhoneCall = () => {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: customerPhone.value
|
||||
@@ -72,15 +90,67 @@ const makePhoneCall = () => {
|
||||
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
|
||||
overflow: hidden;
|
||||
|
||||
.answer-content {
|
||||
padding: 20rpx 30rpx 30rpx;
|
||||
background: #f9f9f9;
|
||||
.collapse-item {
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.answer-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
display: block;
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.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="subtitle">{{ $t('auth.loginDesc') }}</view>
|
||||
|
||||
<!-- 微信一键手机号快捷登录(推荐) -->
|
||||
<!-- 微信小程序:一键手机号快捷登录 -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button v-if="!isAgreed" class="btn primary" @click="handleLoginClick">
|
||||
{{ $t('auth.getPhoneNumber') }}
|
||||
</button>
|
||||
<button v-else class="btn primary" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
|
||||
{{ $t('auth.getPhoneNumber') }}
|
||||
</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">
|
||||
@@ -40,7 +54,7 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
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'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -116,13 +130,12 @@
|
||||
uni.reLaunch({ url: target })
|
||||
}
|
||||
|
||||
// 微信快捷登录入口(备用,目前主流程使用一键手机号登录)
|
||||
const onWeChatLogin = async () => {
|
||||
try {
|
||||
// 先检查是否同意协议
|
||||
await checkAgreement()
|
||||
|
||||
await wxLogin()
|
||||
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
|
||||
await wxLogin()
|
||||
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
|
||||
await navigateAfterLogin()
|
||||
} catch (error) {
|
||||
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) => {
|
||||
if (!e || e.detail.errMsg !== 'getPhoneNumber:ok') {
|
||||
uni.showToast({ title: t('auth.phoneCancelled'), icon: 'none' })
|
||||
@@ -138,9 +167,9 @@
|
||||
}
|
||||
|
||||
try {
|
||||
// 先微信登录,获取 token
|
||||
// 先完成微信登录(/app/user/quickLogin,后端建立/查询 WECHAT_MINI 用户)
|
||||
await wxLogin()
|
||||
// 再用微信返回的临时 code 换取手机号
|
||||
// 再用微信返回的临时 code 换取手机号(后端按文档更新 phone 字段)
|
||||
await getUserPhoneNumber(e.detail.code)
|
||||
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
|
||||
await navigateAfterLogin()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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">
|
||||
<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>
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
</view>
|
||||
<view class="group">
|
||||
<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>
|
||||
</view>
|
||||
<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>
|
||||
</view>
|
||||
<view class="item" @click="navigateTo('/subPackages/other/legal/terms')">
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button class="avatar-choose-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"></button>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<!-- <button class="avatar-choose-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"></button> -->
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<view class="avatar-tip">{{ $t('userProfile.clickToChange') }}</view>
|
||||
</view>
|
||||
@@ -129,6 +132,8 @@ const redirectToLogin = () => {
|
||||
|
||||
// 小程序原生选择头像回调
|
||||
const onChooseAvatar = async (e) => {
|
||||
console.log(e.detail.avatarUrl,'获取头像详情');
|
||||
|
||||
try {
|
||||
const token = uni.getStorageSync('token');
|
||||
if (!token) {
|
||||
@@ -282,7 +287,7 @@ function maskPhone(phone) {
|
||||
}
|
||||
|
||||
/* 仅小程序端存在,此按钮覆盖在头像上捕获点击以触发选择头像 */
|
||||
/* #ifdef MP-WEIXIN */
|
||||
/* #ifdef MP-WEIXIN || MP-ALIPAY */
|
||||
.avatar-choose-btn {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
+150
-18
@@ -1,37 +1,48 @@
|
||||
import {
|
||||
login,
|
||||
quickLogin,
|
||||
getMyIndexInfo,
|
||||
getWxUserPhoneNumber
|
||||
getWxUserPhoneNumber,
|
||||
getAliUserPhoneNumber
|
||||
} from "../config/api/user"
|
||||
import {
|
||||
URL,
|
||||
appid
|
||||
appid,
|
||||
ZFBappid
|
||||
} from "@/config/url.js"
|
||||
import { getCommonByBrand } from "@/config/api/system"
|
||||
import { HELP_CONTENT } from "@/constants/help"
|
||||
// import { GET_PHONE_NUMBER_URL } from "../config/url"
|
||||
|
||||
// 微信登录方法
|
||||
// 统一快捷登录 - 微信小程序登录(使用 /app/user/quickLogin)
|
||||
export const wxLogin = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 1. 获取微信登录凭证
|
||||
// 1. 获取微信登录凭证(code -> 后端换 openId)
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: async (loginRes) => {
|
||||
|
||||
try {
|
||||
if (loginRes.code) {
|
||||
// 2. 发送 code 到后端换取 token
|
||||
const result = await login({
|
||||
code: loginRes.code,
|
||||
appid: "wx2165f0be356ae7a9"
|
||||
})
|
||||
if (!loginRes.code) {
|
||||
throw new Error('获取微信登录凭证失败')
|
||||
}
|
||||
|
||||
if (result.code === 200) {
|
||||
// 3. 保存token和用户信息
|
||||
// 2. 调用统一快捷登录接口,后端根据 code 获取 openId,并按文档用 openId+userSource 处理
|
||||
const result = await quickLogin({
|
||||
loginType: 'WECHAT',
|
||||
appid: appid,
|
||||
code: loginRes.code
|
||||
})
|
||||
|
||||
uni.setStorageSync('token', result.data.LoginWxVo.access_token)
|
||||
uni.setStorageSync('client_id', result.data.LoginWxVo.client_id)
|
||||
if (result.code === 200 && result.data) {
|
||||
// 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. 登录成功后获取并缓存客服电话
|
||||
fetchAndCacheCustomerPhone().catch(err => {
|
||||
@@ -40,13 +51,12 @@ export const wxLogin = () => {
|
||||
|
||||
resolve(result.data)
|
||||
} else {
|
||||
throw new Error(result.message || '登录失败')
|
||||
throw new Error(result.msg || '登录失败,未返回令牌')
|
||||
}
|
||||
} else {
|
||||
throw new Error('获取微信登录凭证失败')
|
||||
throw new Error(result.msg || result.message || '登录失败')
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
uni.showToast({
|
||||
title: error.message || '登录失败',
|
||||
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 = () => {
|
||||
// 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) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
+386
-6
@@ -36,6 +36,206 @@ const getPermissionText = (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
|
||||
const QQMAP_KEY =
|
||||
// #ifdef H5
|
||||
@@ -566,8 +766,15 @@ function getUserLocation() {
|
||||
wx.getLocation({
|
||||
type: 'gcj02',
|
||||
success: (res) => {
|
||||
const longitude = parseFloat(res.longitude.toFixed(5));
|
||||
const latitude = parseFloat(res.latitude.toFixed(5));
|
||||
const longitude = toFixedNumber(res.longitude, 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('地址获取成功');
|
||||
resolve({
|
||||
longitude,
|
||||
@@ -612,8 +819,15 @@ function getUserLocation() {
|
||||
wx.getLocation({
|
||||
type: 'gcj02',
|
||||
success: (res) => {
|
||||
const longitude = parseFloat(res.longitude.toFixed(5));
|
||||
const latitude = parseFloat(res.latitude.toFixed(5));
|
||||
const longitude = toFixedNumber(res.longitude, 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('地址获取成功');
|
||||
resolve({
|
||||
longitude,
|
||||
@@ -635,8 +849,15 @@ function getUserLocation() {
|
||||
uni.getLocation({
|
||||
type: 'gcj02',
|
||||
success: (res) => {
|
||||
const longitude = parseFloat(res.longitude.toFixed(5));
|
||||
const latitude = parseFloat(res.latitude.toFixed(5));
|
||||
const longitude = toFixedNumber(res.longitude, 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('地址获取成功');
|
||||
resolve({
|
||||
longitude,
|
||||
@@ -665,6 +886,41 @@ function getUserLocation() {
|
||||
// 逆地理编码 - 根据经纬度获取地址信息
|
||||
function getRegeo(longitude, latitude) {
|
||||
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
|
||||
// H5环境:使用JSONP方式调用腾讯地图API,避免跨域问题
|
||||
const callbackName = `qqmap_geocoder_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
@@ -756,6 +1012,50 @@ function getRegeo(longitude, latitude) {
|
||||
// 搜索周边POI
|
||||
function getPoiAround(longitude, latitude, keyword = '', radius = 1000) {
|
||||
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
|
||||
// H5环境:使用JSONP方式调用腾讯地图API
|
||||
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) {
|
||||
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
|
||||
// H5环境:使用JSONP方式调用腾讯地图API
|
||||
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 = '全国') {
|
||||
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
|
||||
// H5环境:使用JSONP方式调用腾讯地图API
|
||||
const callbackName = `qqmap_suggestion_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
Reference in New Issue
Block a user