修复室内导览楼层切换事务一致性
This commit is contained in:
@@ -84,7 +84,6 @@ interface CameraFitOptions {
|
|||||||
interface LoadFloorOptions {
|
interface LoadFloorOptions {
|
||||||
preserveCurrentSceneUntilReady?: boolean
|
preserveCurrentSceneUntilReady?: boolean
|
||||||
suppressProgress?: boolean
|
suppressProgress?: boolean
|
||||||
allowOverviewFallback?: boolean
|
|
||||||
detachPoiBeforeLoad?: boolean
|
detachPoiBeforeLoad?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +178,15 @@ interface PoiMarkerCacheEntry {
|
|||||||
markerSize: number
|
markerSize: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PreparedFloorScene {
|
||||||
|
floor: FloorIndexItem
|
||||||
|
model: THREE.Object3D
|
||||||
|
poiEntry: PoiMarkerCacheEntry | null
|
||||||
|
ownsModel: boolean
|
||||||
|
cacheAsSharedModel: boolean
|
||||||
|
protectedResources?: ReusableModelResources
|
||||||
|
}
|
||||||
|
|
||||||
type ContainerRef = HTMLElement | { $el?: HTMLElement } | null
|
type ContainerRef = HTMLElement | { $el?: HTMLElement } | null
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
@@ -270,6 +278,7 @@ let isDisposed = false
|
|||||||
let pendingTargetFocus: TargetPoiFocusRequest | null = null
|
let pendingTargetFocus: TargetPoiFocusRequest | null = null
|
||||||
let targetFocusQueue: Promise<unknown> = Promise.resolve()
|
let targetFocusQueue: Promise<unknown> = Promise.resolve()
|
||||||
let modelLoadVersion = 0
|
let modelLoadVersion = 0
|
||||||
|
let pendingRequestedFloorId = ''
|
||||||
let hasLoadedFloorViewOnce = false
|
let hasLoadedFloorViewOnce = false
|
||||||
let pointerDownState: { x: number; y: number } | null = null
|
let pointerDownState: { x: number; y: number } | null = null
|
||||||
let cachedOverviewModel: THREE.Object3D | null = null
|
let cachedOverviewModel: THREE.Object3D | null = null
|
||||||
@@ -1098,8 +1107,6 @@ const setProgress = (progress: number, message: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const staleModelLoadMessage = 'STALE_MODEL_LOAD'
|
const staleModelLoadMessage = 'STALE_MODEL_LOAD'
|
||||||
let isFloorSwitchLocked = false
|
|
||||||
let queuedFloorSwitchId = ''
|
|
||||||
|
|
||||||
const startModelLoad = () => {
|
const startModelLoad = () => {
|
||||||
modelLoadVersion += 1
|
modelLoadVersion += 1
|
||||||
@@ -1108,6 +1115,7 @@ const startModelLoad = () => {
|
|||||||
|
|
||||||
const invalidateModelLoads = () => {
|
const invalidateModelLoads = () => {
|
||||||
modelLoadVersion += 1
|
modelLoadVersion += 1
|
||||||
|
pendingRequestedFloorId = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCurrentModelLoad = (loadToken: number) => (
|
const isCurrentModelLoad = (loadToken: number) => (
|
||||||
@@ -1146,6 +1154,60 @@ const assertActiveFloorModelState = (loadToken: number, requestedFloorId: string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const assertPreparedFloorScene = (
|
||||||
|
prepared: PreparedFloorScene,
|
||||||
|
loadToken: number,
|
||||||
|
expectedFloorId: string
|
||||||
|
) => {
|
||||||
|
assertCurrentModelLoad(loadToken, prepared.ownsModel ? prepared.model : undefined)
|
||||||
|
|
||||||
|
if (pendingRequestedFloorId !== expectedFloorId) {
|
||||||
|
if (prepared.ownsModel) {
|
||||||
|
disposeObject(prepared.model, prepared.protectedResources)
|
||||||
|
}
|
||||||
|
throw createStaleModelLoadError()
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelFloorId = typeof prepared.model.userData.floorId === 'string'
|
||||||
|
? prepared.model.userData.floorId
|
||||||
|
: ''
|
||||||
|
if (modelFloorId !== expectedFloorId) {
|
||||||
|
if (prepared.ownsModel) {
|
||||||
|
disposeObject(prepared.model, prepared.protectedResources)
|
||||||
|
}
|
||||||
|
throw new Error(`楼层模型校验失败:请求 ${expectedFloorId},模型 ${modelFloorId || '未知'}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const poiFloorId = typeof prepared.poiEntry?.group.userData.floorId === 'string'
|
||||||
|
? prepared.poiEntry.group.userData.floorId
|
||||||
|
: ''
|
||||||
|
if (prepared.poiEntry && (prepared.poiEntry.floorId !== expectedFloorId || poiFloorId !== expectedFloorId)) {
|
||||||
|
if (prepared.ownsModel) {
|
||||||
|
disposeObject(prepared.model, prepared.protectedResources)
|
||||||
|
}
|
||||||
|
throw new Error(`楼层点位校验失败:请求 ${expectedFloorId},点位 ${poiFloorId || prepared.poiEntry.floorId || '未知'}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const assertCommittedFloorScene = (loadToken: number, expectedFloorId: string) => {
|
||||||
|
assertActiveFloorModelState(loadToken, expectedFloorId)
|
||||||
|
|
||||||
|
const poiFloorId = typeof poiGroup?.userData.floorId === 'string'
|
||||||
|
? poiGroup.userData.floorId
|
||||||
|
: ''
|
||||||
|
const poiCurrentFloor = typeof poiGroup?.userData.currentFloor === 'string'
|
||||||
|
? poiGroup.userData.currentFloor
|
||||||
|
: ''
|
||||||
|
|
||||||
|
if (
|
||||||
|
shouldRenderPoiMarkers.value
|
||||||
|
&& poiGroup
|
||||||
|
&& (poiFloorId !== expectedFloorId || poiCurrentFloor !== expectedFloorId)
|
||||||
|
) {
|
||||||
|
throw new Error(`楼层点位状态不一致:请求 ${expectedFloorId},点位 ${poiFloorId || '未知'},当前 ${poiCurrentFloor || '未知'}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const waitForContainer = async () => {
|
const waitForContainer = async () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
@@ -1483,6 +1545,11 @@ const detachPoiMarkerGroups = () => {
|
|||||||
poiMarkerGroupCache.forEach((entry) => {
|
poiMarkerGroupCache.forEach((entry) => {
|
||||||
entry.group.parent?.remove(entry.group)
|
entry.group.parent?.remove(entry.group)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (poiGroup) {
|
||||||
|
poiGroup.userData.floorId = ''
|
||||||
|
poiGroup.userData.currentFloor = ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const detachActivePoiLayer = () => {
|
const detachActivePoiLayer = () => {
|
||||||
@@ -1997,8 +2064,6 @@ const canAttachCachedSharedModel = (modelUrl: string) => (
|
|||||||
Boolean(cachedOverviewModel && cachedSharedModelUrl === modelUrl && scene)
|
Boolean(cachedOverviewModel && cachedSharedModelUrl === modelUrl && scene)
|
||||||
)
|
)
|
||||||
|
|
||||||
const canAttachCachedOverviewModel = () => Boolean(cachedOverviewModel && scene)
|
|
||||||
|
|
||||||
const canLoadFloorSilently = (floorId: string) => {
|
const canLoadFloorSilently = (floorId: string) => {
|
||||||
const floor = floorIndex.value.find((item) => item.floorId === floorId)
|
const floor = floorIndex.value.find((item) => item.floorId === floorId)
|
||||||
return Boolean(floor && canAttachCachedSharedModel(floor.modelUrl))
|
return Boolean(floor && canAttachCachedSharedModel(floor.modelUrl))
|
||||||
@@ -2301,7 +2366,6 @@ const checkAutoSwitch = (options: { forceOverviewEntry?: boolean } = {}) => {
|
|||||||
() => loadFloor(targetFloorId, {
|
() => loadFloor(targetFloorId, {
|
||||||
preserveCurrentSceneUntilReady: hasSceneToKeep,
|
preserveCurrentSceneUntilReady: hasSceneToKeep,
|
||||||
suppressProgress: hasSceneToKeep,
|
suppressProgress: hasSceneToKeep,
|
||||||
allowOverviewFallback: false,
|
|
||||||
detachPoiBeforeLoad: true
|
detachPoiBeforeLoad: true
|
||||||
}),
|
}),
|
||||||
'单楼层模型加载失败',
|
'单楼层模型加载失败',
|
||||||
@@ -2617,24 +2681,123 @@ const loadOverview = async () => {
|
|||||||
renderRoutePreview()
|
renderRoutePreview()
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachCachedSharedModel = (
|
const prepareFloorScene = async (
|
||||||
modelUrl: string,
|
floor: FloorIndexItem,
|
||||||
name: string,
|
|
||||||
loadToken: number,
|
loadToken: number,
|
||||||
floorId?: string,
|
options: LoadFloorOptions = {}
|
||||||
options: { allowAnyCachedOverview?: boolean } = {}
|
): Promise<PreparedFloorScene> => {
|
||||||
|
const requestedFloorId = floor.floorId
|
||||||
|
let model: THREE.Object3D | null = null
|
||||||
|
let ownsModel = true
|
||||||
|
let cacheAsSharedModel = false
|
||||||
|
let protectedResources: ReusableModelResources | undefined
|
||||||
|
|
||||||
|
const cachedExactSharedModel = canAttachCachedSharedModel(floor.modelUrl)
|
||||||
|
? cachedOverviewModel
|
||||||
|
: null
|
||||||
|
|
||||||
|
if (cachedExactSharedModel) {
|
||||||
|
model = cachedExactSharedModel
|
||||||
|
ownsModel = false
|
||||||
|
protectedResources = collectReusableModelResources(cachedExactSharedModel)
|
||||||
|
model.name = `GuideFloorModel_${requestedFloorId}`
|
||||||
|
model.userData.floorId = requestedFloorId
|
||||||
|
model.visible = true
|
||||||
|
applyModelVisibilityForView(model, 'floor', requestedFloorId)
|
||||||
|
} else {
|
||||||
|
if (!options.suppressProgress) {
|
||||||
|
setProgress(18, `正在加载 ${formatFloorLabel(requestedFloorId)} 模型...`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const gltf = await loadModel(
|
||||||
|
floor.modelUrl,
|
||||||
|
`正在加载 ${formatFloorLabel(requestedFloorId)} 模型`,
|
||||||
|
loadToken,
|
||||||
|
{ suppressProgress: options.suppressProgress }
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
assertCurrentModelLoad(loadToken, gltf.scene)
|
||||||
|
} catch (error) {
|
||||||
|
disposeObject(gltf.scene)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
model = gltf.scene
|
||||||
|
model.name = `GuideFloorModel_${requestedFloorId}`
|
||||||
|
model.userData.floorId = requestedFloorId
|
||||||
|
prepareModel(model)
|
||||||
|
applyModelVisibilityForView(model, 'floor', requestedFloorId)
|
||||||
|
cacheAsSharedModel = Boolean(floor.sharedModelAsset)
|
||||||
|
|
||||||
|
if (cacheAsSharedModel) {
|
||||||
|
cacheAutoSwitchOverviewMaxDim(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!model) {
|
||||||
|
throw new Error(`楼层模型加载失败:${requestedFloorId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let poiEntry: PoiMarkerCacheEntry | undefined | null = null
|
||||||
|
try {
|
||||||
|
poiEntry = shouldRenderPoiMarkers.value
|
||||||
|
? await prepareFloorPOIs(floor, loadToken)
|
||||||
|
: null
|
||||||
|
} catch (error) {
|
||||||
|
if (ownsModel) {
|
||||||
|
disposeObject(model, protectedResources)
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
const prepared: PreparedFloorScene = {
|
||||||
|
floor,
|
||||||
|
model,
|
||||||
|
poiEntry: poiEntry || null,
|
||||||
|
ownsModel,
|
||||||
|
cacheAsSharedModel,
|
||||||
|
protectedResources
|
||||||
|
}
|
||||||
|
assertPreparedFloorScene(prepared, loadToken, requestedFloorId)
|
||||||
|
|
||||||
|
return prepared
|
||||||
|
}
|
||||||
|
|
||||||
|
const commitPreparedFloorScene = (
|
||||||
|
prepared: PreparedFloorScene,
|
||||||
|
loadToken: number,
|
||||||
|
expectedFloorId: string
|
||||||
) => {
|
) => {
|
||||||
if (!cachedOverviewModel || (!options.allowAnyCachedOverview && cachedSharedModelUrl !== modelUrl) || !scene) return false
|
assertPreparedFloorScene(prepared, loadToken, expectedFloorId)
|
||||||
|
|
||||||
const targetScene = scene
|
const targetScene = scene
|
||||||
activeModel = cachedOverviewModel
|
if (!targetScene) {
|
||||||
activeModel.name = name
|
if (prepared.ownsModel) {
|
||||||
activeModel.userData.floorId = floorId || ''
|
disposeObject(prepared.model, prepared.protectedResources)
|
||||||
activeModel.visible = true
|
}
|
||||||
applyModelVisibilityForView(activeModel, floorId ? 'floor' : 'multi', floorId)
|
throw createStaleModelLoadError()
|
||||||
|
}
|
||||||
|
|
||||||
|
activeView.value = 'floor'
|
||||||
|
currentFloor.value = expectedFloorId
|
||||||
|
clearSceneData()
|
||||||
|
activeModel = prepared.model
|
||||||
|
|
||||||
|
if (prepared.cacheAsSharedModel) {
|
||||||
|
cachedOverviewModel = activeModel
|
||||||
|
cachedSharedModelUrl = prepared.floor.modelUrl
|
||||||
|
}
|
||||||
|
|
||||||
targetScene.add(activeModel)
|
targetScene.add(activeModel)
|
||||||
assertCurrentModelLoad(loadToken)
|
if (prepared.poiEntry) {
|
||||||
return true
|
attachPoiMarkerGroup(prepared.poiEntry)
|
||||||
|
} else if (poiGroup) {
|
||||||
|
poiGroup.userData.floorId = expectedFloorId
|
||||||
|
poiGroup.userData.currentFloor = expectedFloorId
|
||||||
|
}
|
||||||
|
|
||||||
|
assertCommittedFloorScene(loadToken, expectedFloorId)
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadFloor = async (floorId: string, options: LoadFloorOptions = {}) => {
|
const loadFloor = async (floorId: string, options: LoadFloorOptions = {}) => {
|
||||||
@@ -2648,120 +2811,20 @@ const loadFloor = async (floorId: string, options: LoadFloorOptions = {}) => {
|
|||||||
|
|
||||||
const loadToken = startModelLoad()
|
const loadToken = startModelLoad()
|
||||||
const requestedFloorId = floor.floorId
|
const requestedFloorId = floor.floorId
|
||||||
|
pendingRequestedFloorId = requestedFloorId
|
||||||
if (options.detachPoiBeforeLoad || activeView.value === 'floor' || currentFloor.value !== requestedFloorId) {
|
if (options.detachPoiBeforeLoad || activeView.value === 'floor' || currentFloor.value !== requestedFloorId) {
|
||||||
detachActivePoiLayer()
|
detachActivePoiLayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
const canUseExactCachedModel = canAttachCachedSharedModel(floor.modelUrl)
|
const prepared = await prepareFloorScene(floor, loadToken, options)
|
||||||
const canUseOverviewFallback = !canUseExactCachedModel
|
commitPreparedFloorScene(prepared, loadToken, requestedFloorId)
|
||||||
&& Boolean(options.allowOverviewFallback)
|
|
||||||
&& Boolean(options.preserveCurrentSceneUntilReady)
|
|
||||||
&& canAttachCachedOverviewModel()
|
|
||||||
const preserveCurrentSceneUntilReady = options.preserveCurrentSceneUntilReady && !canUseExactCachedModel
|
|
||||||
let nextModel: THREE.Object3D | null = null
|
|
||||||
|
|
||||||
if (!preserveCurrentSceneUntilReady) {
|
|
||||||
activeView.value = 'floor'
|
|
||||||
currentFloor.value = floor.floorId
|
|
||||||
clearSceneData()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canUseExactCachedModel) {
|
|
||||||
activeView.value = 'floor'
|
|
||||||
currentFloor.value = floor.floorId
|
|
||||||
attachCachedSharedModel(
|
|
||||||
floor.modelUrl,
|
|
||||||
`GuideFloorModel_${floor.floorId}`,
|
|
||||||
loadToken,
|
|
||||||
floor.floorId
|
|
||||||
)
|
|
||||||
assertActiveFloorModelState(loadToken, requestedFloorId)
|
|
||||||
} else {
|
|
||||||
let gltf: GLTF | null = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!options.suppressProgress) {
|
|
||||||
setProgress(18, `正在加载 ${formatFloorLabel(floor.floorId)} 模型...`)
|
|
||||||
}
|
|
||||||
gltf = await loadModel(
|
|
||||||
floor.modelUrl,
|
|
||||||
`正在加载 ${formatFloorLabel(floor.floorId)} 模型`,
|
|
||||||
loadToken,
|
|
||||||
{ suppressProgress: options.suppressProgress }
|
|
||||||
)
|
|
||||||
try {
|
|
||||||
assertCurrentModelLoad(loadToken, gltf.scene)
|
|
||||||
} catch (error) {
|
|
||||||
disposeObject(gltf.scene)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (isStaleModelLoadError(error) || !canUseOverviewFallback) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
activeView.value = 'floor'
|
|
||||||
currentFloor.value = floor.floorId
|
|
||||||
clearSceneData()
|
|
||||||
attachCachedSharedModel(
|
|
||||||
floor.modelUrl,
|
|
||||||
`GuideFloorModel_${floor.floorId}`,
|
|
||||||
loadToken,
|
|
||||||
floor.floorId,
|
|
||||||
{ allowAnyCachedOverview: true }
|
|
||||||
)
|
|
||||||
assertActiveFloorModelState(loadToken, requestedFloorId)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gltf) {
|
|
||||||
const targetScene = scene
|
|
||||||
if (!targetScene) {
|
|
||||||
disposeObject(gltf.scene)
|
|
||||||
throw createStaleModelLoadError()
|
|
||||||
}
|
|
||||||
|
|
||||||
nextModel = gltf.scene
|
|
||||||
nextModel.name = `GuideFloorModel_${floor.floorId}`
|
|
||||||
nextModel.userData.floorId = floor.floorId
|
|
||||||
prepareModel(nextModel)
|
|
||||||
try {
|
|
||||||
assertCurrentModelLoad(loadToken, nextModel)
|
|
||||||
} catch (error) {
|
|
||||||
nextModel = null
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
if (floor.sharedModelAsset) {
|
|
||||||
cacheAutoSwitchOverviewMaxDim(nextModel)
|
|
||||||
}
|
|
||||||
applyModelVisibilityForView(nextModel, 'floor', floor.floorId)
|
|
||||||
|
|
||||||
if (preserveCurrentSceneUntilReady) {
|
|
||||||
activeView.value = 'floor'
|
|
||||||
currentFloor.value = floor.floorId
|
|
||||||
clearSceneData()
|
|
||||||
}
|
|
||||||
|
|
||||||
activeModel = nextModel
|
|
||||||
if (floor.sharedModelAsset) {
|
|
||||||
cachedOverviewModel = activeModel
|
|
||||||
cachedSharedModelUrl = floor.modelUrl
|
|
||||||
}
|
|
||||||
targetScene.add(activeModel)
|
|
||||||
assertActiveFloorModelState(loadToken, requestedFloorId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activeModel) throw createStaleModelLoadError()
|
if (!activeModel) throw createStaleModelLoadError()
|
||||||
assertActiveFloorModelState(loadToken, requestedFloorId)
|
assertCommittedFloorScene(loadToken, requestedFloorId)
|
||||||
hasLoadedFloorViewOnce = true
|
hasLoadedFloorViewOnce = true
|
||||||
fitCameraToObject(activeModel)
|
fitCameraToObject(activeModel)
|
||||||
markFloorAutoSwitchEntry()
|
markFloorAutoSwitchEntry()
|
||||||
|
|
||||||
if (shouldRenderPoiMarkers.value) {
|
|
||||||
await loadFloorPOIs(floor, loadToken)
|
|
||||||
assertActiveFloorModelState(loadToken, requestedFloorId)
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshPoiVisibilityByDistance()
|
refreshPoiVisibilityByDistance()
|
||||||
renderRoutePreview()
|
renderRoutePreview()
|
||||||
}
|
}
|
||||||
@@ -3421,6 +3484,7 @@ const createPoiMarkerGroup = (
|
|||||||
const group = new THREE.Group()
|
const group = new THREE.Group()
|
||||||
group.name = `GuideModelPOI_${floorId}_${displayMode}`
|
group.name = `GuideModelPOI_${floorId}_${displayMode}`
|
||||||
group.userData.floorId = floorId
|
group.userData.floorId = floorId
|
||||||
|
group.userData.currentFloor = floorId
|
||||||
group.userData.displayMode = displayMode
|
group.userData.displayMode = displayMode
|
||||||
|
|
||||||
pois.forEach((poi) => {
|
pois.forEach((poi) => {
|
||||||
@@ -3451,34 +3515,24 @@ const attachPoiMarkerGroup = (entry: PoiMarkerCacheEntry) => {
|
|||||||
if (entry.group.parent !== poiGroup) {
|
if (entry.group.parent !== poiGroup) {
|
||||||
poiGroup.add(entry.group)
|
poiGroup.add(entry.group)
|
||||||
}
|
}
|
||||||
|
poiGroup.userData.floorId = entry.floorId
|
||||||
|
poiGroup.userData.currentFloor = entry.floorId
|
||||||
updatePoiMarkerFocus()
|
updatePoiMarkerFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadFloorPOIs = async (floor: FloorIndexItem, loadToken?: number) => {
|
const prepareFloorPOIs = async (floor: FloorIndexItem, loadToken?: number) => {
|
||||||
if (!poiGroup) return
|
|
||||||
|
|
||||||
if (loadToken !== undefined && !isCurrentModelLoad(loadToken)) return
|
if (loadToken !== undefined && !isCurrentModelLoad(loadToken)) return
|
||||||
if (loadToken !== undefined) {
|
|
||||||
assertActiveFloorModelState(loadToken, floor.floorId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const displayMode = getPoiDisplayMode()
|
const displayMode = getPoiDisplayMode()
|
||||||
const markerCacheKey = getPoiMarkerCacheKey(floor.floorId, displayMode)
|
const markerCacheKey = getPoiMarkerCacheKey(floor.floorId, displayMode)
|
||||||
const cachedEntry = poiMarkerGroupCache.get(markerCacheKey)
|
const cachedEntry = poiMarkerGroupCache.get(markerCacheKey)
|
||||||
if (cachedEntry) {
|
if (cachedEntry) {
|
||||||
if (loadToken !== undefined) {
|
return cachedEntry
|
||||||
assertActiveFloorModelState(loadToken, floor.floorId)
|
|
||||||
}
|
|
||||||
attachPoiMarkerGroup(cachedEntry)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cachedPois = poiDataCache.get(floor.floorId)
|
const cachedPois = poiDataCache.get(floor.floorId)
|
||||||
const floorPois = cachedPois || await props.modelSource.loadFloorPois(floor.floorId)
|
const floorPois = cachedPois || await props.modelSource.loadFloorPois(floor.floorId)
|
||||||
if (loadToken !== undefined && !isCurrentModelLoad(loadToken)) return
|
if (loadToken !== undefined && !isCurrentModelLoad(loadToken)) return
|
||||||
if (loadToken !== undefined) {
|
|
||||||
assertActiveFloorModelState(loadToken, floor.floorId)
|
|
||||||
}
|
|
||||||
|
|
||||||
poiDataCache.set(floor.floorId, floorPois)
|
poiDataCache.set(floor.floorId, floorPois)
|
||||||
const validPois = floorPois.filter((poi) => shouldShowPoiInCurrentMode(poi))
|
const validPois = floorPois.filter((poi) => shouldShowPoiInCurrentMode(poi))
|
||||||
@@ -3496,11 +3550,25 @@ const loadFloorPOIs = async (floor: FloorIndexItem, loadToken?: number) => {
|
|||||||
disposeObject(markerGroup)
|
disposeObject(markerGroup)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
poiMarkerGroupCache.set(markerCacheKey, entry)
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadFloorPOIs = async (floor: FloorIndexItem, loadToken?: number) => {
|
||||||
|
if (!poiGroup) return
|
||||||
|
|
||||||
|
if (loadToken !== undefined) {
|
||||||
|
assertActiveFloorModelState(loadToken, floor.floorId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = await prepareFloorPOIs(floor, loadToken)
|
||||||
|
if (!entry) return
|
||||||
|
|
||||||
if (loadToken !== undefined) {
|
if (loadToken !== undefined) {
|
||||||
assertActiveFloorModelState(loadToken, floor.floorId)
|
assertActiveFloorModelState(loadToken, floor.floorId)
|
||||||
}
|
}
|
||||||
|
|
||||||
poiMarkerGroupCache.set(markerCacheKey, entry)
|
|
||||||
attachPoiMarkerGroup(entry)
|
attachPoiMarkerGroup(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3576,7 +3644,6 @@ const handleSceneTap = async (event: PointerEvent) => {
|
|||||||
await loadFloor(selectedPoi.floorId, {
|
await loadFloor(selectedPoi.floorId, {
|
||||||
preserveCurrentSceneUntilReady: hasRenderableSceneForFloorTransition(),
|
preserveCurrentSceneUntilReady: hasRenderableSceneForFloorTransition(),
|
||||||
suppressProgress: true,
|
suppressProgress: true,
|
||||||
allowOverviewFallback: false,
|
|
||||||
detachPoiBeforeLoad: true
|
detachPoiBeforeLoad: true
|
||||||
})
|
})
|
||||||
emit('floorChange', selectedPoi.floorId)
|
emit('floorChange', selectedPoi.floorId)
|
||||||
@@ -3706,7 +3773,6 @@ const focusTargetPoi = async (request: TargetPoiFocusRequest) => {
|
|||||||
await loadFloor(request.floorId, {
|
await loadFloor(request.floorId, {
|
||||||
preserveCurrentSceneUntilReady: hasRenderableSceneForFloorTransition(),
|
preserveCurrentSceneUntilReady: hasRenderableSceneForFloorTransition(),
|
||||||
suppressProgress: true,
|
suppressProgress: true,
|
||||||
allowOverviewFallback: false,
|
|
||||||
detachPoiBeforeLoad: true
|
detachPoiBeforeLoad: true
|
||||||
})
|
})
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
@@ -3823,22 +3889,14 @@ const init3DScene = async () => {
|
|||||||
|
|
||||||
const handleFloorChange = async (floorId: string) => {
|
const handleFloorChange = async (floorId: string) => {
|
||||||
const requestedFloorId = resolveFloorIdFromRequest(floorId) || floorId
|
const requestedFloorId = resolveFloorIdFromRequest(floorId) || floorId
|
||||||
if (isFloorSwitchLocked) {
|
|
||||||
queuedFloorSwitchId = requestedFloorId
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isFloorSwitchLocked = true
|
|
||||||
queuedFloorSwitchId = ''
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
loadError.value = false
|
loadError.value = false
|
||||||
setProgress(12, `正在切换到 ${formatFloorLabel(requestedFloorId)}...`)
|
setProgress(12, `正在切换到 ${formatFloorLabel(requestedFloorId)}...`)
|
||||||
await loadFloor(requestedFloorId, {
|
await loadFloor(requestedFloorId, {
|
||||||
preserveCurrentSceneUntilReady: false,
|
preserveCurrentSceneUntilReady: true,
|
||||||
suppressProgress: false,
|
suppressProgress: false,
|
||||||
allowOverviewFallback: false,
|
|
||||||
detachPoiBeforeLoad: true
|
detachPoiBeforeLoad: true
|
||||||
})
|
})
|
||||||
updatePoiVisibilityByDistance()
|
updatePoiVisibilityByDistance()
|
||||||
@@ -3851,15 +3909,6 @@ const handleFloorChange = async (floorId: string) => {
|
|||||||
loadError.value = true
|
loadError.value = true
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
setProgress(0, error instanceof Error ? error.message : '楼层模型加载失败')
|
setProgress(0, error instanceof Error ? error.message : '楼层模型加载失败')
|
||||||
} finally {
|
|
||||||
isFloorSwitchLocked = false
|
|
||||||
if (queuedFloorSwitchId && queuedFloorSwitchId !== currentFloor.value) {
|
|
||||||
const nextFloorId = queuedFloorSwitchId
|
|
||||||
queuedFloorSwitchId = ''
|
|
||||||
void handleFloorChange(nextFloorId)
|
|
||||||
} else {
|
|
||||||
queuedFloorSwitchId = ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -445,6 +445,7 @@ const activeFloorId = computed(() => {
|
|||||||
return normalizedFloor?.id || normalizedFloorId
|
return normalizedFloor?.id || normalizedFloorId
|
||||||
})
|
})
|
||||||
const activeFloorScrollIntoView = ref('')
|
const activeFloorScrollIntoView = ref('')
|
||||||
|
const pendingFloorId = ref('')
|
||||||
let suppressFloorAutoScrollUntil = 0
|
let suppressFloorAutoScrollUntil = 0
|
||||||
|
|
||||||
const syncActiveFloorScroll = () => {
|
const syncActiveFloorScroll = () => {
|
||||||
@@ -523,15 +524,31 @@ const handleModeChange = (mode: '2d' | '3d') => {
|
|||||||
emit('modeChange', mode)
|
emit('modeChange', mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isStaleFloorSwitchError = (error: unknown) => (
|
||||||
|
error instanceof Error && error.message === 'STALE_MODEL_LOAD'
|
||||||
|
)
|
||||||
|
|
||||||
const handleFloorChange = (floor: { id: string; label: string }) => {
|
const handleFloorChange = (floor: { id: string; label: string }) => {
|
||||||
const floorId = floor.id || props.normalizeFloorId(floor.label)
|
const floorId = floor.id || props.normalizeFloorId(floor.label)
|
||||||
|
if (!floorId || pendingFloorId.value === floorId) return
|
||||||
|
|
||||||
|
pendingFloorId.value = floorId
|
||||||
suppressFloorAutoScrollUntil = Date.now() + 650
|
suppressFloorAutoScrollUntil = Date.now() + 650
|
||||||
activeFloorScrollIntoView.value = ''
|
activeFloorScrollIntoView.value = ''
|
||||||
indoorRendererRef.value?.disableAutoSwitchTemporarily?.(10000)
|
indoorRendererRef.value?.disableAutoSwitchTemporarily?.(10000)
|
||||||
void indoorRendererRef.value?.switchFloor?.(floorId)
|
|
||||||
emit('indoorViewChange', 'floor')
|
emit('indoorViewChange', 'floor')
|
||||||
emit('layerModeChange', 'single')
|
emit('layerModeChange', 'single')
|
||||||
emit('floorChange', floor.label)
|
|
||||||
|
Promise.resolve(indoorRendererRef.value?.switchFloor?.(floorId))
|
||||||
|
.catch((error) => {
|
||||||
|
if (isStaleFloorSwitchError(error)) return
|
||||||
|
console.error('楼层切换失败:', error)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (pendingFloorId.value === floorId) {
|
||||||
|
pendingFloorId.value = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLayerModeChange = (mode: LayerDisplayMode) => {
|
const handleLayerModeChange = (mode: LayerDisplayMode) => {
|
||||||
@@ -539,10 +556,22 @@ const handleLayerModeChange = (mode: LayerDisplayMode) => {
|
|||||||
indoorRendererRef.value?.disableAutoSwitchTemporarily?.(10000)
|
indoorRendererRef.value?.disableAutoSwitchTemporarily?.(10000)
|
||||||
|
|
||||||
if (mode === 'multi') {
|
if (mode === 'multi') {
|
||||||
|
pendingFloorId.value = ''
|
||||||
void indoorRendererRef.value?.showMultiFloor?.()
|
void indoorRendererRef.value?.showMultiFloor?.()
|
||||||
emit('indoorViewChange', 'multi')
|
emit('indoorViewChange', 'multi')
|
||||||
} else {
|
} else {
|
||||||
void indoorRendererRef.value?.switchFloor?.(activeFloorId.value)
|
const floorId = activeFloorId.value
|
||||||
|
pendingFloorId.value = floorId
|
||||||
|
Promise.resolve(indoorRendererRef.value?.switchFloor?.(floorId))
|
||||||
|
.catch((error) => {
|
||||||
|
if (isStaleFloorSwitchError(error)) return
|
||||||
|
console.error('楼层切换失败:', error)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (pendingFloorId.value === floorId) {
|
||||||
|
pendingFloorId.value = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
emit('indoorViewChange', 'floor')
|
emit('indoorViewChange', 'floor')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -593,8 +622,7 @@ const handleMoreTap = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleThreeFloorChange = (floorId: string) => {
|
const handleThreeFloorChange = (floorId: string) => {
|
||||||
const floor = indoorFloors.value.find((item) => item.id === floorId)
|
emit('floorChange', floorId)
|
||||||
emit('floorChange', floor?.label || floorId)
|
|
||||||
emit('indoorViewChange', 'floor')
|
emit('indoorViewChange', 'floor')
|
||||||
emit('layerModeChange', 'single')
|
emit('layerModeChange', 'single')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,8 +164,15 @@ export const buildSgsFloorAliases = (floors: SgsSdkFloorSummaryPayload[]) => {
|
|||||||
|
|
||||||
const label = formatSgsFloorLabel(floor.floorCode, floor.floorName)
|
const label = formatSgsFloorLabel(floor.floorCode, floor.floorName)
|
||||||
aliases.set(floorId, floorId)
|
aliases.set(floorId, floorId)
|
||||||
if (floor.floorCode) aliases.set(String(floor.floorCode), floorId)
|
aliases.set(floorId.toLowerCase(), floorId)
|
||||||
if (floor.floorName) aliases.set(String(floor.floorName), floorId)
|
if (floor.floorCode) {
|
||||||
|
aliases.set(String(floor.floorCode), floorId)
|
||||||
|
aliases.set(String(floor.floorCode).toLowerCase(), floorId)
|
||||||
|
}
|
||||||
|
if (floor.floorName) {
|
||||||
|
aliases.set(String(floor.floorName), floorId)
|
||||||
|
aliases.set(String(floor.floorName).toLowerCase(), floorId)
|
||||||
|
}
|
||||||
aliases.set(label, floorId)
|
aliases.set(label, floorId)
|
||||||
aliases.set(label.toLowerCase(), floorId)
|
aliases.set(label.toLowerCase(), floorId)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user