同步加载页与首页模型加载状态

This commit is contained in:
lyf
2026-07-06 10:11:20 +08:00
parent b99ae572da
commit 4a6a7f9c29
3 changed files with 151 additions and 14 deletions

View File

@@ -51,6 +51,7 @@
@poi-click="handleGuidePoiClick"
@selection-clear="handleGuideSelectionClear"
@auto-switch="handleAutoSwitch"
@initial-model-progress="handleInitialModelProgress"
@initial-model-ready="handleInitialModelReady"
@initial-model-failed="handleInitialModelFailed"
@tool-click="handleIndoorToolClick"
@@ -255,10 +256,18 @@
<text class="app-launch-subtitle">Shenzhen Natural History Museum</text>
</view>
<view class="app-launch-status">
<text class="app-launch-loading-text">正在进入自然导览</text>
<text class="app-launch-loading-text">{{ launchLoadingText }}</text>
<view class="app-launch-progress">
<view class="app-launch-progress-bar" :style="launchProgressStyle"></view>
</view>
<view v-if="launchOverlayRecoverable" class="app-launch-actions">
<view class="app-launch-action secondary" @tap="handleLaunchRetry">
<text class="app-launch-action-text">重新加载</text>
</view>
<view class="app-launch-action primary" @tap="handleLaunchContinue">
<text class="app-launch-action-text">继续进入</text>
</view>
</view>
</view>
</view>
</view>
@@ -346,6 +355,7 @@ const topTabFromHash = () => {
return isGuideTopTab(tab) ? tab : null
}
const launchOverlayMinDisplayMs = 700
const launchOverlayGuideTimeoutMs = 12000
const launchOverlayNonGuideTimeoutMs = 900
@@ -372,8 +382,10 @@ const selectedGuidePoi = ref<GuideRenderPoi | null>(null)
const currentTab = ref<GuideTopTab>(initialTopTab())
const launchOverlayVisible = ref(shouldShowLaunchOverlay())
const launchOverlayLeaving = ref(false)
const launchProgress = ref(18)
const launchProgress = ref(8)
const launchLoadingText = ref('正在初始化三维场景')
const launchInitialModelSettled = ref(false)
const launchOverlayRecoverable = ref(false)
const showRoutePlanner = ref(false)
const routeStartPoint = ref<RoutePointOption | null>(null)
const routeEndPoint = ref<RoutePointOption | null>(null)
@@ -396,7 +408,8 @@ const homeSearchExpanded = ref(false)
let launchOverlayFallbackTimer: ReturnType<typeof setTimeout> | null = null
let launchOverlayFadeTimer: ReturnType<typeof setTimeout> | null = null
let launchProgressTimer: ReturnType<typeof setInterval> | null = null
let launchOverlayHideDelayTimer: ReturnType<typeof setTimeout> | null = null
let launchOverlayStartedAt = 0
const launchProgressStyle = computed(() => ({
width: `${launchProgress.value}%`
@@ -413,13 +426,13 @@ const clearLaunchTimers = () => {
launchOverlayFadeTimer = null
}
if (launchProgressTimer) {
clearInterval(launchProgressTimer)
launchProgressTimer = null
if (launchOverlayHideDelayTimer) {
clearTimeout(launchOverlayHideDelayTimer)
launchOverlayHideDelayTimer = null
}
}
const hideLaunchOverlay = () => {
const completeHideLaunchOverlay = () => {
if (!launchOverlayVisible.value) return
launchProgress.value = 100
@@ -433,6 +446,24 @@ const hideLaunchOverlay = () => {
}, 360)
}
const hideLaunchOverlay = () => {
if (!launchOverlayVisible.value || launchOverlayLeaving.value) return
const elapsedMs = launchOverlayStartedAt ? Date.now() - launchOverlayStartedAt : launchOverlayMinDisplayMs
const waitMs = Math.max(0, launchOverlayMinDisplayMs - elapsedMs)
if (waitMs > 0) {
if (launchOverlayHideDelayTimer) clearTimeout(launchOverlayHideDelayTimer)
launchOverlayHideDelayTimer = setTimeout(() => {
launchOverlayHideDelayTimer = null
completeHideLaunchOverlay()
}, waitMs)
return
}
completeHideLaunchOverlay()
}
const shouldWaitForLaunchModel = () => (
currentTab.value === 'guide'
&& is3DMode.value
@@ -443,10 +474,12 @@ const startLaunchOverlay = () => {
clearLaunchTimers()
launchInitialModelSettled.value = false
launchProgressTimer = setInterval(() => {
launchProgress.value = Math.min(92, launchProgress.value + 11)
}, 180)
launchOverlayRecoverable.value = false
launchOverlayStartedAt = Date.now()
launchProgress.value = shouldWaitForLaunchModel() ? 8 : 68
launchLoadingText.value = shouldWaitForLaunchModel()
? '正在初始化三维场景'
: '正在进入自然导览'
const timeoutMs = shouldWaitForLaunchModel()
? launchOverlayGuideTimeoutMs
@@ -454,24 +487,53 @@ const startLaunchOverlay = () => {
launchOverlayFallbackTimer = setTimeout(() => {
if (shouldWaitForLaunchModel() && !launchInitialModelSettled.value) {
console.warn(`首页前置加载页等待模型超过 ${timeoutMs}ms交给页面自身加载态接管`)
launchOverlayRecoverable.value = true
launchProgress.value = Math.min(96, Math.max(launchProgress.value, 92))
launchLoadingText.value = '三维场景加载较慢,仍在等待模型就绪'
console.warn(`首页前置加载页等待模型超过 ${timeoutMs}ms继续等待真实模型 ready`)
return
}
hideLaunchOverlay()
}, timeoutMs)
}
const handleInitialModelProgress = (event: { progress: number; message: string; view: GuideIndoorView; floorId?: string; elapsedMs?: number }) => {
if (!launchOverlayVisible.value || launchInitialModelSettled.value || !shouldWaitForLaunchModel()) return
launchOverlayRecoverable.value = false
launchProgress.value = Math.min(99, Math.max(8, event.progress))
launchLoadingText.value = event.message || '正在加载三维场景'
}
const settleLaunchOverlayByModel = (status: 'ready' | 'failed', event?: { view: GuideIndoorView; floorId?: string; message?: string; elapsedMs?: number }) => {
launchInitialModelSettled.value = true
launchOverlayRecoverable.value = status === 'failed'
if (status === 'failed') {
console.warn('首页前置加载页收到初始模型加载失败,交给 ThreeMap 错误态接管:', event)
launchProgress.value = Math.min(launchProgress.value, 96)
launchLoadingText.value = event?.message || '三维场景加载失败'
console.warn('首页前置加载页收到初始模型加载失败,等待用户选择重试或进入降级首页:', event)
return
} else {
launchProgress.value = 100
launchLoadingText.value = '三维场景已就绪'
console.log('首页前置加载页收到初始模型加载完成:', event)
}
hideLaunchOverlay()
}
const handleLaunchRetry = () => {
if (typeof window !== 'undefined') {
window.location.reload()
}
}
const handleLaunchContinue = () => {
launchOverlayRecoverable.value = false
hideLaunchOverlay()
}
// 馆外导航相关状态
const isOutdoorGuidePanelEnabled = false
const showOutdoorNavPanel = ref(false)
@@ -1588,6 +1650,43 @@ const handleExplainBack = () => {
transition: width 0.22s ease;
}
.app-launch-actions {
margin-top: 14px;
display: flex;
gap: 10px;
}
.app-launch-action {
height: 36px;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
border-radius: 8px;
}
.app-launch-action.secondary {
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.38);
}
.app-launch-action.primary {
background: var(--museum-accent);
border: 1px solid var(--museum-accent);
}
.app-launch-action-text {
font-size: 13px;
line-height: 18px;
font-weight: 700;
color: #ffffff;
}
.app-launch-action.primary .app-launch-action-text {
color: #151713;
}
@media (max-width: 360px) {
.app-launch-brand {
top: calc(env(safe-area-inset-top) + 48px);