Files
frontend-miniapp/src/composables/useGuideModelState.ts
lyf 122013ccfb
Some checks failed
CI / verify (push) Has been cancelled
修复导览楼层默认进入与模型复位状态
2026-07-17 11:54:08 +08:00

169 lines
5.2 KiB
TypeScript

import { readonly, ref } from 'vue'
export type GuideModelInitialState = Readonly<{
mode: '3d'
indoorView: 'overview' | 'floor' | 'multi'
layerMode: 'single' | 'multi'
defaultActiveFloorId: string
loadedFloorId: string
selectedPoiId: null
activeFocusPoiId: null
targetFocusRequest: null
pendingTargetFocus: null
visiblePoiIds: null
temporaryFocus: false
poiInfoLabelVisible: false
routePreviewActive: false
navigationSimulationActive: false
autoFloorSwitchActive: boolean
}>
export type PoiDetailReturnContext = Readonly<{
floorId: string
resetMode: 'floor-baseline'
requestId: number
}>
export type GuideModelResetReason = 'poi-detail-close' | 'floor-reset'
export type GuideModelResetRequest = Readonly<{
floorId: string
requestId: number
rendererReason: GuideModelResetReason
}>
type GuideModelReadyState = Readonly<{
view: 'overview' | 'floor' | 'multi'
floorId: string
}>
// The object is frozen so callers cannot accidentally turn a captured baseline into live state.
export const GUIDE_MODEL_INITIAL_STATE: GuideModelInitialState = Object.freeze({
mode: '3d',
indoorView: 'overview',
layerMode: 'single',
defaultActiveFloorId: '',
loadedFloorId: '',
selectedPoiId: null,
activeFocusPoiId: null,
targetFocusRequest: null,
pendingTargetFocus: null,
visiblePoiIds: null,
temporaryFocus: false,
poiInfoLabelVisible: false,
routePreviewActive: false,
navigationSimulationActive: false,
autoFloorSwitchActive: true
})
const initialState = ref<GuideModelInitialState>(GUIDE_MODEL_INITIAL_STATE)
const pendingModelResetAfterPoiDetail = ref<PoiDetailReturnContext | null>(null)
const confirmedPoiDetailRequestId = ref<number | null>(null)
const requestGeneration = ref(0)
const pendingViewBaselineReset = ref<GuideModelResetRequest | null>(null)
let poiDetailRequestId = 0
export const useGuideModelState = () => {
const initializeModel = ({ view, floorId }: GuideModelReadyState) => {
if (initialState.value.defaultActiveFloorId) return initialState.value
if (view !== 'overview' || !floorId.trim()) return initialState.value
initialState.value = Object.freeze({
...GUIDE_MODEL_INITIAL_STATE,
defaultActiveFloorId: floorId,
loadedFloorId: floorId
})
return initialState.value
}
const beginPoiDetailReturn = (floorId: string) => {
const context = Object.freeze({
floorId,
resetMode: 'floor-baseline' as const,
requestId: ++poiDetailRequestId
})
pendingModelResetAfterPoiDetail.value = context
confirmedPoiDetailRequestId.value = null
return context
}
const confirmPoiDetailReturn = (requestId: number) => {
if (pendingModelResetAfterPoiDetail.value?.requestId !== requestId) return false
confirmedPoiDetailRequestId.value = requestId
return true
}
const cancelPoiDetailReturn = (requestId?: number) => {
if (
requestId !== undefined
&& pendingModelResetAfterPoiDetail.value?.requestId !== requestId
) return false
pendingModelResetAfterPoiDetail.value = null
confirmedPoiDetailRequestId.value = null
return true
}
const consumePoiDetailReturn = () => {
const context = pendingModelResetAfterPoiDetail.value
if (!context || confirmedPoiDetailRequestId.value !== context.requestId) return null
pendingModelResetAfterPoiDetail.value = null
confirmedPoiDetailRequestId.value = null
return context
}
// All async focus/floor operations compare this generation before committing.
const invalidatePendingRequests = () => {
requestGeneration.value += 1
return requestGeneration.value
}
const beginViewBaselineReset = ({ floorId, rendererReason }: {
floorId: string
rendererReason: GuideModelResetReason
}) => Object.freeze({
floorId,
rendererReason,
requestId: invalidatePendingRequests()
})
const isCurrentRequest = (requestId: number) => requestGeneration.value === requestId
// The renderer may not yet exist while the page has already received a return event.
// Keep exactly one latest request here so page lifecycle hooks cannot race each other.
const queueViewBaselineReset = (request: GuideModelResetRequest) => {
if (!isCurrentRequest(request.requestId)) return false
pendingViewBaselineReset.value = request
return true
}
const completeViewBaselineReset = (request: GuideModelResetRequest) => {
if (!isCurrentRequest(request.requestId)) return false
pendingViewBaselineReset.value = null
return true
}
const consumePendingViewBaselineReset = () => {
const request = pendingViewBaselineReset.value
if (!request || !isCurrentRequest(request.requestId)) return null
pendingViewBaselineReset.value = null
return request
}
return {
initialState: readonly(initialState),
pendingModelResetAfterPoiDetail: readonly(pendingModelResetAfterPoiDetail),
requestGeneration: readonly(requestGeneration),
pendingViewBaselineReset: readonly(pendingViewBaselineReset),
initializeModel,
beginPoiDetailReturn,
confirmPoiDetailReturn,
cancelPoiDetailReturn,
consumePoiDetailReturn,
invalidatePendingRequests,
beginViewBaselineReset,
isCurrentRequest,
queueViewBaselineReset,
completeViewBaselineReset,
consumePendingViewBaselineReset
}
}