修复小程序内来馆地图导航
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
lyf
2026-07-10 11:20:17 +08:00
parent 52f5217252
commit a5b19db355
4 changed files with 304 additions and 1 deletions

View File

@@ -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
}

View File

@@ -53,6 +53,13 @@
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
},
{
"path": "pages/open-location/index",
"style": {
"navigationBarTitleText": "地图导航",
"navigationBarBackgroundColor": "#FFFFFF"
}
}
],
"globalStyle": {

View 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>

View File

@@ -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
}