fix: 统一馆内三维模型返回初始状态
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
:active-floor="activeFloor"
|
||||
map-type="indoor"
|
||||
:tools="[]"
|
||||
:indoor-model-source="indoorModelSource"
|
||||
:indoor-model-source="modelSource"
|
||||
:floors="guideFloors"
|
||||
:normalize-floor-id="normalizeGuideFloorId"
|
||||
:target-focus-request="targetFocusRequest"
|
||||
@@ -105,18 +105,17 @@ import type {
|
||||
MuseumPoi
|
||||
} from '@/domain/museum'
|
||||
import type {
|
||||
GuideModelSource,
|
||||
GuideRenderPoi
|
||||
} from '@/domain/guideModel'
|
||||
|
||||
const indoorModelSource: GuideModelSource = guideUseCase.getModelSource()
|
||||
const modelSource = guideUseCase.getModelSource()
|
||||
const guideFloors = ref<MuseumFloor[]>([])
|
||||
const normalizeGuideFloorId = (labelOrId: string) =>
|
||||
guideFloors.value.find((floor) => floor.id === labelOrId || floor.label === labelOrId)?.id
|
||||
|| guideUseCase.normalizeFloorId(labelOrId)
|
||||
const requestedDetailFloorId = ref('')
|
||||
const activeMode = ref<'2d' | '3d'>('3d')
|
||||
const activeFloor = ref('1F')
|
||||
const requestedTargetFloorId = ref('')
|
||||
const activeFloor = ref('')
|
||||
const targetFocusRequest = ref<TargetPoiFocusRequestViewModel | null>(null)
|
||||
const targetFocusRequestId = ref(0)
|
||||
const locationErrorMessage = ref('')
|
||||
@@ -146,68 +145,13 @@ const searchContext = ref<FacilityDetailSearchContext>(
|
||||
const loadGuideFloors = async () => {
|
||||
try {
|
||||
guideFloors.value = await guideUseCase.getFloors()
|
||||
const floors = guideFloors.value
|
||||
const normalizedActiveFloorId = activeFloor.value
|
||||
? normalizeGuideFloorId(activeFloor.value)
|
||||
: ''
|
||||
const defaultFloor = floors.find((floor) => floor.id === normalizedActiveFloorId)?.id
|
||||
|| floors.find((floor) => floor.label === '1F')?.id
|
||||
|| floors[0]?.id
|
||||
|| activeFloor.value
|
||||
activeFloor.value = defaultFloor
|
||||
if (!activeFloor.value) activeFloor.value = guideFloors.value[0]?.id || ''
|
||||
} catch (error) {
|
||||
console.error('加载导览楼层失败:', error)
|
||||
guideFloors.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const focusFacilityPoi = (poi: MuseumPoi) => {
|
||||
if (!poi.floorId) {
|
||||
locationErrorMessage.value = '该点位缺少楼层信息,暂时无法在地图中定位。'
|
||||
return
|
||||
}
|
||||
|
||||
locationErrorMessage.value = ''
|
||||
activeMode.value = '3d'
|
||||
activeFloor.value = poi.floorId
|
||||
targetFocusRequestId.value += 1
|
||||
targetFocusRequest.value = toTargetFocusRequestViewModel({
|
||||
poiId: poi.id,
|
||||
name: poi.name,
|
||||
floorId: poi.floorId,
|
||||
floorLabel: poi.floorLabel,
|
||||
primaryCategoryZh: poi.primaryCategory.label,
|
||||
positionGltf: poi.positionGltf,
|
||||
sourceObjectName: poi.sourceObjectName
|
||||
}, targetFocusRequestId.value)
|
||||
}
|
||||
|
||||
const focusRenderPoi = (poi: GuideRenderPoi) => {
|
||||
if (!poi.floorId) {
|
||||
locationErrorMessage.value = '该点位缺少楼层信息,暂时无法在地图中定位。'
|
||||
return
|
||||
}
|
||||
|
||||
locationErrorMessage.value = ''
|
||||
const floorLabel = guideFloors.value.find((floor) => floor.id === poi.floorId)?.label || poi.floorId
|
||||
activeMode.value = '3d'
|
||||
activeFloor.value = poi.floorId
|
||||
targetFocusRequestId.value += 1
|
||||
targetFocusRequest.value = toTargetFocusRequestViewModel({
|
||||
poiId: poi.id,
|
||||
name: poi.name,
|
||||
floorId: poi.floorId,
|
||||
floorLabel,
|
||||
primaryCategoryZh: poi.primaryCategoryZh,
|
||||
positionGltf: poi.positionGltf,
|
||||
sourceObjectName: poi.sourceObjectName,
|
||||
kind: poi.kind,
|
||||
hallId: poi.hallId,
|
||||
hallName: poi.hallName,
|
||||
entrances: poi.entrances
|
||||
}, targetFocusRequestId.value)
|
||||
}
|
||||
|
||||
const getRenderPoiFloorLabel = (poi: GuideRenderPoi) =>
|
||||
guideFloors.value.find((floor) => floor.id === poi.floorId)?.label || poi.floorId
|
||||
|
||||
@@ -224,6 +168,50 @@ const applyRenderPoiToFacility = (poi: GuideRenderPoi) => {
|
||||
focusRenderPoi(poi)
|
||||
}
|
||||
|
||||
const focusFacilityPoi = (poi: MuseumPoi) => {
|
||||
if (!poi.floorId) {
|
||||
locationErrorMessage.value = '该点位缺少楼层信息,暂时无法提供位置预览。'
|
||||
return
|
||||
}
|
||||
|
||||
locationErrorMessage.value = ''
|
||||
activeFloor.value = poi.floorId
|
||||
targetFocusRequestId.value += 1
|
||||
targetFocusRequest.value = toTargetFocusRequestViewModel({
|
||||
poiId: poi.id,
|
||||
name: poi.name,
|
||||
floorId: poi.floorId,
|
||||
floorLabel: poi.floorLabel,
|
||||
primaryCategoryZh: poi.primaryCategory.label,
|
||||
positionGltf: poi.positionGltf,
|
||||
sourceObjectName: poi.sourceObjectName
|
||||
}, targetFocusRequestId.value)
|
||||
}
|
||||
|
||||
const focusRenderPoi = (poi: GuideRenderPoi) => {
|
||||
if (!poi.floorId) {
|
||||
locationErrorMessage.value = '该点位缺少楼层信息,暂时无法提供位置预览。'
|
||||
return
|
||||
}
|
||||
|
||||
locationErrorMessage.value = ''
|
||||
activeFloor.value = poi.floorId
|
||||
targetFocusRequestId.value += 1
|
||||
targetFocusRequest.value = toTargetFocusRequestViewModel({
|
||||
poiId: poi.id,
|
||||
name: poi.name,
|
||||
floorId: poi.floorId,
|
||||
floorLabel: getRenderPoiFloorLabel(poi),
|
||||
primaryCategoryZh: poi.primaryCategoryZh,
|
||||
positionGltf: poi.positionGltf,
|
||||
sourceObjectName: poi.sourceObjectName,
|
||||
kind: poi.kind,
|
||||
hallId: poi.hallId,
|
||||
hallName: poi.hallName,
|
||||
entrances: poi.entrances
|
||||
}, targetFocusRequestId.value)
|
||||
}
|
||||
|
||||
const normalizePoiName = (value: string) => value
|
||||
.trim()
|
||||
.replace(/[\s()()【】[\]_-]/g, '')
|
||||
@@ -242,7 +230,7 @@ const decodeRouteText = (value: unknown) => (
|
||||
)
|
||||
|
||||
const isPoiOnRequestedFloor = (poi: Pick<MuseumPoi | GuideRenderPoi, 'floorId'>) => (
|
||||
!requestedTargetFloorId.value || poi.floorId === requestedTargetFloorId.value
|
||||
!requestedDetailFloorId.value || poi.floorId === requestedDetailFloorId.value
|
||||
)
|
||||
|
||||
const resolvePoiFromHall = async () => {
|
||||
@@ -269,7 +257,7 @@ const resolveFacilityPoi = async () => {
|
||||
if (!normalizedTarget) return null
|
||||
|
||||
const candidates = await guideUseCase.searchPois(facility.value.name)
|
||||
const floorMatchedCandidates = requestedTargetFloorId.value
|
||||
const floorMatchedCandidates = requestedDetailFloorId.value
|
||||
? candidates.filter(isPoiOnRequestedFloor)
|
||||
: candidates
|
||||
const candidatesToMatch = floorMatchedCandidates.length ? floorMatchedCandidates : candidates
|
||||
@@ -289,16 +277,16 @@ const resolveRenderPoi = async () => {
|
||||
const normalizedTarget = normalizePoiName(facility.value.name)
|
||||
if (!resolveByExactId && !normalizedTarget) return null
|
||||
|
||||
const modelPackage = await indoorModelSource.loadPackage()
|
||||
const floorsToSearch = requestedTargetFloorId.value
|
||||
? modelPackage.floors.filter((floor) => floor.floorId === requestedTargetFloorId.value)
|
||||
const modelPackage = await modelSource.loadPackage()
|
||||
const floorsToSearch = requestedDetailFloorId.value
|
||||
? modelPackage.floors.filter((floor) => floor.floorId === requestedDetailFloorId.value)
|
||||
: modelPackage.floors
|
||||
|
||||
for (const floor of floorsToSearch) {
|
||||
const floorId = floor.floorId
|
||||
let pois: GuideRenderPoi[] = []
|
||||
try {
|
||||
pois = await indoorModelSource.loadFloorPois(floorId)
|
||||
pois = await modelSource.loadFloorPois(floorId)
|
||||
} catch (error) {
|
||||
resolutionLoadFailed.value = true
|
||||
console.warn(`楼层 ${floorId} 的模型点位加载失败:`, error)
|
||||
@@ -362,10 +350,8 @@ onLoad(async (options: Record<string, unknown> = {}) => {
|
||||
const requestedFloorId = decodeRouteText(options.floorId)
|
||||
|| searchContext.value.searchFloorId
|
||||
if (requestedFloorId) {
|
||||
requestedTargetFloorId.value = normalizeGuideFloorId(requestedFloorId)
|
||||
if (requestedTargetFloorId.value) {
|
||||
activeFloor.value = requestedTargetFloorId.value
|
||||
}
|
||||
requestedDetailFloorId.value = normalizeGuideFloorId(requestedFloorId)
|
||||
activeFloor.value = requestedDetailFloorId.value || activeFloor.value
|
||||
}
|
||||
|
||||
const requestedFloorLabel = decodeRouteText(options.floorLabel)
|
||||
@@ -403,7 +389,7 @@ onLoad(async (options: Record<string, unknown> = {}) => {
|
||||
console.error('加载设施位置详情失败:', error)
|
||||
locationErrorMessage.value = '位置数据加载失败,请稍后重试。'
|
||||
} finally {
|
||||
// 目标楼层与聚焦请求准备完成后再挂载 ThreeMap,避免默认楼层的中间帧。
|
||||
// Wait for the target context so the preview does not flash an unrelated floor.
|
||||
mapContextReady.value = true
|
||||
}
|
||||
})
|
||||
@@ -419,8 +405,8 @@ const handleTargetFocus = (result: TargetPoiFocusResult) => {
|
||||
}
|
||||
|
||||
locationErrorMessage.value = result.status === 'missing'
|
||||
? '该点位缺少可用的位置数据,暂时无法在地图中定位。'
|
||||
: '地图定位暂不可用,请稍后重试。'
|
||||
? '该点位缺少可用的位置数据,暂时无法提供位置预览。'
|
||||
: '位置预览暂不可用,请稍后重试。'
|
||||
}
|
||||
|
||||
const handleCloseDetailPanel = () => {
|
||||
@@ -432,21 +418,7 @@ const handleTopTabChange = (tab: GuideTopTab) => {
|
||||
}
|
||||
|
||||
const handlePageBack = () => {
|
||||
if (searchContext.value.source === 'search' && searchContext.value.resultCount === 1) {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
fail: () => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
})
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -325,9 +325,9 @@ import type {
|
||||
PoiSearchContext
|
||||
} from '@/domain/poiSearch'
|
||||
import {
|
||||
toTargetFocusRequestViewModel,
|
||||
type TargetPoiFocusRequestViewModel
|
||||
} from '@/view-models/guideViewModels'
|
||||
import { useGuideModelState } from '@/composables/useGuideModelState'
|
||||
import {
|
||||
ARRIVAL_TARGETS_BY_TYPE,
|
||||
type ArrivalSearchTarget,
|
||||
@@ -344,6 +344,8 @@ import type {
|
||||
type GuideIndoorView = 'overview' | 'floor' | 'multi'
|
||||
type LayerDisplayMode = 'single' | 'multi'
|
||||
|
||||
const guideModelState = useGuideModelState()
|
||||
|
||||
const isIndexHashRoute = () => {
|
||||
if (typeof window === 'undefined') return false
|
||||
|
||||
@@ -416,6 +418,7 @@ const returningToOverview = ref(false)
|
||||
const guideMapShellRef = ref<{
|
||||
clearRoute?: () => void
|
||||
showOverview?: () => Promise<void> | void
|
||||
resetToInitialState?: () => Promise<boolean> | boolean
|
||||
} | null>(null)
|
||||
const homeSearchPanelRef = ref<{
|
||||
collapseHomePanel?: () => void
|
||||
@@ -427,9 +430,7 @@ const homeSearchExpanded = ref(false)
|
||||
const homeCategoryModeActive = ref(false)
|
||||
const searchVisiblePoiIds = ref<string[] | null>(null)
|
||||
const searchTargetFocusRequest = ref<TargetPoiFocusRequestViewModel | null>(null)
|
||||
let searchTargetFocusRequestId = 0
|
||||
let pendingCollapseSearchAfterDetail = false
|
||||
let pendingSearchDetailFocusRequest: TargetPoiFocusRequestViewModel | null = null
|
||||
let pendingGuideModelResetUntilReady = false
|
||||
|
||||
type PoiSearchOverlayHistoryState = {
|
||||
museumGuideOverlay?: 'poi-search'
|
||||
@@ -916,6 +917,7 @@ const loadGuideFloors = async () => {
|
||||
|| floors[0]?.id
|
||||
|| activeGuideFloor.value
|
||||
renderedFloorId.value = activeGuideFloor.value
|
||||
guideModelState.initializeModel(activeGuideFloor.value)
|
||||
} catch (error) {
|
||||
console.error('加载导览楼层失败:', error)
|
||||
guideFloors.value = []
|
||||
@@ -1031,7 +1033,12 @@ const handleAutoSwitch = (event: { from: 'overview' | 'floor'; to: 'overview' |
|
||||
}
|
||||
|
||||
const handleInitialModelReady = (event: { view: GuideIndoorView; floorId?: string; elapsedMs?: number }) => {
|
||||
guideModelState.initializeModel(event.floorId || activeGuideFloor.value)
|
||||
settleLaunchOverlayByModel('ready', event)
|
||||
if (pendingGuideModelResetUntilReady) {
|
||||
pendingGuideModelResetUntilReady = false
|
||||
void resetGuideModelToInitial({ reason: 'queued-poi-detail-close' })
|
||||
}
|
||||
}
|
||||
|
||||
const handleInitialModelFailed = (event: { view: GuideIndoorView; floorId?: string; message: string; elapsedMs?: number }) => {
|
||||
@@ -1449,27 +1456,45 @@ const closeHomeSearchDock = () => {
|
||||
homeSearchExpanded.value = false
|
||||
}
|
||||
|
||||
const resetGuideModelToInitial = async ({ reason }: { reason: 'poi-detail-close' | 'queued-poi-detail-close' }) => {
|
||||
const initial = guideModelState.initialState.value
|
||||
guideModelState.invalidatePendingRequests()
|
||||
searchTargetFocusRequest.value = null
|
||||
searchVisiblePoiIds.value = null
|
||||
homeCategoryModeActive.value = false
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
showRoutePlanner.value = false
|
||||
activeRoutePreview.value = null
|
||||
routeStartPoint.value = null
|
||||
routeEndPoint.value = null
|
||||
routePlanError.value = ''
|
||||
isSimulatingRoute.value = false
|
||||
requestedFloorId.value = ''
|
||||
requestedFloorLabel.value = ''
|
||||
loadingFloorId.value = ''
|
||||
failedFloorId.value = ''
|
||||
is3DMode.value = true
|
||||
guideOutdoorState.value = 'home'
|
||||
indoorView.value = initial.indoorView
|
||||
activeGuideFloor.value = initial.defaultActiveFloorId || activeGuideFloor.value
|
||||
renderedFloorId.value = initial.loadedFloorId || activeGuideFloor.value
|
||||
closeArrivalPanel()
|
||||
await homeSearchPanelRef.value?.resetSearchState?.()
|
||||
closeHomeSearchDock()
|
||||
|
||||
const resetApplied = await guideMapShellRef.value?.resetToInitialState?.()
|
||||
if (!resetApplied) {
|
||||
// The renderer is still becoming ready; retain only the latest requested reset.
|
||||
pendingGuideModelResetUntilReady = true
|
||||
}
|
||||
console.info('馆内三维模型已恢复统一初始状态:', { reason })
|
||||
}
|
||||
|
||||
onShow(async () => {
|
||||
await nextTick()
|
||||
if (pendingCollapseSearchAfterDetail) {
|
||||
pendingCollapseSearchAfterDetail = false
|
||||
const pendingFocusRequest = pendingSearchDetailFocusRequest
|
||||
pendingSearchDetailFocusRequest = null
|
||||
await homeSearchPanelRef.value?.returnToCollapsedAfterDetail?.()
|
||||
homeSearchExpanded.value = false
|
||||
homeCategoryModeActive.value = false
|
||||
searchVisiblePoiIds.value = null
|
||||
if (pendingFocusRequest) {
|
||||
closeArrivalPanel()
|
||||
showRoutePlanner.value = false
|
||||
isSimulatingRoute.value = false
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
is3DMode.value = true
|
||||
indoorView.value = 'floor'
|
||||
activeGuideFloor.value = pendingFocusRequest.floorId
|
||||
searchTargetFocusRequest.value = pendingFocusRequest
|
||||
}
|
||||
if (guideModelState.consumePoiDetailReturn()) {
|
||||
await resetGuideModelToInitial({ reason: 'poi-detail-close' })
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1569,29 +1594,13 @@ const createSearchDetailUrl = (poi: MuseumPoi, context: PoiSearchContext) => {
|
||||
}
|
||||
|
||||
const handleHomeSearchResultTap = async (poi: MuseumPoi, context: PoiSearchContext) => {
|
||||
searchTargetFocusRequestId += 1
|
||||
pendingSearchDetailFocusRequest = toTargetFocusRequestViewModel({
|
||||
poiId: poi.id,
|
||||
name: poi.name,
|
||||
floorId: poi.floorId,
|
||||
floorLabel: poi.floorLabel,
|
||||
primaryCategoryZh: poi.primaryCategory.label,
|
||||
positionGltf: poi.positionGltf,
|
||||
sourceObjectName: poi.sourceObjectName,
|
||||
kind: poi.kind,
|
||||
hallId: poi.hallId,
|
||||
hallName: poi.hallName,
|
||||
entrances: poi.entrances
|
||||
}, searchTargetFocusRequestId)
|
||||
|
||||
// 详情页会一次性挂载目标楼层;首页不再先启动并销毁 ThreeMap。
|
||||
// The homepage keeps its renderer mounted; detail close is consumed by onShow once.
|
||||
searchTargetFocusRequest.value = null
|
||||
pendingCollapseSearchAfterDetail = true
|
||||
guideModelState.beginPoiDetailReturn()
|
||||
uni.navigateTo({
|
||||
url: createSearchDetailUrl(poi, context),
|
||||
fail: () => {
|
||||
pendingCollapseSearchAfterDetail = false
|
||||
pendingSearchDetailFocusRequest = null
|
||||
guideModelState.cancelPoiDetailReturn()
|
||||
uni.showToast({
|
||||
title: '点位详情打开失败,请重试',
|
||||
icon: 'none'
|
||||
|
||||
Reference in New Issue
Block a user