This commit is contained in:
@@ -41,17 +41,19 @@
|
||||
:show-floor-header="!isTwoDimensionalIndoorMode"
|
||||
:show-layer-mode-toggle="false"
|
||||
:show-floor="isBuildingGuideActive"
|
||||
:show-zoom-controls="isBuildingGuideActive"
|
||||
:show-zoom-controls="true"
|
||||
zoom-controls-top="calc(100vh - 292px)"
|
||||
:route-preview="activeRoutePreview"
|
||||
:show-route="Boolean(activeRoutePreview)"
|
||||
:route-navigation-active="isSimulatingRoute"
|
||||
:route-start-selection-active="routeStartSelectionActive"
|
||||
:route-selectable-poi-ids="routeSelectablePoiIds"
|
||||
:route-selectable-points="routePointOptions"
|
||||
:outdoor-markers="homeArrivalFeatureEnabled ? arrivalOutdoorMarkers : []"
|
||||
:active-outdoor-marker-id="homeArrivalFeatureEnabled ? activeArrivalMarkerId : ''"
|
||||
:outdoor-focus-offset-y="homeArrivalFeatureEnabled ? arrivalOutdoorFocusOffsetY : 0"
|
||||
:route-selectable-poi-ids="routeSelectablePoiIds"
|
||||
:route-selectable-points="routePointOptions"
|
||||
:outdoor-nav-polylines="homeArrivalFeatureEnabled ? outdoorNavPolylines : []"
|
||||
:outdoor-markers="homeArrivalFeatureEnabled ? outdoorMapMarkers : []"
|
||||
:active-outdoor-marker-id="homeArrivalFeatureEnabled ? activeArrivalMarkerId : ''"
|
||||
:outdoor-focus-offset-y="outdoorArrivalStage === 'route' ? 170 : (arrivalActiveType ? 180 : 0)"
|
||||
:semantic-zoom-enabled="semanticMapZoomEnabled"
|
||||
:disable-auto-exit="disableIndoorAutoExit"
|
||||
@mode-change="handleModeChange"
|
||||
@floor-request="handleFloorRequest"
|
||||
@@ -71,8 +73,12 @@
|
||||
@indoor-render-mode-fallback="handleIndoorRenderModeFallback"
|
||||
@indoor-render-mode-change="handleIndoorRenderModeChange"
|
||||
@scene-viewport-change="handleGuideSceneViewportChange"
|
||||
@tool-click="handleIndoorToolClick"
|
||||
@outdoor-marker-click="handleArrivalMarkerClick"
|
||||
@tool-click="handleIndoorToolClick"
|
||||
@outdoor-marker-click="handleOutdoorMarkerClick"
|
||||
@map-tap="handleOutdoorMapTap"
|
||||
@semantic-enter-building="handleSemanticEnterBuilding"
|
||||
@semantic-exit-outdoor="handleSemanticExitOutdoor"
|
||||
@outdoor-unavailable="handleOutdoorMapUnavailable"
|
||||
>
|
||||
<template #overlay>
|
||||
<view v-if="homeArrivalFeatureEnabled && guideOutdoorState === 'entrance' && !isBuildingGuideActive" class="entrance-tip">
|
||||
@@ -92,6 +98,7 @@
|
||||
<view
|
||||
v-if="showGuideFloatingActions"
|
||||
class="guide-quick-actions"
|
||||
:class="{ 'is-outdoor': showOutdoorNavigationPanel }"
|
||||
>
|
||||
<view
|
||||
v-if="homeArrivalFeatureEnabled"
|
||||
@@ -196,20 +203,48 @@
|
||||
</view>
|
||||
</GuideMapShell>
|
||||
|
||||
<ArrivalPanel
|
||||
ref="arrivalPanelRef"
|
||||
v-if="homeArrivalFeatureEnabled && currentTab === 'guide'"
|
||||
:visible="showArrivalPanel"
|
||||
:active-type="activeArrivalType"
|
||||
:targets="activeArrivalTargets"
|
||||
:selected-target-id="selectedArrivalTargetId"
|
||||
:selected-target="selectedArrivalTarget"
|
||||
@update:active-type="handleArrivalTypeChange"
|
||||
@select-target="handleArrivalTargetSelect"
|
||||
@collapsed-change="handleArrivalPanelCollapsedChange"
|
||||
@layout-change="handleArrivalPanelLayoutChange"
|
||||
<OutdoorArrivalRoutePanel
|
||||
v-if="outdoorArrivalStage === 'route' && homeArrivalFeatureEnabled && currentTab === 'guide'"
|
||||
:visible="showOutdoorNavigationPanel"
|
||||
:phase="outdoorRouteStartPhase"
|
||||
:location-message="outdoorLocationMessage"
|
||||
:location-help-visible="outdoorLocationHelpVisible"
|
||||
:loading="outdoorRouteLoading"
|
||||
:route-error="outdoorRouteError"
|
||||
:route-distance="outdoorRoute?.distance || 0"
|
||||
:route-duration="outdoorRoute?.duration || 0"
|
||||
:start-point="outdoorRouteStart"
|
||||
:destination="selectedArrivalTarget"
|
||||
:manual-search-results="outdoorStartSearchResults"
|
||||
:manual-search-loading="outdoorStartSearchLoading"
|
||||
@retry-location="handleOutdoorLocationRetry"
|
||||
@manual-start-request="handleOutdoorManualSearchRequest"
|
||||
@manual-map-request="handleOutdoorManualMapRequest"
|
||||
@manual-start-confirm="handleOutdoorManualStartConfirm"
|
||||
@manual-start-search="handleOutdoorManualStartSearch"
|
||||
@confirm-start="handleOutdoorRouteStartConfirm"
|
||||
@retry-route="handleOutdoorRouteRetry"
|
||||
@back="returnToArrivalDetail"
|
||||
/>
|
||||
|
||||
<ArrivalPanel
|
||||
v-if="showArrivalDestinationPanel && homeArrivalFeatureEnabled && currentTab === 'guide'"
|
||||
:visible="showOutdoorNavigationPanel"
|
||||
:mode="outdoorArrivalStage === 'detail' ? 'detail' : 'browse'"
|
||||
:active-type="arrivalActiveType"
|
||||
:targets="arrivalTargets"
|
||||
:selected-target-id="selectedArrivalTarget?.id || ''"
|
||||
:selected-target="selectedArrivalTarget"
|
||||
:loading="arrivalTargetsLoading"
|
||||
:error="arrivalTargetsError"
|
||||
@type-change="handleArrivalTypeChange"
|
||||
@select-target="handleArrivalTargetSelect"
|
||||
@navigate="handleArrivalNavigate"
|
||||
@back="returnToArrivalBrowse"
|
||||
@collapse="collapseArrivalBrowse"
|
||||
@retry="arrivalActiveType && loadArrivalTargets(arrivalActiveType)"
|
||||
/>
|
||||
|
||||
<view v-else-if="currentTab === 'explain'" class="explain-page">
|
||||
<ExplainHallSelect
|
||||
:halls="explainHallItems"
|
||||
@@ -261,6 +296,9 @@ import GuidePoiSummaryCard, {
|
||||
type GuidePoiSummaryAction
|
||||
} from '@/components/map/GuidePoiSummaryCard.vue'
|
||||
import RoutePlannerPanel from '@/components/navigation/RoutePlannerPanel.vue'
|
||||
import OutdoorArrivalRoutePanel, {
|
||||
type OutdoorRouteStartPhase
|
||||
} from '@/components/navigation/OutdoorArrivalRoutePanel.vue'
|
||||
import ArrivalPanel from '@/components/navigation/ArrivalPanel.vue'
|
||||
import PoiSearchPanel from '@/components/search/PoiSearchPanel.vue'
|
||||
import type {
|
||||
@@ -313,11 +351,6 @@ import {
|
||||
type GuideRenderMode
|
||||
} from '@/composables/useGuideSceneState'
|
||||
import type { ResetViewBaselineResult } from '@/components/map/ThreeMap.vue'
|
||||
import {
|
||||
ARRIVAL_TARGETS_BY_TYPE,
|
||||
type ArrivalSearchTarget,
|
||||
type ArrivalTargetType
|
||||
} from '@/data/arrivalSearchTargets'
|
||||
import type {
|
||||
GuideRouteResult,
|
||||
GuideRouteTarget,
|
||||
@@ -331,6 +364,31 @@ import { getFloorSortLevel } from '@/domain/guideFloor'
|
||||
import { normalizeVisitorPoiDisplayName } from '@/view-models/visitorPoiPresentation'
|
||||
import { getBrowserNetworkInfo } from '@/services/performance/backgroundPreloadScheduler'
|
||||
import { resolveInitialGuideRenderMode } from '@/services/performance/guideRenderModePolicy'
|
||||
import { MUSEUM_LOCATION, type TravelMode } from '@/config/museum'
|
||||
import {
|
||||
BrowserLocationError,
|
||||
getBrowserLocationPermissionState,
|
||||
getBrowserGcjLocation
|
||||
} from '@/services/outdoor/BrowserLocationService'
|
||||
import {
|
||||
refreshOutdoorMapProfile,
|
||||
subscribeOutdoorMapAvailability,
|
||||
planOutdoorRoute,
|
||||
reverseGeocodeOutdoorLocation,
|
||||
searchOutdoorPlaces,
|
||||
suggestOutdoorPlaces,
|
||||
type OutdoorPlace,
|
||||
type OutdoorRoutePlan
|
||||
} from '@/services/outdoor/OutdoorMapAppService'
|
||||
import {
|
||||
ARRIVAL_SEARCH_REGION,
|
||||
ARRIVAL_TARGET_TYPES,
|
||||
type ArrivalSearchTarget,
|
||||
type ArrivalTargetType
|
||||
} from '@/data/arrivalSearchTargets'
|
||||
// #ifdef H5
|
||||
import { preloadTencentJsMap } from '@/services/outdoor/TencentJsMapLoader'
|
||||
// #endif
|
||||
|
||||
type GuideIndoorView = 'overview' | 'floor' | 'multi'
|
||||
type LayerDisplayMode = 'single' | 'multi'
|
||||
@@ -370,8 +428,48 @@ const shouldShowLaunchOverlay = () => {
|
||||
}
|
||||
|
||||
const initialTopTab = (): GuideTopTab => topTabFromHash() || 'guide'
|
||||
// 到馆服务暂不对外开放;保留实现,后续仅需恢复此开关即可。
|
||||
const homeArrivalFeatureEnabled = false
|
||||
const homeArrivalFeatureEnabled = ref(false)
|
||||
let outdoorMapPreloadStarted = false
|
||||
let outdoorMapPreloadTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let stopOutdoorMapAvailabilitySubscription: (() => void) | null = null
|
||||
|
||||
const handleOutdoorMapUnavailable = (message?: string) => {
|
||||
homeArrivalFeatureEnabled.value = false
|
||||
if (showOutdoorNavigationPanel.value || !isBuildingGuideActive.value) {
|
||||
closeArrivalPanel()
|
||||
}
|
||||
if (message) console.warn('来馆服务暂不可用:', message)
|
||||
}
|
||||
|
||||
const refreshOutdoorMapAvailability = async () => {
|
||||
try {
|
||||
const profile = await refreshOutdoorMapProfile()
|
||||
homeArrivalFeatureEnabled.value = profile.available
|
||||
if (profile.available) {
|
||||
preloadOutdoorMapAfterInitialView()
|
||||
return
|
||||
}
|
||||
|
||||
if (showOutdoorNavigationPanel.value || !isBuildingGuideActive.value) closeArrivalPanel()
|
||||
} catch (error) {
|
||||
console.warn('读取来馆服务状态失败:', error)
|
||||
handleOutdoorMapUnavailable()
|
||||
}
|
||||
}
|
||||
|
||||
const preloadOutdoorMapAfterInitialView = () => {
|
||||
// #ifdef H5
|
||||
if (!homeArrivalFeatureEnabled.value || outdoorMapPreloadStarted || typeof window === 'undefined') return
|
||||
outdoorMapPreloadStarted = true
|
||||
outdoorMapPreloadTimer = setTimeout(() => {
|
||||
outdoorMapPreloadTimer = null
|
||||
void preloadTencentJsMap().catch((error) => {
|
||||
console.warn('腾讯地图预加载失败:', error)
|
||||
handleOutdoorMapUnavailable(error instanceof Error ? error.message : '腾讯地图预加载失败')
|
||||
})
|
||||
}, 600)
|
||||
// #endif
|
||||
}
|
||||
|
||||
// Building guide and outdoor-arrival maps are separate products. This flag is
|
||||
// deliberately independent from the 2D/3D renderer used by the building guide.
|
||||
@@ -420,14 +518,6 @@ let pendingPoiDetailUiContext: Readonly<{
|
||||
|
||||
// 状态
|
||||
const currentTab = ref<GuideTopTab>(initialTopTab())
|
||||
const topTabNavigationTitle = (tab: GuideTopTab) => (
|
||||
tab === 'explain' ? '免费讲解' : '地图导览'
|
||||
)
|
||||
|
||||
watch(currentTab, (tab) => {
|
||||
syncHostNavigationTitle(topTabNavigationTitle(tab))
|
||||
}, { immediate: true })
|
||||
|
||||
const launchOverlayVisible = ref(shouldShowLaunchOverlay())
|
||||
const launchOverlayLeaving = ref(false)
|
||||
const launchProgress = ref(8)
|
||||
@@ -608,12 +698,39 @@ const settleLaunchOverlayByModel = (status: 'ready' | 'failed', event?: { view:
|
||||
hideLaunchOverlay()
|
||||
}
|
||||
|
||||
const showArrivalPanel = ref(false)
|
||||
const showArrivalPanelCollapsed = ref(false)
|
||||
const arrivalPanelRef = ref<InstanceType<typeof ArrivalPanel> | null>(null)
|
||||
const activeArrivalType = ref<ArrivalTargetType>('bus')
|
||||
const selectedArrivalTargetId = ref('')
|
||||
const arrivalPanelHeight = ref(0)
|
||||
const showOutdoorNavigationPanel = ref(false)
|
||||
// The arrival journey keeps destination selection separate from route planning:
|
||||
// browse nearby transport/parking, confirm one destination, then choose a start.
|
||||
type OutdoorArrivalStage = 'browse' | 'detail' | 'route'
|
||||
const ARRIVAL_TARGET_CACHE_TTL_MS = 5 * 60 * 1000
|
||||
|
||||
type ArrivalTargetCacheEntry = {
|
||||
expiresAt: number
|
||||
items: ArrivalSearchTarget[]
|
||||
}
|
||||
|
||||
const outdoorArrivalStage = ref<OutdoorArrivalStage>('browse')
|
||||
const outdoorRouteStartPhase = ref<OutdoorRouteStartPhase>('locating')
|
||||
const outdoorLocationMessage = ref('')
|
||||
const outdoorLocationHelpVisible = ref(false)
|
||||
const arrivalActiveType = ref<ArrivalTargetType | null>(null)
|
||||
const arrivalTargets = ref<ArrivalSearchTarget[]>([])
|
||||
const arrivalTargetsLoading = ref(false)
|
||||
const arrivalTargetsError = ref('')
|
||||
const selectedArrivalTarget = ref<ArrivalSearchTarget | null>(null)
|
||||
const outdoorStartSearchResults = ref<OutdoorPlace[]>([])
|
||||
const outdoorStartSearchLoading = ref(false)
|
||||
const arrivalTargetCache = new Map<ArrivalTargetType, ArrivalTargetCacheEntry>()
|
||||
let arrivalTargetRequestId = 0
|
||||
let outdoorStartSearchRequestId = 0
|
||||
let outdoorManualReverseGeocodeRequestId = 0
|
||||
let outdoorStartLocationRequestId = 0
|
||||
let outdoorRoutePlanRequestId = 0
|
||||
const outdoorRouteStart = ref<{ latitude: number; longitude: number; name?: string } | null>(null)
|
||||
const outdoorTravelMode = ref<TravelMode>('walking')
|
||||
const outdoorRoute = ref<OutdoorRoutePlan | null>(null)
|
||||
const outdoorRouteLoading = ref(false)
|
||||
const outdoorRouteError = ref('')
|
||||
|
||||
const indoorNavAssetBaseUrl = guideUseCase.getAssetBaseUrl()
|
||||
const indoorModelSource = guideUseCase.getModelSource()
|
||||
@@ -697,11 +814,27 @@ const disableIndoorAutoExit = computed(() => (
|
||||
|| Boolean(searchTargetFocusRequest.value)
|
||||
))
|
||||
|
||||
// Semantic zoom only changes the presentation layer while the visitor is
|
||||
// browsing. Route planning and active navigation keep ownership of the map.
|
||||
const semanticMapZoomEnabled = computed(() => (
|
||||
currentTab.value === 'guide'
|
||||
&& !showRoutePlanner.value
|
||||
&& !routePlanning.value
|
||||
&& !routeStartSelectionActive.value
|
||||
&& !activeRoutePreview.value
|
||||
&& !isSimulatingRoute.value
|
||||
&& !selectedGuidePoi.value
|
||||
&& !homeSearchExpanded.value
|
||||
&& outdoorArrivalStage.value !== 'route'
|
||||
&& !outdoorRouteLoading.value
|
||||
&& !outdoorRoute.value
|
||||
))
|
||||
|
||||
const showGuideHomeDock = computed(() => (
|
||||
currentTab.value === 'guide'
|
||||
&& isBuildingGuideActive.value
|
||||
&& !showRoutePlanner.value
|
||||
&& !showArrivalPanel.value
|
||||
&& !showOutdoorNavigationPanel.value
|
||||
&& !selectedGuidePoi.value
|
||||
&& !isSimulatingRoute.value
|
||||
&& !(guideOutdoorState.value === 'entrance' && !isBuildingGuideActive.value)
|
||||
@@ -710,44 +843,97 @@ const showGuideHomeDock = computed(() => (
|
||||
const showGuideFloatingActions = computed(() => (
|
||||
currentTab.value === 'guide'
|
||||
&& !showRoutePlanner.value
|
||||
&& (!showArrivalPanel.value || showArrivalPanelCollapsed.value)
|
||||
&& !selectedGuidePoi.value
|
||||
&& !isSimulatingRoute.value
|
||||
&& (!showGuideHomeDock.value || !homeSearchExpanded.value)
|
||||
))
|
||||
|
||||
const activeArrivalTargets = computed(() => ARRIVAL_TARGETS_BY_TYPE[activeArrivalType.value])
|
||||
const outdoorNavPolylines = computed(() => outdoorRoute.value?.polyline.length
|
||||
? [{ points: outdoorRoute.value.polyline, color: '#1565C0', width: 6 }]
|
||||
: []
|
||||
)
|
||||
|
||||
const selectedArrivalTarget = computed(() => (
|
||||
activeArrivalTargets.value.find((target) => target.id === selectedArrivalTargetId.value)
|
||||
|| activeArrivalTargets.value[0]
|
||||
|| null
|
||||
const showArrivalDestinationPanel = computed(() => (
|
||||
showOutdoorNavigationPanel.value
|
||||
&& (outdoorArrivalStage.value === 'browse' || outdoorArrivalStage.value === 'detail')
|
||||
))
|
||||
|
||||
const arrivalOutdoorMarkers = computed(() => {
|
||||
if (!showArrivalPanel.value || isBuildingGuideActive.value) return []
|
||||
const outdoorMapMarkers = computed(() => {
|
||||
if (!showOutdoorNavigationPanel.value) return []
|
||||
|
||||
return activeArrivalTargets.value.map((target) => ({
|
||||
id: target.id,
|
||||
title: target.title,
|
||||
subtitle: target.subtitle,
|
||||
latitude: target.latitude,
|
||||
longitude: target.longitude
|
||||
}))
|
||||
if (outdoorArrivalStage.value === 'browse' || outdoorArrivalStage.value === 'detail') {
|
||||
// Tencent base-map POIs are not interactive in the JS API. Every
|
||||
// selected transport category therefore uses a tappable blue marker.
|
||||
const museumMarker = {
|
||||
id: 'museum-destination',
|
||||
title: MUSEUM_LOCATION.name,
|
||||
subtitle: MUSEUM_LOCATION.address,
|
||||
latitude: MUSEUM_LOCATION.latitude,
|
||||
longitude: MUSEUM_LOCATION.longitude,
|
||||
markerKind: 'museum' as const
|
||||
}
|
||||
if (outdoorArrivalStage.value === 'browse' && arrivalActiveType.value) {
|
||||
return [
|
||||
museumMarker,
|
||||
...arrivalTargets.value.map((target) => ({
|
||||
id: target.id,
|
||||
title: target.title,
|
||||
subtitle: target.subtitle,
|
||||
latitude: target.latitude,
|
||||
longitude: target.longitude,
|
||||
markerKind: target.type
|
||||
}))
|
||||
]
|
||||
}
|
||||
if (outdoorArrivalStage.value === 'detail' && selectedArrivalTarget.value) {
|
||||
return [
|
||||
museumMarker,
|
||||
{
|
||||
id: selectedArrivalTarget.value.id,
|
||||
title: selectedArrivalTarget.value.title,
|
||||
subtitle: selectedArrivalTarget.value.subtitle,
|
||||
latitude: selectedArrivalTarget.value.latitude,
|
||||
longitude: selectedArrivalTarget.value.longitude,
|
||||
markerKind: selectedArrivalTarget.value.type
|
||||
}
|
||||
]
|
||||
}
|
||||
return [museumMarker]
|
||||
}
|
||||
|
||||
if (outdoorArrivalStage.value === 'route' && selectedArrivalTarget.value) {
|
||||
return [
|
||||
...(outdoorRouteStart.value ? [{
|
||||
id: 'outdoor-route-start',
|
||||
title: outdoorRouteStart.value.name || '起点',
|
||||
subtitle: '起点',
|
||||
latitude: outdoorRouteStart.value.latitude,
|
||||
longitude: outdoorRouteStart.value.longitude,
|
||||
markerKind: 'user' as const
|
||||
}] : []),
|
||||
{
|
||||
id: 'outdoor-route-destination',
|
||||
title: selectedArrivalTarget.value.title,
|
||||
subtitle: selectedArrivalTarget.value.subtitle,
|
||||
latitude: selectedArrivalTarget.value.latitude,
|
||||
longitude: selectedArrivalTarget.value.longitude
|
||||
}
|
||||
]
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
const activeArrivalMarkerId = computed(() => (
|
||||
showArrivalPanel.value && !isBuildingGuideActive.value ? selectedArrivalTarget.value?.id || '' : ''
|
||||
))
|
||||
|
||||
const arrivalOutdoorFocusOffsetY = computed(() => {
|
||||
if (!showArrivalPanel.value || showArrivalPanelCollapsed.value || isBuildingGuideActive.value) return 0
|
||||
return Math.min(260, Math.max(0, Math.round(arrivalPanelHeight.value / 2 + 24)))
|
||||
})
|
||||
const activeArrivalMarkerId = computed(() => (
|
||||
!showOutdoorNavigationPanel.value ? ''
|
||||
: outdoorArrivalStage.value === 'route' ? 'outdoor-route-destination'
|
||||
: outdoorArrivalStage.value === 'detail' && selectedArrivalTarget.value
|
||||
? selectedArrivalTarget.value.id
|
||||
: 'museum-destination'
|
||||
))
|
||||
|
||||
const guideQuickActiveAction = computed<'indoor' | 'arrival' | 'explain'>(() => {
|
||||
if (currentTab.value === 'explain') return 'explain'
|
||||
if (showArrivalPanel.value) return 'arrival'
|
||||
if (showOutdoorNavigationPanel.value) return 'arrival'
|
||||
return isBuildingGuideActive.value ? 'indoor' : 'arrival'
|
||||
})
|
||||
|
||||
@@ -987,6 +1173,12 @@ onMounted(() => {
|
||||
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
if (isEmbeddedInWechatMiniProgram()) {
|
||||
syncHostNavigationTitle('地图导览')
|
||||
}
|
||||
|
||||
stopOutdoorMapAvailabilitySubscription = subscribeOutdoorMapAvailability(handleOutdoorMapUnavailable)
|
||||
void refreshOutdoorMapAvailability()
|
||||
syncTopTabFromHash()
|
||||
removePoiSearchOverlayFromCurrentHistoryState()
|
||||
window.addEventListener('hashchange', syncTopTabFromHash)
|
||||
@@ -997,6 +1189,14 @@ onUnmounted(() => {
|
||||
launchOverlayRunId += 1
|
||||
clearLaunchTimers()
|
||||
|
||||
if (outdoorMapPreloadTimer) {
|
||||
clearTimeout(outdoorMapPreloadTimer)
|
||||
outdoorMapPreloadTimer = null
|
||||
}
|
||||
|
||||
stopOutdoorMapAvailabilitySubscription?.()
|
||||
stopOutdoorMapAvailabilitySubscription = null
|
||||
|
||||
if (indoorGestureHintTimer) {
|
||||
clearTimeout(indoorGestureHintTimer)
|
||||
indoorGestureHintTimer = null
|
||||
@@ -1171,6 +1371,7 @@ const handleInitialModelReady = (event: { view: GuideIndoorView; floorId?: strin
|
||||
guideModelState.initializeModel({ view: event.view, floorId: readyFloorId })
|
||||
}
|
||||
settleLaunchOverlayByModel('ready', event)
|
||||
preloadOutdoorMapAfterInitialView()
|
||||
const context = guideModelState.consumePendingViewBaselineReset()
|
||||
if (context) {
|
||||
void resetGuideModelToFloorBaseline({
|
||||
@@ -1642,6 +1843,37 @@ const handleEnterBuildingGuide = () => {
|
||||
showIndoorHint()
|
||||
}
|
||||
|
||||
const handleSemanticEnterBuilding = () => {
|
||||
if (!semanticMapZoomEnabled.value || isBuildingGuideActive.value) return
|
||||
|
||||
showOutdoorNavigationPanel.value = false
|
||||
clearOutdoorRoute()
|
||||
indoorView.value = 'overview'
|
||||
isBuildingGuideActive.value = true
|
||||
guideOutdoorState.value = 'home'
|
||||
guideSceneState.commit({
|
||||
view: 'overview',
|
||||
floorId: activeGuideFloor.value
|
||||
})
|
||||
}
|
||||
|
||||
const handleSemanticExitOutdoor = () => {
|
||||
if (!homeArrivalFeatureEnabled.value || !semanticMapZoomEnabled.value || !isBuildingGuideActive.value) return
|
||||
|
||||
// Zooming out of the building is also an entry into the arrival map. Keep
|
||||
// its first screen consistent with tapping the "来馆" quick action.
|
||||
showOutdoorNavigationPanel.value = true
|
||||
outdoorArrivalStage.value = 'browse'
|
||||
clearOutdoorRoute()
|
||||
arrivalActiveType.value = null
|
||||
arrivalTargets.value = []
|
||||
arrivalTargetsError.value = ''
|
||||
selectedArrivalTarget.value = null
|
||||
indoorView.value = 'overview'
|
||||
isBuildingGuideActive.value = false
|
||||
guideOutdoorState.value = 'home'
|
||||
}
|
||||
|
||||
const handleModeChange = (mode: '2d' | '3d') => {
|
||||
isBuildingGuideActive.value = mode === '3d'
|
||||
|
||||
@@ -1730,61 +1962,371 @@ const handleMoreRouteGuide = async () => {
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
isSimulatingRoute.value = false
|
||||
const floorId = activeGuideFloor.value
|
||||
const floorLabel = getGuideFloorLabel(floorId) || '默认楼层'
|
||||
if (!floorId) {
|
||||
showIndoorHint('暂未获取到可进入的楼层,请稍后重试', 3200)
|
||||
indoorView.value = 'overview'
|
||||
requestedFloorId.value = ''
|
||||
requestedFloorLabel.value = ''
|
||||
loadingFloorId.value = ''
|
||||
failedFloorId.value = ''
|
||||
guideSceneState.commit({
|
||||
view: 'overview',
|
||||
floorId: activeGuideFloor.value
|
||||
})
|
||||
}
|
||||
|
||||
const toArrivalSearchTarget = (place: OutdoorPlace, type: ArrivalTargetType): ArrivalSearchTarget => ({
|
||||
id: place.id || `${type}:${place.latitude}:${place.longitude}`,
|
||||
type,
|
||||
title: place.title,
|
||||
subtitle: place.address || place.category || '周边服务点',
|
||||
keyword: place.title,
|
||||
region: ARRIVAL_SEARCH_REGION,
|
||||
latitude: place.latitude,
|
||||
longitude: place.longitude,
|
||||
distance: place.distance
|
||||
})
|
||||
|
||||
const loadArrivalTargets = async (type: ArrivalTargetType) => {
|
||||
const requestId = ++arrivalTargetRequestId
|
||||
const config = ARRIVAL_TARGET_TYPES.find((item) => item.type === type)
|
||||
if (!config) return
|
||||
|
||||
const cached = arrivalTargetCache.get(type)
|
||||
if (cached && cached.expiresAt > Date.now()) {
|
||||
arrivalTargetsLoading.value = false
|
||||
arrivalTargetsError.value = ''
|
||||
arrivalTargets.value = cached.items
|
||||
selectedArrivalTarget.value = null
|
||||
return
|
||||
}
|
||||
arrivalTargetCache.delete(type)
|
||||
arrivalTargetsLoading.value = true
|
||||
arrivalTargetsError.value = ''
|
||||
arrivalTargets.value = []
|
||||
selectedArrivalTarget.value = null
|
||||
try {
|
||||
const response = await searchOutdoorPlaces({
|
||||
keyword: config.keyword,
|
||||
region: ARRIVAL_SEARCH_REGION,
|
||||
latitude: MUSEUM_LOCATION.latitude,
|
||||
longitude: MUSEUM_LOCATION.longitude,
|
||||
pageSize: 12
|
||||
})
|
||||
if (requestId !== arrivalTargetRequestId) return
|
||||
|
||||
requestedFloorId.value = floorId
|
||||
requestedFloorLabel.value = floorLabel
|
||||
loadingFloorId.value = floorId
|
||||
failedFloorId.value = ''
|
||||
// 渲染器提交 floor-change 前仍保持建筑外观,避免楼层控件显示假状态。
|
||||
showIndoorHint(`正在进入 ${floorLabel}…`, 3200)
|
||||
await guideMapShellRef.value?.switchFloor?.(floorId)
|
||||
}
|
||||
|
||||
const selectFirstArrivalTarget = () => {
|
||||
selectedArrivalTargetId.value = activeArrivalTargets.value[0]?.id || ''
|
||||
}
|
||||
|
||||
const resetArrivalPanelState = () => {
|
||||
activeArrivalType.value = 'bus'
|
||||
selectFirstArrivalTarget()
|
||||
}
|
||||
|
||||
const closeArrivalPanel = () => {
|
||||
showArrivalPanel.value = false
|
||||
showArrivalPanelCollapsed.value = false
|
||||
selectedArrivalTargetId.value = ''
|
||||
arrivalPanelHeight.value = 0
|
||||
}
|
||||
|
||||
const handleArrivalPanelCollapsedChange = (collapsed: boolean) => {
|
||||
showArrivalPanelCollapsed.value = collapsed
|
||||
}
|
||||
|
||||
const handleArrivalPanelLayoutChange = (layout: { height: number; collapsed: boolean }) => {
|
||||
arrivalPanelHeight.value = layout.height
|
||||
showArrivalPanelCollapsed.value = layout.collapsed
|
||||
const uniqueTargets = new Map<string, ArrivalSearchTarget>()
|
||||
response.items.forEach((place) => {
|
||||
const target = toArrivalSearchTarget(place, type)
|
||||
if (!uniqueTargets.has(target.id)) uniqueTargets.set(target.id, target)
|
||||
})
|
||||
const items = [...uniqueTargets.values()]
|
||||
arrivalTargetCache.set(type, {
|
||||
expiresAt: Date.now() + ARRIVAL_TARGET_CACHE_TTL_MS,
|
||||
items
|
||||
})
|
||||
arrivalTargets.value = items
|
||||
} catch (error) {
|
||||
if (requestId !== arrivalTargetRequestId) return
|
||||
console.error('读取来馆分类点位失败:', error)
|
||||
arrivalTargetsError.value = '周边点位暂时无法加载'
|
||||
} finally {
|
||||
if (requestId === arrivalTargetRequestId) arrivalTargetsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleArrivalTypeChange = (type: ArrivalTargetType) => {
|
||||
activeArrivalType.value = type
|
||||
selectFirstArrivalTarget()
|
||||
if (type === arrivalActiveType.value) return
|
||||
arrivalActiveType.value = type
|
||||
outdoorArrivalStage.value = 'browse'
|
||||
void loadArrivalTargets(type)
|
||||
}
|
||||
|
||||
const handleArrivalTargetSelect = (target: ArrivalSearchTarget) => {
|
||||
selectedArrivalTargetId.value = target.id
|
||||
selectedArrivalTarget.value = target
|
||||
outdoorArrivalStage.value = 'detail'
|
||||
}
|
||||
|
||||
const handleArrivalMarkerClick = (markerId: string) => {
|
||||
if (!homeArrivalFeatureEnabled) return
|
||||
const target = activeArrivalTargets.value.find((item) => item.id === markerId)
|
||||
if (!target) return
|
||||
selectedArrivalTargetId.value = target.id
|
||||
const handleArrivalNavigate = (target: ArrivalSearchTarget) => {
|
||||
selectedArrivalTarget.value = target
|
||||
// The selected arrival category already establishes the route strategy.
|
||||
// Transit points use Tencent transit planning; parking uses driving.
|
||||
outdoorTravelMode.value = target.type === 'parking' ? 'driving' : 'transit'
|
||||
outdoorArrivalStage.value = 'route'
|
||||
clearOutdoorRoute()
|
||||
// Real-time location is the preferred zero-step route start. Manual input
|
||||
// and map selection retain an explicit confirmation because they are
|
||||
// visitor-selected coordinates.
|
||||
void requestOutdoorCurrentLocation()
|
||||
}
|
||||
|
||||
const returnToArrivalBrowse = () => {
|
||||
outdoorArrivalStage.value = 'browse'
|
||||
selectedArrivalTarget.value = null
|
||||
clearOutdoorRoute()
|
||||
}
|
||||
|
||||
const collapseArrivalBrowse = () => {
|
||||
arrivalTargetRequestId += 1
|
||||
outdoorArrivalStage.value = 'browse'
|
||||
arrivalActiveType.value = null
|
||||
arrivalTargets.value = []
|
||||
selectedArrivalTarget.value = null
|
||||
arrivalTargetsError.value = ''
|
||||
arrivalTargetsLoading.value = false
|
||||
clearOutdoorRoute()
|
||||
}
|
||||
|
||||
const returnToArrivalDetail = () => {
|
||||
outdoorArrivalStage.value = selectedArrivalTarget.value ? 'detail' : 'browse'
|
||||
clearOutdoorRoute()
|
||||
}
|
||||
|
||||
const closeArrivalPanel = () => {
|
||||
arrivalTargetRequestId += 1
|
||||
showOutdoorNavigationPanel.value = false
|
||||
outdoorArrivalStage.value = 'browse'
|
||||
arrivalActiveType.value = null
|
||||
arrivalTargets.value = []
|
||||
selectedArrivalTarget.value = null
|
||||
arrivalTargetsError.value = ''
|
||||
arrivalTargetsLoading.value = false
|
||||
outdoorStartSearchResults.value = []
|
||||
clearOutdoorRoute()
|
||||
isBuildingGuideActive.value = true
|
||||
guideOutdoorState.value = 'home'
|
||||
}
|
||||
|
||||
const clearOutdoorRoute = () => {
|
||||
outdoorStartLocationRequestId += 1
|
||||
outdoorRoutePlanRequestId += 1
|
||||
outdoorStartSearchRequestId += 1
|
||||
outdoorManualReverseGeocodeRequestId += 1
|
||||
outdoorRoute.value = null
|
||||
outdoorRouteError.value = ''
|
||||
outdoorRouteStart.value = null
|
||||
outdoorRouteStartPhase.value = 'locating'
|
||||
outdoorLocationMessage.value = ''
|
||||
outdoorLocationHelpVisible.value = false
|
||||
outdoorStartSearchResults.value = []
|
||||
outdoorStartSearchLoading.value = false
|
||||
outdoorRouteLoading.value = false
|
||||
}
|
||||
|
||||
const planOutdoorRouteFromStart = async (start: { latitude: number; longitude: number; name?: string }) => {
|
||||
const destination = selectedArrivalTarget.value
|
||||
if (!destination) {
|
||||
outdoorRouteError.value = '请先选择目的地'
|
||||
return
|
||||
}
|
||||
|
||||
const requestId = ++outdoorRoutePlanRequestId
|
||||
outdoorRouteLoading.value = true
|
||||
outdoorRouteError.value = ''
|
||||
outdoorRoute.value = null
|
||||
try {
|
||||
const route = await planOutdoorRoute({
|
||||
from: start,
|
||||
to: destination,
|
||||
mode: outdoorTravelMode.value.toUpperCase() as 'DRIVING' | 'WALKING' | 'TRANSIT'
|
||||
})
|
||||
if (requestId !== outdoorRoutePlanRequestId) return
|
||||
outdoorRoute.value = route
|
||||
} catch (error) {
|
||||
if (requestId !== outdoorRoutePlanRequestId) return
|
||||
outdoorRouteError.value = error instanceof Error ? error.message : '路线规划失败,请稍后重试'
|
||||
} finally {
|
||||
if (requestId === outdoorRoutePlanRequestId) outdoorRouteLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const messageForOutdoorLocationFailure = (error: unknown) => {
|
||||
if (error instanceof BrowserLocationError) {
|
||||
if (error.reason === 'denied') {
|
||||
return '请在微信小程序的设置中开启位置权限后重试'
|
||||
}
|
||||
return error.message
|
||||
}
|
||||
return '暂时无法获取当前位置,请手动选择起点'
|
||||
}
|
||||
|
||||
const requestOutdoorCurrentLocation = async () => {
|
||||
const requestId = ++outdoorStartLocationRequestId
|
||||
outdoorRoutePlanRequestId += 1
|
||||
outdoorManualReverseGeocodeRequestId += 1
|
||||
outdoorRouteStartPhase.value = 'locating'
|
||||
outdoorLocationMessage.value = ''
|
||||
outdoorLocationHelpVisible.value = false
|
||||
outdoorRouteStart.value = null
|
||||
outdoorRouteError.value = ''
|
||||
outdoorRoute.value = null
|
||||
outdoorRouteLoading.value = false
|
||||
try {
|
||||
const location = await getBrowserGcjLocation()
|
||||
if (requestId !== outdoorStartLocationRequestId) return
|
||||
outdoorRouteStart.value = {
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
name: '我的位置'
|
||||
}
|
||||
outdoorRouteStartPhase.value = 'route'
|
||||
await planOutdoorRouteFromStart(outdoorRouteStart.value)
|
||||
} catch (error) {
|
||||
if (requestId !== outdoorStartLocationRequestId) return
|
||||
outdoorLocationMessage.value = messageForOutdoorLocationFailure(error)
|
||||
outdoorRouteStartPhase.value = 'permission-needed'
|
||||
} finally {
|
||||
if (requestId === outdoorStartLocationRequestId) outdoorRouteLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleOutdoorLocationRetry = async () => {
|
||||
const permissionState = await getBrowserLocationPermissionState()
|
||||
if (permissionState === 'denied') {
|
||||
outdoorLocationHelpVisible.value = true
|
||||
outdoorLocationMessage.value = '当前位置权限已关闭'
|
||||
return
|
||||
}
|
||||
void requestOutdoorCurrentLocation()
|
||||
}
|
||||
|
||||
const handleOutdoorManualSearchRequest = () => {
|
||||
outdoorStartLocationRequestId += 1
|
||||
outdoorRoutePlanRequestId += 1
|
||||
outdoorStartSearchRequestId += 1
|
||||
outdoorManualReverseGeocodeRequestId += 1
|
||||
outdoorRouteStartPhase.value = 'manual-search'
|
||||
outdoorLocationMessage.value = ''
|
||||
outdoorLocationHelpVisible.value = false
|
||||
outdoorRouteError.value = ''
|
||||
outdoorRoute.value = null
|
||||
outdoorRouteLoading.value = false
|
||||
outdoorStartSearchResults.value = []
|
||||
}
|
||||
|
||||
const handleOutdoorManualMapRequest = () => {
|
||||
outdoorStartLocationRequestId += 1
|
||||
outdoorRoutePlanRequestId += 1
|
||||
outdoorStartSearchRequestId += 1
|
||||
outdoorManualReverseGeocodeRequestId += 1
|
||||
outdoorRouteStartPhase.value = 'manual-map'
|
||||
outdoorLocationMessage.value = ''
|
||||
outdoorLocationHelpVisible.value = false
|
||||
outdoorRouteError.value = ''
|
||||
outdoorRoute.value = null
|
||||
outdoorRouteLoading.value = false
|
||||
outdoorStartSearchResults.value = []
|
||||
}
|
||||
|
||||
const handleOutdoorManualStartSearch = async (keyword: string) => {
|
||||
const requestId = ++outdoorStartSearchRequestId
|
||||
if (!keyword.trim()) {
|
||||
outdoorStartSearchResults.value = []
|
||||
outdoorStartSearchLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
outdoorStartSearchLoading.value = true
|
||||
try {
|
||||
const response = await suggestOutdoorPlaces({
|
||||
keyword,
|
||||
region: ARRIVAL_SEARCH_REGION,
|
||||
pageSize: 6
|
||||
})
|
||||
if (requestId !== outdoorStartSearchRequestId) return
|
||||
outdoorStartSearchResults.value = response.items
|
||||
} catch (error) {
|
||||
if (requestId !== outdoorStartSearchRequestId) return
|
||||
console.warn('手动起点地点检索失败:', error)
|
||||
outdoorStartSearchResults.value = []
|
||||
} finally {
|
||||
if (requestId === outdoorStartSearchRequestId) outdoorStartSearchLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleOutdoorManualStartConfirm = (location: {
|
||||
latitude: number
|
||||
longitude: number
|
||||
name?: string
|
||||
title?: string
|
||||
}) => {
|
||||
outdoorStartLocationRequestId += 1
|
||||
outdoorRoutePlanRequestId += 1
|
||||
outdoorStartSearchRequestId += 1
|
||||
outdoorManualReverseGeocodeRequestId += 1
|
||||
outdoorStartSearchResults.value = []
|
||||
const start = {
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
name: location.name || location.title || '手动选择起点'
|
||||
}
|
||||
outdoorRouteStart.value = start
|
||||
outdoorLocationMessage.value = ''
|
||||
outdoorLocationHelpVisible.value = false
|
||||
outdoorRouteError.value = ''
|
||||
outdoorRoute.value = null
|
||||
outdoorRouteLoading.value = false
|
||||
outdoorRouteStartPhase.value = 'confirm-start'
|
||||
}
|
||||
|
||||
const resolveOutdoorManualMapStartName = async (
|
||||
location: { latitude: number; longitude: number },
|
||||
requestId: number
|
||||
) => {
|
||||
try {
|
||||
const address = await reverseGeocodeOutdoorLocation(location)
|
||||
if (requestId !== outdoorManualReverseGeocodeRequestId) return
|
||||
|
||||
const start = outdoorRouteStart.value
|
||||
if (
|
||||
!start
|
||||
|| outdoorRouteStartPhase.value !== 'confirm-start'
|
||||
|| start.latitude !== location.latitude
|
||||
|| start.longitude !== location.longitude
|
||||
) return
|
||||
|
||||
outdoorRouteStart.value = {
|
||||
...start,
|
||||
name: address.address?.trim() || start.name
|
||||
}
|
||||
} catch (error) {
|
||||
// The coordinate remains usable when the optional address lookup fails.
|
||||
console.warn('地图选点地址解析失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleOutdoorMapTap = (location: { latitude: number; longitude: number }) => {
|
||||
if (
|
||||
outdoorArrivalStage.value !== 'route'
|
||||
|| outdoorRouteStartPhase.value !== 'manual-map'
|
||||
|| isBuildingGuideActive.value
|
||||
) return
|
||||
handleOutdoorManualStartConfirm({ ...location, name: '地图选点' })
|
||||
const requestId = outdoorManualReverseGeocodeRequestId
|
||||
void resolveOutdoorManualMapStartName(location, requestId)
|
||||
}
|
||||
|
||||
const handleOutdoorRouteStartConfirm = () => {
|
||||
if (!outdoorRouteStart.value) {
|
||||
void requestOutdoorCurrentLocation()
|
||||
return
|
||||
}
|
||||
outdoorRouteStartPhase.value = 'route'
|
||||
void planOutdoorRouteFromStart(outdoorRouteStart.value)
|
||||
}
|
||||
|
||||
const handleOutdoorRouteRetry = () => {
|
||||
if (!outdoorRouteStart.value) return
|
||||
void planOutdoorRouteFromStart(outdoorRouteStart.value)
|
||||
}
|
||||
|
||||
const handleOutdoorMarkerClick = (markerId: string) => {
|
||||
if (markerId === 'outdoor-route-start') {
|
||||
if (outdoorArrivalStage.value === 'route') outdoorRouteStartPhase.value = 'confirm-start'
|
||||
return
|
||||
}
|
||||
if (outdoorArrivalStage.value === 'route') return
|
||||
|
||||
const target = arrivalTargets.value.find((item) => item.id === markerId)
|
||||
if (target) handleArrivalTargetSelect(target)
|
||||
}
|
||||
|
||||
const closeHomeSearchDock = () => {
|
||||
@@ -1868,26 +2410,24 @@ onShow(async () => {
|
||||
await homeSearchPanelRef.value?.restoreResultListScroll?.()
|
||||
})
|
||||
|
||||
const handleMoreOutdoorNav = () => {
|
||||
if (!homeArrivalFeatureEnabled) return
|
||||
if (showArrivalPanel.value && showArrivalPanelCollapsed.value) {
|
||||
arrivalPanelRef.value?.expandPanel()
|
||||
showArrivalPanelCollapsed.value = false
|
||||
return
|
||||
const handleMoreOutdoorNav = () => {
|
||||
if (!homeArrivalFeatureEnabled.value) return
|
||||
showOutdoorNavigationPanel.value = true
|
||||
outdoorArrivalStage.value = 'browse'
|
||||
clearOutdoorRoute()
|
||||
arrivalActiveType.value = null
|
||||
arrivalTargets.value = []
|
||||
arrivalTargetsError.value = ''
|
||||
selectedArrivalTarget.value = null
|
||||
showRoutePlanner.value = false
|
||||
isBuildingGuideActive.value = false
|
||||
guideOutdoorState.value = 'home'
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
isSimulatingRoute.value = false
|
||||
closeHomeSearchDock()
|
||||
}
|
||||
|
||||
resetArrivalPanelState()
|
||||
showArrivalPanel.value = true
|
||||
showArrivalPanelCollapsed.value = false
|
||||
showRoutePlanner.value = false
|
||||
isBuildingGuideActive.value = false
|
||||
guideOutdoorState.value = 'home'
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
isSimulatingRoute.value = false
|
||||
closeHomeSearchDock()
|
||||
}
|
||||
|
||||
const handleExplainQuickTap = () => {
|
||||
handleTabChange('explain')
|
||||
}
|
||||
@@ -2238,13 +2778,15 @@ const handleExplainBack = () => {
|
||||
|
||||
.guide-quick-action {
|
||||
width: 46px;
|
||||
min-height: 58px;
|
||||
padding: 7px 4px 6px;
|
||||
height: 46px;
|
||||
min-height: 46px;
|
||||
flex: 0 0 46px;
|
||||
padding: 2px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
gap: 1px;
|
||||
box-sizing: border-box;
|
||||
background: rgba(245, 245, 237, 0.94);
|
||||
border: 1px solid rgba(255, 255, 255, 0.72);
|
||||
@@ -2262,7 +2804,6 @@ const handleExplainBack = () => {
|
||||
background: var(--museum-accent);
|
||||
border-color: var(--museum-accent);
|
||||
box-shadow: 0 14px 30px rgba(0, 0, 0, 0.24), 0 0 0 1px rgba(224, 225, 0, 0.35);
|
||||
animation: guide-quick-active-in 0.22s ease-out both;
|
||||
}
|
||||
|
||||
.guide-quick-action:active {
|
||||
@@ -2352,7 +2893,7 @@ const handleExplainBack = () => {
|
||||
.guide-quick-action-text {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
line-height: 14px;
|
||||
font-weight: 700;
|
||||
color: #151713;
|
||||
text-align: center;
|
||||
@@ -2363,14 +2904,9 @@ const handleExplainBack = () => {
|
||||
color: #0a0b08;
|
||||
}
|
||||
|
||||
@keyframes guide-quick-active-in {
|
||||
from {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scale(1);
|
||||
}
|
||||
.guide-quick-actions.is-outdoor {
|
||||
right: 18px;
|
||||
width: 46px;
|
||||
}
|
||||
|
||||
.route-sim-step-bar {
|
||||
|
||||
Reference in New Issue
Block a user