@@ -68,7 +68,7 @@
|
||||
:class="{ disabled: !selectedTarget }"
|
||||
@tap="handleNavigateTap"
|
||||
>
|
||||
<text class="arrival-primary-text">第三方导航</text>
|
||||
<text class="arrival-primary-text">{{ primaryActionText }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
@@ -116,9 +116,11 @@ import {
|
||||
} from '@/data/arrivalSearchTargets'
|
||||
import {
|
||||
openThirdPartyMapSearch,
|
||||
openWechatMiniProgramLocation,
|
||||
THIRD_PARTY_MAP_PROVIDERS,
|
||||
type ThirdPartyMapProviderOption
|
||||
} from '@/services/ThirdPartyMapSearchService'
|
||||
import { isEmbeddedInWechatMiniProgram } from '@/utils/hostEnvironment'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
visible?: boolean
|
||||
@@ -142,6 +144,10 @@ const emit = defineEmits<{
|
||||
|
||||
const typeOptions = computed(() => ARRIVAL_TARGET_TYPES)
|
||||
const mapProviders = computed(() => THIRD_PARTY_MAP_PROVIDERS)
|
||||
const shouldUseMiniProgramLocation = computed(() => isEmbeddedInWechatMiniProgram())
|
||||
const primaryActionText = computed(() => (
|
||||
shouldUseMiniProgramLocation.value ? '打开地图导航' : '第三方导航'
|
||||
))
|
||||
const arrivalPanelRef = ref<unknown>(null)
|
||||
const isCollapsed = ref(false)
|
||||
const providerSheetVisible = ref(false)
|
||||
@@ -238,6 +244,16 @@ const handleNavigateTap = () => {
|
||||
return
|
||||
}
|
||||
|
||||
if (shouldUseMiniProgramLocation.value) {
|
||||
void openWechatMiniProgramLocation({
|
||||
latitude: props.selectedTarget.latitude,
|
||||
longitude: props.selectedTarget.longitude,
|
||||
name: props.selectedTarget.title,
|
||||
address: props.selectedTarget.subtitle
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
providerSheetVisible.value = true
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,13 @@
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/open-location/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "地图导航",
|
||||
"navigationBarBackgroundColor": "#FFFFFF"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
184
src/pages/open-location/index.vue
Normal file
184
src/pages/open-location/index.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<view class="open-location-page">
|
||||
<view class="open-location-panel">
|
||||
<text class="open-location-title">{{ title }}</text>
|
||||
<text class="open-location-desc">{{ description }}</text>
|
||||
<view class="open-location-actions">
|
||||
<view class="open-location-button primary" @tap="openLocation">
|
||||
<text class="open-location-button-text">重新打开</text>
|
||||
</view>
|
||||
<view class="open-location-button" @tap="goBack">
|
||||
<text class="open-location-button-text">返回导览</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
interface OpenLocationOptions {
|
||||
latitude?: string
|
||||
longitude?: string
|
||||
name?: string
|
||||
address?: string
|
||||
}
|
||||
|
||||
const latitude = ref<number | null>(null)
|
||||
const longitude = ref<number | null>(null)
|
||||
const locationName = ref('')
|
||||
const locationAddress = ref('')
|
||||
const hasOpened = ref(false)
|
||||
const hasError = ref(false)
|
||||
|
||||
const title = computed(() => {
|
||||
if (hasError.value) return '地图打开失败'
|
||||
if (hasOpened.value) return '已打开地图'
|
||||
return '正在打开地图'
|
||||
})
|
||||
|
||||
const description = computed(() => {
|
||||
if (hasError.value) return '请返回导览后重试,或检查小程序位置权限。'
|
||||
if (locationName.value) return locationName.value
|
||||
return '正在为你唤起地图位置页。'
|
||||
})
|
||||
|
||||
const safeDecode = (value?: string) => {
|
||||
if (!value) return ''
|
||||
|
||||
try {
|
||||
return decodeURIComponent(value)
|
||||
} catch {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
fail: () => {
|
||||
uni.redirectTo({
|
||||
url: '/pages/index/index?tab=guide'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const openLocation = () => {
|
||||
if (latitude.value === null || longitude.value === null) {
|
||||
hasError.value = true
|
||||
uni.showToast({
|
||||
title: '缺少地图坐标',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.openLocation({
|
||||
latitude: latitude.value,
|
||||
longitude: longitude.value,
|
||||
name: locationName.value,
|
||||
address: locationAddress.value,
|
||||
scale: 18,
|
||||
success: () => {
|
||||
hasOpened.value = true
|
||||
hasError.value = false
|
||||
setTimeout(goBack, 300)
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('打开地图位置失败:', error)
|
||||
hasError.value = true
|
||||
uni.showToast({
|
||||
title: '无法打开地图',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onLoad((options?: OpenLocationOptions) => {
|
||||
latitude.value = Number(options?.latitude)
|
||||
longitude.value = Number(options?.longitude)
|
||||
locationName.value = safeDecode(options?.name) || '深圳自然博物馆'
|
||||
locationAddress.value = safeDecode(options?.address)
|
||||
|
||||
if (!Number.isFinite(latitude.value) || !Number.isFinite(longitude.value)) {
|
||||
latitude.value = null
|
||||
longitude.value = null
|
||||
hasError.value = true
|
||||
return
|
||||
}
|
||||
|
||||
openLocation()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.open-location-page {
|
||||
min-height: 100vh;
|
||||
padding: 28px 20px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f5f5ed;
|
||||
}
|
||||
|
||||
.open-location-panel {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
padding: 24px 20px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
background: #ffffff;
|
||||
border: 1px solid rgba(31, 35, 41, 0.08);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.open-location-title {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
font-weight: 700;
|
||||
color: #262421;
|
||||
}
|
||||
|
||||
.open-location-desc {
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #424754;
|
||||
}
|
||||
|
||||
.open-location-actions {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.open-location-button {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
height: 42px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid rgba(31, 35, 41, 0.12);
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.open-location-button.primary {
|
||||
border-color: #d7d800;
|
||||
background: #e0e100;
|
||||
}
|
||||
|
||||
.open-location-button-text {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-weight: 600;
|
||||
color: #262421;
|
||||
}
|
||||
</style>
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isEmbeddedInWechatMiniProgram } from '@/utils/hostEnvironment'
|
||||
|
||||
export type ThirdPartyMapProvider = 'amap' | 'baidu' | 'tencent'
|
||||
|
||||
export interface ThirdPartyMapSearchTarget {
|
||||
@@ -5,6 +7,23 @@ export interface ThirdPartyMapSearchTarget {
|
||||
region: string
|
||||
}
|
||||
|
||||
export interface WechatMiniProgramLocationTarget {
|
||||
latitude: number
|
||||
longitude: number
|
||||
name: string
|
||||
address?: string
|
||||
}
|
||||
|
||||
interface WechatMiniProgramBridge {
|
||||
navigateTo?: (options: {
|
||||
url: string
|
||||
success?: () => void
|
||||
fail?: (error: unknown) => void
|
||||
}) => void
|
||||
}
|
||||
|
||||
let wechatJsSdkLoadPromise: Promise<boolean> | null = null
|
||||
|
||||
export interface ThirdPartyMapProviderOption {
|
||||
provider: ThirdPartyMapProvider
|
||||
label: string
|
||||
@@ -55,3 +74,80 @@ export const openThirdPartyMapSearch = (
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
export const buildWechatMiniProgramOpenLocationUrl = (
|
||||
target: WechatMiniProgramLocationTarget
|
||||
) => {
|
||||
const params = new URLSearchParams({
|
||||
latitude: String(target.latitude),
|
||||
longitude: String(target.longitude),
|
||||
name: target.name,
|
||||
address: target.address || ''
|
||||
})
|
||||
|
||||
return `/pages/open-location/index?${params.toString()}`
|
||||
}
|
||||
|
||||
const getWechatMiniProgramBridge = (): WechatMiniProgramBridge | null => {
|
||||
if (typeof window === 'undefined') return null
|
||||
|
||||
return (window as Window & {
|
||||
wx?: {
|
||||
miniProgram?: WechatMiniProgramBridge
|
||||
}
|
||||
}).wx?.miniProgram || null
|
||||
}
|
||||
|
||||
const loadWechatJsSdk = () => {
|
||||
if (typeof document === 'undefined') return Promise.resolve(false)
|
||||
if (getWechatMiniProgramBridge()) return Promise.resolve(true)
|
||||
if (wechatJsSdkLoadPromise) return wechatJsSdkLoadPromise
|
||||
|
||||
wechatJsSdkLoadPromise = new Promise<boolean>((resolve) => {
|
||||
const existingScript = document.querySelector<HTMLScriptElement>('script[data-wechat-js-sdk="true"]')
|
||||
if (existingScript) {
|
||||
existingScript.addEventListener('load', () => resolve(Boolean(getWechatMiniProgramBridge())), { once: true })
|
||||
existingScript.addEventListener('error', () => resolve(false), { once: true })
|
||||
return
|
||||
}
|
||||
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js'
|
||||
script.async = true
|
||||
script.dataset.wechatJsSdk = 'true'
|
||||
script.onload = () => resolve(Boolean(getWechatMiniProgramBridge()))
|
||||
script.onerror = () => resolve(false)
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
|
||||
return wechatJsSdkLoadPromise
|
||||
}
|
||||
|
||||
export const openWechatMiniProgramLocation = async (
|
||||
target: WechatMiniProgramLocationTarget
|
||||
) => {
|
||||
if (!isEmbeddedInWechatMiniProgram()) return false
|
||||
|
||||
await loadWechatJsSdk()
|
||||
const miniProgram = getWechatMiniProgramBridge()
|
||||
if (!miniProgram?.navigateTo) {
|
||||
uni.showToast({
|
||||
title: '请在小程序内打开地图',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
miniProgram.navigateTo({
|
||||
url: buildWechatMiniProgramOpenLocationUrl(target),
|
||||
fail: (error) => {
|
||||
console.error('跳转小程序地图页失败:', error)
|
||||
uni.showToast({
|
||||
title: '无法打开地图导航',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user