fix: 修复导览按钮文案与三维路线显示联动

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
lyf
2026-06-26 04:17:03 +08:00
parent d4b97379bb
commit a7413ee037
5 changed files with 592 additions and 93 deletions

View File

@@ -9,6 +9,7 @@
>
<GuideMapShell
v-if="currentTab === 'guide'"
ref="guideMapShellRef"
:search-text="guideSearchText"
:active-mode="guideShellMode"
:mode-layout="'status'"
@@ -35,18 +36,18 @@
show-floor-header
:show-layer-mode-toggle="false"
:show-floor="is3DMode"
tools-top="calc(50% - 126px)"
tools-bottom="54px"
show-zoom-controls
zoom-controls-top="calc(100vh - 284px)"
show-location-control
location-control-bottom="54px"
show-more-control
more-control-bottom="54px"
:tools="guide3DTools"
:camera-view="guideCameraView"
:route-preview="activeRoutePreview"
:show-route="Boolean(activeRoutePreview)"
:auto-switch-threshold-low="0.58"
:auto-switch-threshold-high="1.18"
:outdoor-nav-polylines="outdoorNavPolylines"
@search-tap="handleGuideSearchTap"
@mode-change="handleModeChange"
@floor-change="handleFloorChange"
@@ -56,6 +57,7 @@
@tool-click="handleGuideToolClick"
@more-click="handleGuideMoreClick"
@auto-switch="handleAutoSwitch"
@map-tap="handleMapTapForOutdoorNav"
>
<template #overlay>
<view v-if="guideOutdoorState === 'entrance' && !is3DMode" class="entrance-tip">
@@ -123,16 +125,12 @@
<view class="more-popover-handle" @tap.stop="closeGuideMoreMenu"></view>
<view class="more-popover-grid">
<view class="more-action" @tap="handleMoreRouteGuide">
<text class="more-action-title">线路预览</text>
<text class="more-action-desc">室内到室内</text>
<text class="more-action-title">{{ routePlannerEntryTitle }}</text>
<text class="more-action-desc">{{ routePlannerEntryDesc }}</text>
</view>
<view class="more-action" @tap="handleMoreOutdoorReference">
<text class="more-action-title">室外参考</text>
<text class="more-action-desc">入口位置</text>
</view>
<view class="more-action" @tap="handleMoreRecommendedEntrance">
<text class="more-action-title">推荐入口</text>
<text class="more-action-desc">入口参考</text>
<view class="more-action" @tap="handleMoreOutdoorNav">
<text class="more-action-title">室外导航</text>
<text class="more-action-desc">导航到博物馆</text>
</view>
</view>
</view>
@@ -149,6 +147,7 @@
:summary="routePlanSummary"
:picker-loading="routeTargetsLoading"
:picker-error="routeTargetsError"
:route-ready="routeReady"
@update:start-point="handleRouteStartPointChange"
@update:end-point="handleRouteEndPointChange"
@picker-open="handleRoutePickerOpen"
@@ -189,6 +188,27 @@
</view>
</GuideMapShell>
<OutdoorNavigationPanel
v-if="currentTab === 'guide'"
:visible="showOutdoorNavPanel"
:loading="outdoorNavLoading"
:route-error="outdoorNavError"
:route-distance="outdoorNavRoute?.distance ?? 0"
:route-duration="outdoorNavRoute?.duration ?? 0"
:selecting-manual-start="outdoorNavSelectingManualStart"
:start-point="outdoorNavStartPoint ? { ...outdoorNavStartPoint, name: '已选位置' } : null"
:start-mode="outdoorNavManualMode || outdoorNavStartPoint ? 'manual' : 'gps'"
@close="handleOutdoorNavClose"
@gps-start="handleOutdoorNavGpsStart"
@manual-start-request="handleOutdoorNavManualStartRequest"
@manual-start-cancel="handleOutdoorNavManualStartCancel"
@manual-start-confirm="handleOutdoorNavManualStartConfirm"
@travel-mode-change="handleOutdoorNavTravelModeChange"
@start-navigation="handleOutdoorNavStartNavigation"
@clear-route="handleOutdoorNavClearRoute"
@back="handleOutdoorNavBack"
/>
<view v-else-if="currentTab === 'explain'" class="explain-page">
<ExplainHallSelect
:halls="explainHallItems"
@@ -207,6 +227,7 @@ import { onLoad } from '@dcloudio/uni-app'
import GuidePageFrame from '@/components/navigation/GuidePageFrame.vue'
import GuideMapShell from '@/components/navigation/GuideMapShell.vue'
import RoutePlannerPanel from '@/components/navigation/RoutePlannerPanel.vue'
import OutdoorNavigationPanel from '@/components/navigation/OutdoorNavigationPanel.vue'
import type {
RoutePointOption
} from '@/components/navigation/RoutePointPicker.vue'
@@ -236,6 +257,17 @@ import type {
MuseumExhibit,
MuseumFloor
} from '@/domain/museum'
import { MUSEUM_LOCATION, type TravelMode } from '@/config/museum'
import {
planWalkingRoute,
planDrivingRoute,
planTransitRoute,
type Route,
type RoutePoint
} from '@/services/tencent/TencentMapService'
import {
navigateToMuseum
} from '@/services/MapNavigationService'
type GuideIndoorView = 'overview' | 'floor' | 'multi'
type LayerDisplayMode = 'single' | 'multi'
@@ -282,6 +314,7 @@ const routeTargetsError = ref('')
const routePlanning = ref(false)
const routePlanError = ref('')
const routePreviewUnavailableMessage = ref('')
const routeReady = ref(false)
const activeRoutePreview = ref<GuideRouteResult | null>(null)
const showGuideMoreMenu = ref(false)
const isSimulatingRoute = ref(false)
@@ -289,6 +322,22 @@ const isPoiCardCollapsed = ref(false)
const guideMoreTouchStartY = ref(0)
const guideMoreTouchCurrentY = ref(0)
// GuideMapShell 组件引用,用于显式调用路线清除方法
const guideMapShellRef = ref<{
clearRoute?: () => void
} | null>(null)
// 室外导航相关状态
const showOutdoorNavPanel = ref(false)
const outdoorNavLoading = ref(false)
const outdoorNavError = ref('')
const outdoorNavRoute = ref<Route | null>(null)
const outdoorNavPolylines = ref<Array<{ points: Array<{ latitude: number; longitude: number }>; color: string; width: number }>>([])
const outdoorNavStartPoint = ref<{ latitude: number; longitude: number } | null>(null)
const outdoorNavTravelMode = ref<TravelMode>('walking')
const outdoorNavSelectingManualStart = ref(false)
const outdoorNavManualMode = ref(false)
const indoorNavAssetBaseUrl = guideUseCase.getAssetBaseUrl()
const indoorModelSource = guideUseCase.getModelSource()
const guideFloors = ref<MuseumFloor[]>([])
@@ -297,7 +346,7 @@ const indoorLayerMode = computed<LayerDisplayMode>(() => (
indoorView.value === 'multi' ? 'multi' : 'single'
))
const guide3DTools = computed(() => {
return is3DMode.value ? ['重置', '俯视', '斜视', '清除', '清路线'] : []
return is3DMode.value ? ['视角'] : []
})
const routePlanSummary = computed(() => {
@@ -314,7 +363,13 @@ const routePlanSummary = computed(() => {
return ''
})
const routePanelError = computed(() => routePlanError.value || routePreviewUnavailableMessage.value)
const routePanelError = computed(() => routePlanError.value)
const routePlannerEntryTitle = computed(() => '室内导览')
const routePlannerEntryDesc = computed(() => (
routeReady.value ? '开始路线规划' : '查看室内位置'
))
const toRoutePointOption = (target: GuideRouteTarget): RoutePointOption => ({
poiId: target.poiId,
@@ -333,9 +388,12 @@ const selectedGuidePoiSubtitle = computed(() => {
})
const routeSimulationStepText = computed(() => {
if (!activeRoutePreview.value) return '路线示意 · 查看预览'
if (!activeRoutePreview.value) return routeReady.value ? '室内导览 · 查看路线' : '室内导览 · 查看位置'
const hasConnector = activeRoutePreview.value.connectorPoints.length > 0
return hasConnector ? '路线示意 · 跨楼层连接点' : '路线示意 · 同层位置关系'
if (routeReady.value) {
return hasConnector ? '室内导览 · 跨楼层路线' : '室内导览 · 同层路线'
}
return hasConnector ? '室内导览 · 跨楼层位置关系' : '室内导览 · 同层位置关系'
})
const explainHallItems = ref<ExplainHallSelectItem[]>([])
@@ -384,6 +442,8 @@ const handleTabChange = (tabId: GuideTopTab) => {
showRoutePlanner.value = false
isSimulatingRoute.value = false
isPoiCardCollapsed.value = false
showOutdoorNavPanel.value = false
clearOutdoorNavState()
}
const syncTopTabFromHash = () => {
@@ -648,13 +708,14 @@ const handleSelectedPoiRelated = () => {
})
}
const handleGuideToolClick = (tool: string) => {
if (tool === '清除') {
selectedGuidePoi.value = null
} else if (tool === '清路线') {
clearRoutePreviewState()
isSimulatingRoute.value = false
}
const guideCameraView = ref<'reset' | 'top' | 'oblique'>('reset')
const handleGuideToolClick = (_tool: string) => {
// 视角循环切换:重置 -> 俯视 -> 斜视 -> 重置
const viewOrder: Array<'reset' | 'top' | 'oblique'> = ['reset', 'top', 'oblique']
const currentIndex = viewOrder.indexOf(guideCameraView.value)
const nextIndex = (currentIndex + 1) % viewOrder.length
guideCameraView.value = viewOrder[nextIndex]
}
const loadRouteTargets = async (keyword = '') => {
@@ -679,11 +740,13 @@ const loadRouteTargets = async (keyword = '') => {
const refreshRouteReadinessState = async () => {
try {
const readiness = await guideRouteUseCase.getRouteReadiness()
routeReady.value = readiness.ready
routePreviewUnavailableMessage.value = readiness.ready
? ''
: readiness.message || '当前暂不支持正式导航,可查看位置预览和路线示意'
} catch (error) {
console.warn('读取路线能力状态失败:', error)
routeReady.value = false
routePreviewUnavailableMessage.value = '当前暂不支持正式导航,可查看位置预览和路线示意'
}
}
@@ -703,6 +766,7 @@ const openRoutePlanner = () => {
}
const clearRoutePreviewState = () => {
guideMapShellRef.value?.clearRoute?.()
activeRoutePreview.value = null
routePlanError.value = ''
isSimulatingRoute.value = false
@@ -782,7 +846,7 @@ const handleRouteView = async ({ startPoint, endPoint }: { startPoint: RoutePoin
if (!result.route) {
activeRoutePreview.value = null
routePlanError.value = result.error || '路线预览生成失败'
routePlanError.value = result.error || '室内导览位置关系生成失败'
return
}
@@ -790,9 +854,9 @@ const handleRouteView = async ({ startPoint, endPoint }: { startPoint: RoutePoin
focusRouteFloor(result.route)
showRoutePlanner.value = true
} catch (error) {
console.error('路线预览生成失败:', error)
console.error('室内导览位置关系生成失败:', error)
activeRoutePreview.value = null
routePlanError.value = error instanceof Error ? error.message : '路线预览生成失败'
routePlanError.value = error instanceof Error ? error.message : '室内导览位置关系生成失败'
} finally {
routePlanning.value = false
}
@@ -864,6 +928,7 @@ const handleGuideMoreClick = () => {
const closeGuideMoreMenu = () => {
showGuideMoreMenu.value = false
showOutdoorNavPanel.value = false
}
const getGestureClientY = (event: TouchEvent | MouseEvent) => {
@@ -914,23 +979,181 @@ const handleMoreRouteGuide = () => {
openRoutePlanner()
}
const openOutdoorReference = (state: 'home' | 'entrance') => {
is3DMode.value = false
guideOutdoorState.value = state
indoorView.value = 'overview'
// 室外导航处理函数
const openOutdoorNavPanel = () => {
showOutdoorNavPanel.value = true
showGuideMoreMenu.value = false
showRoutePlanner.value = false
selectedGuidePoi.value = null
isPoiCardCollapsed.value = false
showRoutePlanner.value = false
showGuideMoreMenu.value = false
clearRoutePreviewState()
// 切换到室外 2D 地图
is3DMode.value = false
clearOutdoorNavState()
}
const handleMoreOutdoorReference = () => {
openOutdoorReference('home')
const clearOutdoorNavState = () => {
outdoorNavRoute.value = null
outdoorNavPolylines.value = []
outdoorNavStartPoint.value = null
outdoorNavError.value = ''
outdoorNavLoading.value = false
outdoorNavSelectingManualStart.value = false
outdoorNavManualMode.value = false
}
const handleMoreRecommendedEntrance = () => {
openOutdoorReference('entrance')
const handleMoreOutdoorNav = () => {
openOutdoorNavPanel()
}
const handleOutdoorNavClose = () => {
showOutdoorNavPanel.value = false
clearOutdoorNavState()
}
const handleOutdoorNavBack = () => {
showOutdoorNavPanel.value = false
clearOutdoorNavState()
}
const handleOutdoorNavGpsStart = () => {
outdoorNavLoading.value = true
outdoorNavError.value = ''
uni.getLocation({
type: 'gcj02',
success: (res) => {
console.log('GPS 定位成功:', res)
outdoorNavStartPoint.value = {
latitude: res.latitude,
longitude: res.longitude
}
void planOutdoorRoute({
latitude: res.latitude,
longitude: res.longitude
})
},
fail: (err) => {
console.error('GPS 定位失败:', err)
const errMsg = err.errMsg || ''
if (errMsg.includes('auth deny') || errMsg.includes('auth reject')) {
outdoorNavError.value = '定位权限被拒绝,请在设置中开启'
} else if (errMsg.includes('timeout')) {
outdoorNavError.value = '定位超时,请重试'
} else if (errMsg.includes('unavailable')) {
outdoorNavError.value = '位置服务不可用,请检查设备设置'
} else {
outdoorNavError.value = '定位失败,请检查位置权限'
}
outdoorNavLoading.value = false
}
})
}
const handleOutdoorNavManualStartRequest = () => {
outdoorNavManualMode.value = true
outdoorNavSelectingManualStart.value = true
uni.showToast({
title: '输入地址搜索,或点击地图选择起点',
icon: 'none'
})
}
const handleOutdoorNavManualStartCancel = () => {
outdoorNavManualMode.value = false
outdoorNavSelectingManualStart.value = false
}
const handleOutdoorNavManualStartConfirm = (location: { latitude: number; longitude: number }) => {
outdoorNavStartPoint.value = location
outdoorNavManualMode.value = false
outdoorNavSelectingManualStart.value = false
void planOutdoorRoute(location)
}
const handleOutdoorNavTravelModeChange = (mode: TravelMode) => {
outdoorNavTravelMode.value = mode
if (outdoorNavStartPoint.value) {
outdoorNavLoading.value = true
void planOutdoorRoute(outdoorNavStartPoint.value)
}
}
const planOutdoorRoute = async (from: { latitude: number; longitude: number }) => {
outdoorNavLoading.value = true
outdoorNavError.value = ''
const to: RoutePoint = {
latitude: MUSEUM_LOCATION.latitude,
longitude: MUSEUM_LOCATION.longitude
}
try {
let result
switch (outdoorNavTravelMode.value) {
case 'walking':
result = await planWalkingRoute(from, to)
break
case 'driving':
result = await planDrivingRoute(from, to)
break
case 'transit':
result = await planTransitRoute(from, to)
break
default:
result = await planWalkingRoute(from, to)
}
if (result.status === 0 && result.routes && result.routes.length > 0) {
const route = result.routes[0]
outdoorNavRoute.value = route
outdoorNavPolylines.value = [{
points: route.polyline,
color: '#E0E100',
width: 6
}]
console.log('路线规划成功:', {
distance: route.distance,
duration: route.duration,
steps: route.steps.length
})
} else {
outdoorNavError.value = result.message || '路线规划失败'
outdoorNavRoute.value = null
outdoorNavPolylines.value = []
}
} catch (error) {
console.error('路线规划异常:', error)
outdoorNavError.value = '路线规划失败,请重试'
outdoorNavRoute.value = null
outdoorNavPolylines.value = []
} finally {
outdoorNavLoading.value = false
}
}
const handleOutdoorNavStartNavigation = async () => {
// 直接调起导航
await navigateToMuseum()
}
const handleOutdoorNavClearRoute = () => {
outdoorNavRoute.value = null
outdoorNavPolylines.value = []
outdoorNavStartPoint.value = null
outdoorNavError.value = ''
}
// 处理地图点击(用于手动选择起点)
const handleMapTapForOutdoorNav = (location: { latitude?: number; longitude?: number }) => {
if (!outdoorNavSelectingManualStart.value) return
const latitude = location.latitude
const longitude = location.longitude
if (latitude !== undefined && longitude !== undefined) {
outdoorNavStartPoint.value = { latitude, longitude }
outdoorNavSelectingManualStart.value = false
void planOutdoorRoute({ latitude, longitude })
}
}
const guideShellMode = computed(() => (is3DMode.value ? '3d' : '2d'))