fix(splash-screen): 启动屏等待首页内容就绪后再消失

问题描述: 首次启动时,启动屏已关闭但首页内容还是空白的,用户体验差。

解决方案:
1. 在 splash-state.js 中新增页面就绪等待机制 (_pageReady 标志、notifyPageReady 方法)
2. 修改软隐藏逻辑,必须同时满足组件已 mount 和页面已就绪才关闭启动屏
3. 在首页 onDiyGroupLoaded 回调中通知页面就绪

兜底保护:
- 组件 1s 未 mount → 强制关闭
- 页面 2s 未就绪 → 强制关闭
- 总超时 8s → 强制关闭
This commit is contained in:
2026-07-13 10:36:04 +08:00
parent 3f09dca054
commit edea8c32de
2 changed files with 58 additions and 6 deletions

View File

@@ -35,6 +35,7 @@ function useNativeLoading() {
const DEFAULT_TIMEOUT = 8000; // 总兜底超时
const MOUNT_GRACE_MS = 1000; // 软隐藏后等组件 mount 的宽限时间
const PAGE_READY_GRACE_MS = 2000; // 软隐藏后等页面就绪的宽限时间
// 启动期阶段定义 (id → { percent, label })
// percent: 阶段完成的百分比 (0-100)
@@ -58,7 +59,10 @@ class SplashState {
// 软隐藏相关
this._pendingHide = null; // 软隐藏请求的 opts, 等待组件 mount
this._hasMounted = false; // 是否有 splash 组件 mount 过
this._mountGraceId = null; // 软隐藏兜底定时器
this._mountGraceId = null; // 软隐藏兜底定时器 (等 mount)
// 页面就绪等待
this._pageReady = false; // 页面内容是否已就绪 (由首页通知)
this._pageReadyGraceId = null; // 页面就绪兜底定时器
// 进度上报
this._stage = 'boot';
this._percent = 0;
@@ -77,6 +81,7 @@ class SplashState {
this._shownAt = Date.now();
this._pendingHide = null;
this._hasMounted = false;
this._pageReady = false;
this._stage = 'boot';
// 初始化时直接显示 'boot' 阶段的 5%, 而不是 0%
// 避免首帧出现"进度条 0%"的视觉突兀
@@ -91,6 +96,10 @@ class SplashState {
clearTimeout(this._mountGraceId);
this._mountGraceId = null;
}
if (this._pageReadyGraceId) {
clearTimeout(this._pageReadyGraceId);
this._pageReadyGraceId = null;
}
this._emit('splash:show');
// H5 降级: 走系统 loading (Vue 还没挂载,组件层无法渲染)
@@ -138,15 +147,16 @@ class SplashState {
}
/**
* 软隐藏: 标记应该 hide, 等组件 mount
* 软隐藏: 标记应该 hide, 等组件 mount + 页面就绪
* 如果组件 1s 内没 mount, 兜底自动 hide
* 如果页面 2s 内没就绪, 兜底自动 hide
*/
_softHide(opts) {
if (!this._visible) return; // 已经隐藏了
this._pendingHide = opts;
// 如果组件已经 mount, 立即隐藏
if (this._hasMounted) {
// 如果组件已经 mount 且页面已就绪, 立即隐藏
if (this._hasMounted && this._pageReady) {
this._flushPendingHide();
return;
}
@@ -162,6 +172,7 @@ class SplashState {
/**
* 真正触发软隐藏
* 如果页面还未就绪, 等待页面就绪后再 hide
*/
_flushPendingHide() {
const opts = this._pendingHide;
@@ -170,6 +181,20 @@ class SplashState {
clearTimeout(this._mountGraceId);
this._mountGraceId = null;
}
// 如果页面还未就绪, 等待页面就绪
if (!this._pageReady) {
// 兜底: 2s 内没就绪, 强制隐藏
if (this._pageReadyGraceId) clearTimeout(this._pageReadyGraceId);
this._pageReadyGraceId = setTimeout(() => {
if (this._visible) {
console.warn('[splash-state] auto-hide after page-ready timeout');
this._doHide(opts || { reason: 'page-ready-timeout' });
}
}, PAGE_READY_GRACE_MS);
return;
}
this._doHide(opts || { reason: 'mount-completed' });
}
@@ -186,6 +211,10 @@ class SplashState {
clearTimeout(this._mountGraceId);
this._mountGraceId = null;
}
if (this._pageReadyGraceId) {
clearTimeout(this._pageReadyGraceId);
this._pageReadyGraceId = null;
}
this._pendingHide = null;
this._visible = false;
// H5 降级对应的关闭
@@ -217,6 +246,25 @@ class SplashState {
}
}
/**
* 页面内容就绪后通知 state 模块
* - 标记 _pageReady
* - 如果有 _pendingHide, 触发真关
* 调用时机: 首页 diy-group 渲染完成后调用
*/
notifyPageReady() {
if (this._pageReady) return;
this._pageReady = true;
if (this._pageReadyGraceId) {
clearTimeout(this._pageReadyGraceId);
this._pageReadyGraceId = null;
}
if (this._pendingHide) {
// 给页面一帧时间完成渲染, 再隐藏
setTimeout(() => this._flushPendingHide(), 50);
}
}
/**
* 组件 unmount 时通知 (用于 _hasMounted 状态管理)
* 目前保留 _hasMounted=true, 因为一般只有 tab 页有 splash 组件, 不会动态增减

View File

@@ -1,3 +1,5 @@
import splashState from '@/common/js/splash-state.js';
export default {
data() {
return {
@@ -124,8 +126,10 @@ export default {
return false;
},
// diy-group 渲染完毕回调, 用于触发版本水印等延后显示元素
// 同时通知 splash-state 页面就绪, 启动屏可以关闭
onDiyGroupLoaded() {
this.diyGroupReady = true;
try { splashState.notifyPageReady(); } catch (e) {}
},
/**
* 版本连击 3 次以上显示构建版本信息(供测试人员使用)