feat: 新增统一启动屏组件,优化跨平台加载体验
1. 新增splash-screen全局启动屏组件,适配非APP平台的加载过渡 2. 新增splash-state状态管理模块,统一处理启动屏的显示隐藏、软隐藏时序、兜底超时逻辑 3. 重构App.vue和main.js的加载逻辑,替换原有uni.showLoading方案 4. 在首页和空页面挂载启动屏组件,适配不同页面场景 5. 兼容H5平台降级处理,解决main.js挂载前无法渲染组件的问题
This commit is contained in:
18
App.vue
18
App.vue
@@ -4,6 +4,7 @@
|
|||||||
import Lang from 'common/js/lang.js';
|
import Lang from 'common/js/lang.js';
|
||||||
import ThemeOverride from 'common/js/theme-override.js';
|
import ThemeOverride from 'common/js/theme-override.js';
|
||||||
import PrivacyManager from 'common/js/privacy.js';
|
import PrivacyManager from 'common/js/privacy.js';
|
||||||
|
import splashState from 'common/js/splash-state.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Weixin
|
Weixin
|
||||||
@@ -44,18 +45,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remote-only 模式:
|
// remote-only 模式:
|
||||||
// H5: main.js 已 await boot 完成, 这里秒回
|
// H5: main.js 已 await boot 完成, 这里秒回 (splash 也由 main.js 全权负责)
|
||||||
// 小程序/鸿蒙: main.js 同步 $mount(), boot 在这里异步执行
|
// 小程序/鸿蒙: main.js 同步 $mount(), boot 在这里异步执行
|
||||||
// → boot 期间 showLoading 遮罩, 完成后 hideLoading
|
// → splashState.show() 同步设置 visible 标志, 框架推页面后 splash 组件挂载时读取 isShow() 渲染
|
||||||
const langBoot = Lang.boot();
|
const langBoot = Lang.boot();
|
||||||
this.$langReady = langBoot;
|
this.$langReady = langBoot;
|
||||||
let _hideLangLoading = false;
|
|
||||||
// #ifndef H5
|
// #ifndef H5
|
||||||
if (Lang.isRemoteOnly && Lang.isRemoteOnly()) {
|
const _needSplash = Lang.isRemoteOnly && Lang.isRemoteOnly();
|
||||||
try {
|
if (_needSplash) {
|
||||||
uni.showLoading({ title: '加载中...', mask: true });
|
splashState.show();
|
||||||
_hideLangLoading = true;
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
@@ -74,8 +72,8 @@
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// #ifndef H5
|
// #ifndef H5
|
||||||
if (_hideLangLoading) {
|
if (_needSplash) {
|
||||||
try { uni.hideLoading(); } catch (e) {}
|
splashState.hide({ reason: 'boot-complete' });
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
|
|||||||
225
common/js/splash-state.js
Normal file
225
common/js/splash-state.js
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
// ============================================================
|
||||||
|
// 启动屏单例状态
|
||||||
|
//
|
||||||
|
// 设计要点:
|
||||||
|
// 1. 状态 + 事件双通道: 组件既可 watch visible,也可监听 'splash:hide' 事件
|
||||||
|
// 解决 uni.$emit 在组件挂载前发出就丢失的问题 (memory lesson)
|
||||||
|
// 2. 软隐藏 (softHide): 标记应该 hide, 等组件 mount 后再真正 hide
|
||||||
|
// 解决 onLaunch 跑完 + hide() 早于 page mount 的时序问题
|
||||||
|
// 3. 防重入: show()/hide() 多次调用安全,内部用 guard
|
||||||
|
// 4. 兜底超时: 默认 8s 未关闭则强制 hide,防止卡死
|
||||||
|
// 5. 平台隔离: APP-PLUS 有原生 splash,这里直接返回 false
|
||||||
|
// 6. 跨平台兼容: 同步状态可被 Vue 组件直接 watch
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// APP-PLUS 直接返回 false (有原生启动屏)
|
||||||
|
function needSplash() {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
return false;
|
||||||
|
// #endif
|
||||||
|
// #ifndef APP-PLUS
|
||||||
|
return true;
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// H5 平台特殊处理: Lang.boot() 在 main.js 中 await, 此时 Vue 还没挂载,
|
||||||
|
// 组件无法渲染, 内部降级为 uni.showLoading 做兜底 (虽然样式较丑但至少有遮罩)
|
||||||
|
function useNativeLoading() {
|
||||||
|
// #ifdef H5
|
||||||
|
return true;
|
||||||
|
// #endif
|
||||||
|
// #ifndef H5
|
||||||
|
return false;
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_TIMEOUT = 8000; // 总兜底超时
|
||||||
|
const MOUNT_GRACE_MS = 1000; // 软隐藏后等组件 mount 的宽限时间
|
||||||
|
|
||||||
|
class SplashState {
|
||||||
|
constructor() {
|
||||||
|
this._visible = false;
|
||||||
|
this._timeoutId = null;
|
||||||
|
this._listeners = [];
|
||||||
|
this._shownAt = 0;
|
||||||
|
// 软隐藏相关
|
||||||
|
this._pendingHide = null; // 软隐藏请求的 opts, 等待组件 mount
|
||||||
|
this._hasMounted = false; // 是否有 splash 组件 mount 过
|
||||||
|
this._mountGraceId = null; // 软隐藏兜底定时器
|
||||||
|
}
|
||||||
|
|
||||||
|
show(opts = {}) {
|
||||||
|
if (!needSplash()) return;
|
||||||
|
if (this._visible) return; // 防重入
|
||||||
|
|
||||||
|
this._visible = true;
|
||||||
|
this._shownAt = Date.now();
|
||||||
|
this._pendingHide = null;
|
||||||
|
this._hasMounted = false;
|
||||||
|
if (this._mountGraceId) {
|
||||||
|
clearTimeout(this._mountGraceId);
|
||||||
|
this._mountGraceId = null;
|
||||||
|
}
|
||||||
|
this._emit('splash:show');
|
||||||
|
|
||||||
|
// H5 降级: 走系统 loading (Vue 还没挂载,组件层无法渲染)
|
||||||
|
if (useNativeLoading()) {
|
||||||
|
try { uni.showLoading({ title: opts.title || '加载中...', mask: true }); } catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeout = opts.timeout || DEFAULT_TIMEOUT;
|
||||||
|
if (this._timeoutId) {
|
||||||
|
clearTimeout(this._timeoutId);
|
||||||
|
this._timeoutId = null;
|
||||||
|
}
|
||||||
|
this._timeoutId = setTimeout(() => {
|
||||||
|
// 兜底: 超时强制关闭,防止卡死 (走硬隐藏路径, 不等组件)
|
||||||
|
if (this._visible) {
|
||||||
|
console.warn('[splash-state] auto-hide after timeout', timeout);
|
||||||
|
this._doHide({ reason: 'timeout' });
|
||||||
|
}
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏 splash
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {string} opts.reason - 隐藏原因
|
||||||
|
* - 'boot-complete': 软隐藏, 等组件 mount 后再真关 (默认)
|
||||||
|
* - 'timeout': 硬隐藏, 立即关 (兜底用)
|
||||||
|
* - 'manual': 硬隐藏, 立即关
|
||||||
|
* @param {boolean} [opts.soft] - 强制走软隐藏
|
||||||
|
*/
|
||||||
|
hide(opts = {}) {
|
||||||
|
const reason = opts.reason || 'manual';
|
||||||
|
// boot-complete 默认走软隐藏, 避免时序问题
|
||||||
|
const useSoft = opts.soft === true || (opts.soft !== false && reason === 'boot-complete');
|
||||||
|
|
||||||
|
if (useSoft) {
|
||||||
|
this._softHide({ reason });
|
||||||
|
} else {
|
||||||
|
this._doHide({ reason });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 软隐藏: 标记应该 hide, 等组件 mount
|
||||||
|
* 如果组件 1s 内没 mount, 兜底自动 hide
|
||||||
|
*/
|
||||||
|
_softHide(opts) {
|
||||||
|
if (!this._visible) return; // 已经隐藏了
|
||||||
|
this._pendingHide = opts;
|
||||||
|
|
||||||
|
// 如果组件已经 mount, 立即隐藏
|
||||||
|
if (this._hasMounted) {
|
||||||
|
this._flushPendingHide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兜底: 1s 内没 mount, 强制隐藏
|
||||||
|
if (this._mountGraceId) clearTimeout(this._mountGraceId);
|
||||||
|
this._mountGraceId = setTimeout(() => {
|
||||||
|
if (this._pendingHide) {
|
||||||
|
this._flushPendingHide();
|
||||||
|
}
|
||||||
|
}, MOUNT_GRACE_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实际隐藏 (硬路径)
|
||||||
|
*/
|
||||||
|
_doHide(opts) {
|
||||||
|
if (!this._visible) return; // 防重入
|
||||||
|
if (this._timeoutId) {
|
||||||
|
clearTimeout(this._timeoutId);
|
||||||
|
this._timeoutId = null;
|
||||||
|
}
|
||||||
|
if (this._mountGraceId) {
|
||||||
|
clearTimeout(this._mountGraceId);
|
||||||
|
this._mountGraceId = null;
|
||||||
|
}
|
||||||
|
this._pendingHide = null;
|
||||||
|
this._visible = false;
|
||||||
|
// H5 降级对应的关闭
|
||||||
|
if (useNativeLoading()) {
|
||||||
|
try { uni.hideLoading(); } catch (e) {}
|
||||||
|
}
|
||||||
|
this._emit('splash:hide', { reason: opts.reason || 'manual' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 真正触发软隐藏
|
||||||
|
*/
|
||||||
|
_flushPendingHide() {
|
||||||
|
const opts = this._pendingHide;
|
||||||
|
this._pendingHide = null;
|
||||||
|
if (this._mountGraceId) {
|
||||||
|
clearTimeout(this._mountGraceId);
|
||||||
|
this._mountGraceId = null;
|
||||||
|
}
|
||||||
|
this._doHide(opts || { reason: 'mount-completed' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件 mount 后通知 state 模块
|
||||||
|
* - 标记 _hasMounted
|
||||||
|
* - 如果有 _pendingHide, 触发真关
|
||||||
|
* - H5 降级场景: 立即关掉原生 loading, 让 splash 组件接管视觉
|
||||||
|
*/
|
||||||
|
notifyMounted() {
|
||||||
|
if (this._hasMounted) return;
|
||||||
|
this._hasMounted = true;
|
||||||
|
// H5 降级: 立即关掉原生 loading, 避免和 splash 组件重叠
|
||||||
|
if (useNativeLoading()) {
|
||||||
|
try { uni.hideLoading(); } catch (e) {}
|
||||||
|
}
|
||||||
|
if (this._pendingHide) {
|
||||||
|
// 给组件一帧时间渲染, 再隐藏 (避免一帧闪烁)
|
||||||
|
setTimeout(() => this._flushPendingHide(), 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件 unmount 时通知 (用于 _hasMounted 状态管理)
|
||||||
|
* 目前保留 _hasMounted=true, 因为一般只有 tab 页有 splash 组件, 不会动态增减
|
||||||
|
*/
|
||||||
|
notifyUnmounted() {
|
||||||
|
// 不重置 _hasMounted, 因为软隐藏的 _pendingHide 处理不依赖 unmount
|
||||||
|
}
|
||||||
|
|
||||||
|
isShow() {
|
||||||
|
return this._visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPendingHide() {
|
||||||
|
return this._pendingHide != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed() {
|
||||||
|
if (!this._visible) return 0;
|
||||||
|
return Date.now() - this._shownAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
on(event, fn) {
|
||||||
|
if (!this._listeners[event]) this._listeners[event] = [];
|
||||||
|
this._listeners[event].push(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
off(event, fn) {
|
||||||
|
if (!this._listeners[event]) return;
|
||||||
|
this._listeners[event] = this._listeners[event].filter(f => f !== fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
_emit(event, payload) {
|
||||||
|
const arr = this._listeners[event];
|
||||||
|
if (!arr || !arr.length) return;
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
try { arr[i](payload); } catch (e) { console.warn('[splash-state] listener err', e); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const splashState = new SplashState();
|
||||||
|
|
||||||
|
export default splashState;
|
||||||
|
export { splashState };
|
||||||
178
components/splash-screen/splash-screen.vue
Normal file
178
components/splash-screen/splash-screen.vue
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
<template>
|
||||||
|
<!-- #ifndef APP-PLUS -->
|
||||||
|
<view v-if="visible" class="splash-screen" :class="{ 'splash-screen--leaving': leaving }">
|
||||||
|
<view class="splash-screen__bg"></view>
|
||||||
|
<view class="splash-screen__content">
|
||||||
|
<view class="splash-screen__logo">
|
||||||
|
<view class="splash-screen__logo-inner">S</view>
|
||||||
|
</view>
|
||||||
|
<view class="splash-screen__name">Shop</view>
|
||||||
|
<view class="splash-screen__loader">
|
||||||
|
<view class="splash-screen__dot"></view>
|
||||||
|
<view class="splash-screen__dot"></view>
|
||||||
|
<view class="splash-screen__dot"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import splashState from '@/common/js/splash-state.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'splash-screen',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
leaving: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 1) 启动时同步读取状态 (防止 mount 前已经 hide 的情况)
|
||||||
|
if (splashState.isShow()) {
|
||||||
|
this.visible = true;
|
||||||
|
}
|
||||||
|
// 2) 通知 state 模块: 组件已 mount
|
||||||
|
// 如果此时 splashState 有 _pendingHide (软隐藏), 会触发真关
|
||||||
|
splashState.notifyMounted();
|
||||||
|
// 3) 监听后续事件
|
||||||
|
splashState.on('splash:show', () => {
|
||||||
|
this.visible = true;
|
||||||
|
this.leaving = false;
|
||||||
|
});
|
||||||
|
splashState.on('splash:hide', () => {
|
||||||
|
this._leave();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
splashState.off('splash:show');
|
||||||
|
splashState.off('splash:hide');
|
||||||
|
splashState.notifyUnmounted();
|
||||||
|
if (this._leaveTimer) {
|
||||||
|
clearTimeout(this._leaveTimer);
|
||||||
|
this._leaveTimer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
_leave() {
|
||||||
|
// 触发淡出动画,动画结束再 unmount
|
||||||
|
this.leaving = true;
|
||||||
|
if (this._leaveTimer) clearTimeout(this._leaveTimer);
|
||||||
|
this._leaveTimer = setTimeout(() => {
|
||||||
|
this.visible = false;
|
||||||
|
this.leaving = false;
|
||||||
|
}, 320);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.splash-screen {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 99999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
|
||||||
|
&--leaving {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash-screen__bg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(180deg, #ffffff 0%, #f7f7f7 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash-screen__content {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash-screen__logo {
|
||||||
|
width: 96rpx;
|
||||||
|
height: 96rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
background: #FF0036;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(255, 0, 54, 0.18);
|
||||||
|
animation: splash-pop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash-screen__logo-inner {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 56rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash-screen__name {
|
||||||
|
margin-top: 28rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash-screen__loader {
|
||||||
|
margin-top: 48rpx;
|
||||||
|
display: flex;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash-screen__dot {
|
||||||
|
width: 12rpx;
|
||||||
|
height: 12rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #FF0036;
|
||||||
|
opacity: 0.4;
|
||||||
|
animation: splash-bounce 1.2s ease-in-out infinite;
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
animation-delay: 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3) {
|
||||||
|
animation-delay: 0.3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes splash-pop {
|
||||||
|
0% {
|
||||||
|
transform: scale(0.6);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes splash-bounce {
|
||||||
|
0%, 80%, 100% {
|
||||||
|
transform: scale(0.7);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
main.js
15
main.js
@@ -16,6 +16,7 @@ import {
|
|||||||
} from './common/js/storage.js'
|
} from './common/js/storage.js'
|
||||||
import configExternal from './common/js/config-external.js'
|
import configExternal from './common/js/config-external.js'
|
||||||
import PrivacyManager from './common/js/privacy.js'
|
import PrivacyManager from './common/js/privacy.js'
|
||||||
|
import splashState from './common/js/splash-state.js'
|
||||||
|
|
||||||
Vue.prototype.$store = store //挂在vue
|
Vue.prototype.$store = store //挂在vue
|
||||||
|
|
||||||
@@ -67,26 +68,22 @@ uniStorage();
|
|||||||
// 以下代码所有平台共享:
|
// 以下代码所有平台共享:
|
||||||
// - lang.js: bootCompleted 标志, boot 未完成时 lang() 返回空串
|
// - lang.js: bootCompleted 标志, boot 未完成时 lang() 返回空串
|
||||||
// - http.js: sendRequest 门控, boot 未完成时业务请求排队
|
// - http.js: sendRequest 门控, boot 未完成时业务请求排队
|
||||||
// - App.vue: onLaunch 中 showLoading + await boot + hideLoading
|
// - splash-state.js: 统一启动屏状态 (H5 降级 uni.showLoading, 小程序/鸿蒙走 splash 组件)
|
||||||
|
// - App.vue: onLaunch 中 splashState.show() + await boot + splashState.hide()
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
// H5: remote-only 下先 await boot 再 $mount(), 首屏直接有翻译数据
|
// H5: remote-only 下先 await boot 再 $mount(), 首屏直接有翻译数据
|
||||||
|
// splashState.show() 内部 H5 降级为 uni.showLoading (await 期间 Vue 还没挂载,组件层无法渲染)
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
if (Lang.isRemoteOnly && Lang.isRemoteOnly()) {
|
if (Lang.isRemoteOnly && Lang.isRemoteOnly()) {
|
||||||
let _hideLoading = false;
|
splashState.show();
|
||||||
try {
|
|
||||||
uni.showLoading({ title: '加载中...', mask: true });
|
|
||||||
_hideLoading = true;
|
|
||||||
} catch (e) {}
|
|
||||||
try {
|
try {
|
||||||
await Lang.boot();
|
await Lang.boot();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[i18n] remote-only boot fail', e);
|
console.warn('[i18n] remote-only boot fail', e);
|
||||||
} finally {
|
} finally {
|
||||||
if (_hideLoading) {
|
splashState.hide({ reason: 'boot-complete' });
|
||||||
try { uni.hideLoading(); } catch (e) {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="empty" :style="themeColor">
|
<view class="empty" :style="themeColor">
|
||||||
|
<splash-screen />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,8 @@
|
|||||||
<ns-login ref="login"></ns-login>
|
<ns-login ref="login"></ns-login>
|
||||||
<!-- 版本信息触发区(固定定位,不可见,始终可点击) -->
|
<!-- 版本信息触发区(固定定位,不可见,始终可点击) -->
|
||||||
<view class="version-trigger" @tap="onTitleTap"></view>
|
<view class="version-trigger" @tap="onTitleTap"></view>
|
||||||
|
<!-- 启动屏: 由 splash-state 状态控制, 仅在 boot 阶段显示 -->
|
||||||
|
<splash-screen />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user