改造 H5 第三方地图导航
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
lyf
2026-07-13 16:56:18 +08:00
parent 9c003c16dc
commit 140f13633e
5 changed files with 308 additions and 220 deletions

View File

@@ -77,41 +77,6 @@
</template>
</view>
<!-- #ifndef MP-WEIXIN -->
<view
v-if="providerSheetVisible && selectedTarget"
class="provider-scrim"
@tap="closeProviderSheet"
></view>
<view
v-if="providerSheetVisible && selectedTarget"
class="provider-sheet"
@tap.stop
>
<view class="provider-header">
<view class="provider-title-group">
<text class="provider-kicker">选择地图</text>
<text class="provider-title">{{ selectedTarget.title }}</text>
</view>
<view class="provider-close" @tap="closeProviderSheet">
<text class="provider-close-text">×</text>
</view>
</view>
<view
v-for="provider in mapProviders"
:key="provider.provider"
class="provider-row"
@tap="handleProviderSelect(provider)"
>
<view class="provider-mark">
<text class="provider-mark-text">{{ provider.label.slice(0, 1) }}</text>
</view>
<text class="provider-label">{{ provider.label }}</text>
<text class="provider-action">打开</text>
</view>
</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view
v-if="buildDiagnostics"
@@ -138,11 +103,7 @@ import {
type ArrivalTargetType
} from '@/data/arrivalSearchTargets'
// #ifndef MP-WEIXIN
import {
openThirdPartyMapSearch,
THIRD_PARTY_MAP_PROVIDERS,
type ThirdPartyMapProviderOption
} from '@/services/ThirdPartyMapSearchService'
import { navigateToMuseum } from '@/services/MapNavigationService'
// #endif
// #ifdef MP-WEIXIN
import { openWechatHostLocation } from '@/services/WechatOpenLocationService'
@@ -172,15 +133,11 @@ const emit = defineEmits<{
}>()
const typeOptions = computed(() => ARRIVAL_TARGET_TYPES)
// #ifndef MP-WEIXIN
const mapProviders = computed(() => THIRD_PARTY_MAP_PROVIDERS)
// #endif
const navigationMode: ArrivalNavigationMode = __APP_BUILD_PLATFORM__ === 'mp-weixin'
? 'native-location'
: 'third-party-providers'
const arrivalPanelRef = ref<unknown>(null)
const isCollapsed = ref(false)
const providerSheetVisible = ref(false)
const isOpeningNavigation = ref(false)
let resizeObserver: ResizeObserver | null = null
@@ -236,12 +193,10 @@ const activeTypeLabel = computed(() => (
const handleTypeChange = (type: ArrivalTargetType) => {
if (type === props.activeType) return
closeProviderSheet()
emit('update:activeType', type)
}
const handleTargetSelect = (target: ArrivalSearchTarget) => {
closeProviderSheet()
emit('selectTarget', target)
}
@@ -255,10 +210,6 @@ const expandPanel = () => {
emit('collapsedChange', false)
}
const closeProviderSheet = () => {
providerSheetVisible.value = false
}
const getArrivalPanelElement = () => {
const rawRef = arrivalPanelRef.value
if (rawRef instanceof HTMLElement) return rawRef
@@ -325,7 +276,12 @@ const handleNavigateTap = async () => {
// #ifndef MP-WEIXIN
if (__APP_BUILD_PLATFORM__ !== 'mp-weixin') {
providerSheetVisible.value = true
isOpeningNavigation.value = true
try {
await navigateToMuseum()
} finally {
isOpeningNavigation.value = false
}
return
}
// #endif
@@ -360,18 +316,6 @@ const handleNavigateTap = async () => {
// #endif
}
// #ifndef MP-WEIXIN
const handleProviderSelect = (provider: ThirdPartyMapProviderOption) => {
const target = props.selectedTarget
if (!target) return
openThirdPartyMapSearch(provider.provider, {
keyword: target.keyword,
region: target.region
})
}
// #endif
watch(
() => props.visible,
(visible) => {
@@ -380,7 +324,6 @@ watch(
emit('collapsedChange', false)
startLayoutObserver()
} else {
closeProviderSheet()
isCollapsed.value = false
disconnectLayoutObserver()
emit('collapsedChange', false)
@@ -389,13 +332,6 @@ watch(
}
)
watch(
() => props.selectedTargetId,
() => {
closeProviderSheet()
}
)
watch(
() => [props.targets.length, isCollapsed.value] as const,
() => {
@@ -701,116 +637,6 @@ defineExpose({
color: #e0e100;
}
.provider-scrim {
position: fixed;
inset: 0;
z-index: 2190;
background: rgba(0, 0, 0, 0.28);
}
.provider-sheet {
position: fixed;
left: 50%;
bottom: calc(env(safe-area-inset-bottom) + 12px);
z-index: 2200;
width: min(430px, calc(100vw - 24px));
padding: 14px;
box-sizing: border-box;
background: #ffffff;
border-radius: 8px;
box-shadow: 0 12px 36px rgba(31, 35, 41, 0.22);
transform: translateX(-50%);
}
.provider-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.provider-title-group {
min-width: 0;
display: flex;
flex-direction: column;
gap: 3px;
}
.provider-kicker {
font-size: 12px;
line-height: 16px;
font-weight: 600;
color: #696962;
}
.provider-title {
font-size: 16px;
line-height: 22px;
font-weight: 700;
color: #262421;
}
.provider-close {
width: 32px;
height: 32px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
background: #f3f3f3;
border-radius: 8px;
}
.provider-close-text {
font-size: 22px;
line-height: 26px;
color: #424754;
}
.provider-row {
min-height: 52px;
display: flex;
align-items: center;
gap: 10px;
padding: 9px 0;
border-top: 1px solid #eceee7;
}
.provider-mark {
width: 30px;
height: 30px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
background: #262421;
border-radius: 8px;
}
.provider-mark-text {
font-size: 13px;
line-height: 18px;
font-weight: 700;
color: #e0e100;
}
.provider-label {
flex: 1;
font-size: 15px;
line-height: 21px;
font-weight: 600;
color: #262421;
}
.provider-action {
flex-shrink: 0;
font-size: 12px;
line-height: 17px;
font-weight: 600;
color: #696962;
}
.arrival-build-diagnostics {
position: fixed;
top: calc(env(safe-area-inset-top) + 8px);

View File

@@ -1,6 +1,6 @@
/**
* 地图导航调起服务
* 使用 uni.openLocation 调起设备地图 App 进行导航
* 使用原生地图 Scheme 调起设备地图 App 进行导航
*/
import { MUSEUM_LOCATION } from '@/config/museum'
@@ -18,6 +18,62 @@ export interface NavigationOptions {
scale?: number
}
type NavigationTarget = Pick<NavigationOptions, 'latitude' | 'longitude' | 'name'>
export type MobilePlatform = 'ios' | 'android'
const encode = (value: string) => encodeURIComponent(value)
export function buildBaiduNavigationUrl(target: NavigationTarget): string {
const destination = encode(
`latlng:${target.latitude},${target.longitude}|name:${target.name}`
)
return `baidumap://map/direction?destination=${destination}&coord_type=gcj02&mode=driving&src=museum-guide`
}
export function buildAmapNavigationUrl(
target: NavigationTarget,
platform: MobilePlatform
): string {
const scheme = platform === 'ios' ? 'iosamap://path' : 'androidamap://route'
return `${scheme}?sourceApplication=${encode('深圳自然博物馆')}&dlat=${target.latitude}&dlon=${target.longitude}&dname=${encode(target.name)}&dev=0&t=0`
}
const detectMobilePlatform = (): MobilePlatform => {
if (typeof window === 'undefined') {
return 'android'
}
const { userAgent, platform, maxTouchPoints } = window.navigator
const isIos = /iPhone|iPad|iPod/i.test(userAgent)
|| (platform === 'MacIntel' && maxTouchPoints > 1)
return isIos ? 'ios' : 'android'
}
const openNativeMapScheme = (url: string): boolean => {
if (typeof window === 'undefined' || !window.location) {
uni.showToast({
title: '请在 H5 浏览器中打开地图',
icon: 'none'
})
return false
}
try {
window.location.href = url
return true
} catch (error) {
console.error('调起地图应用失败:', error)
uni.showToast({
title: '无法调起地图应用',
icon: 'none'
})
return false
}
}
/**
* 调起外部地图应用进行导航
* @param options 导航选项
@@ -25,42 +81,27 @@ export interface NavigationOptions {
*/
export function openExternalNavigation(options: NavigationOptions): Promise<boolean> {
return new Promise((resolve) => {
const scale = options.scale ?? 18
uni.showActionSheet({
itemList: ['百度地图', '高德地图'],
success: ({ tapIndex }) => {
const url = tapIndex === 0
? buildBaiduNavigationUrl(options)
: buildAmapNavigationUrl(options, detectMobilePlatform())
uni.openLocation({
latitude: options.latitude,
longitude: options.longitude,
name: options.name,
address: options.address ?? '',
scale,
success: () => {
console.log('成功调起地图导航:', options.name)
resolve(true)
resolve(openNativeMapScheme(url))
},
fail: (err) => {
console.error('调起地图导航失败:', err)
// 根据错误类型提供友好的提示
if (err.errMsg) {
if (err.errMsg.includes('cancel')) {
// 用户取消
resolve(false)
} else if (err.errMsg.includes('auth deny') || err.errMsg.includes('auth reject')) {
uni.showToast({
title: '需要位置权限才能导航',
icon: 'none'
})
resolve(false)
} else {
uni.showToast({
title: '无法调起地图应用',
icon: 'none'
})
resolve(false)
}
} else {
if (err.errMsg?.includes('cancel')) {
resolve(false)
return
}
console.error('打开地图选择失败:', err)
uni.showToast({
title: '无法打开地图选择',
icon: 'none'
})
resolve(false)
}
})
})