@@ -68,7 +68,7 @@
|
|||||||
:class="{ disabled: !selectedTarget }"
|
:class="{ disabled: !selectedTarget }"
|
||||||
@tap="handleNavigateTap"
|
@tap="handleNavigateTap"
|
||||||
>
|
>
|
||||||
<text class="arrival-primary-text">第三方导航</text>
|
<text class="arrival-primary-text">{{ primaryActionText }}</text>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
</view>
|
</view>
|
||||||
@@ -116,9 +116,11 @@ import {
|
|||||||
} from '@/data/arrivalSearchTargets'
|
} from '@/data/arrivalSearchTargets'
|
||||||
import {
|
import {
|
||||||
openThirdPartyMapSearch,
|
openThirdPartyMapSearch,
|
||||||
|
openWechatMiniProgramLocation,
|
||||||
THIRD_PARTY_MAP_PROVIDERS,
|
THIRD_PARTY_MAP_PROVIDERS,
|
||||||
type ThirdPartyMapProviderOption
|
type ThirdPartyMapProviderOption
|
||||||
} from '@/services/ThirdPartyMapSearchService'
|
} from '@/services/ThirdPartyMapSearchService'
|
||||||
|
import { isEmbeddedInWechatMiniProgram } from '@/utils/hostEnvironment'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
visible?: boolean
|
visible?: boolean
|
||||||
@@ -142,6 +144,10 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const typeOptions = computed(() => ARRIVAL_TARGET_TYPES)
|
const typeOptions = computed(() => ARRIVAL_TARGET_TYPES)
|
||||||
const mapProviders = computed(() => THIRD_PARTY_MAP_PROVIDERS)
|
const mapProviders = computed(() => THIRD_PARTY_MAP_PROVIDERS)
|
||||||
|
const shouldUseMiniProgramLocation = computed(() => isEmbeddedInWechatMiniProgram())
|
||||||
|
const primaryActionText = computed(() => (
|
||||||
|
shouldUseMiniProgramLocation.value ? '打开地图导航' : '第三方导航'
|
||||||
|
))
|
||||||
const arrivalPanelRef = ref<unknown>(null)
|
const arrivalPanelRef = ref<unknown>(null)
|
||||||
const isCollapsed = ref(false)
|
const isCollapsed = ref(false)
|
||||||
const providerSheetVisible = ref(false)
|
const providerSheetVisible = ref(false)
|
||||||
@@ -238,6 +244,16 @@ const handleNavigateTap = () => {
|
|||||||
return
|
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
|
providerSheetVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,13 @@
|
|||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/open-location/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "地图导航",
|
||||||
|
"navigationBarBackgroundColor": "#FFFFFF"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalStyle": {
|
"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 type ThirdPartyMapProvider = 'amap' | 'baidu' | 'tencent'
|
||||||
|
|
||||||
export interface ThirdPartyMapSearchTarget {
|
export interface ThirdPartyMapSearchTarget {
|
||||||
@@ -5,6 +7,23 @@ export interface ThirdPartyMapSearchTarget {
|
|||||||
region: string
|
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 {
|
export interface ThirdPartyMapProviderOption {
|
||||||
provider: ThirdPartyMapProvider
|
provider: ThirdPartyMapProvider
|
||||||
label: string
|
label: string
|
||||||
@@ -55,3 +74,80 @@ export const openThirdPartyMapSearch = (
|
|||||||
icon: 'none'
|
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