@@ -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);
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user