Files
lucky_shop/components/splash-screen/splash-screen.vue
ZF sun 55a9ed5946 refactor: 重构底部导航高亮逻辑,新增跨平台兼容与工具脚本
1. 抽离底部导航归属逻辑为纯函数resolveEntryTab,优化分包页面高亮规则,对齐iOS HIG规范
2. 新增页面Tab通配符映射,简化PAGE_TAB_MAP维护
3. 修复鸿蒙平台webview返回按钮适配问题
4. 新增pages.json校验与导航标题自动注入脚本
5. 优化启动页动画性能,提升合成渲染效率
6. 修复diy-picture组件v-for缺少key的警告
7. 清理鸿蒙平台多余的分包构建配置
8. 更新统计数据与store注释说明
2026-07-16 11:47:29 +08:00

376 lines
8.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<!-- #ifndef APP-PLUS -->
<view v-if="visible" class="splash-screen" :class="{ 'splash-screen--leaving': leaving }">
<view class="splash-screen__field">
<view class="splash-screen__aura"></view>
<view class="splash-screen__orbit splash-screen__orbit--1">
<view class="splash-screen__particle splash-screen__particle--1"></view>
</view>
<view class="splash-screen__orbit splash-screen__orbit--2">
<view class="splash-screen__particle splash-screen__particle--2"></view>
</view>
<view class="splash-screen__orbit splash-screen__orbit--3">
<view class="splash-screen__particle splash-screen__particle--3"></view>
</view>
<view class="splash-screen__core">
<view class="splash-screen__core-halo"></view>
<view class="splash-screen__core-body"></view>
</view>
</view>
<!-- 进度条 + 百分比 -->
<view class="splash-screen__progress">
<text class="splash-screen__progress-percent">{{ percent }}%</text>
<view class="splash-screen__progress-bar">
<view class="splash-screen__progress-fill" :style="{ width: percent + '%' }"></view>
</view>
</view>
</view>
<!-- #endif -->
</template>
<script>
import splashState from '@/common/js/splash-state.js';
export default {
name: 'splash-screen',
data() {
return {
visible: true,
leaving: false,
percent: 0,
};
},
created() {
try {
const init = splashState.getProgress()
this.percent = init.percent
} catch (e) {}
if (!splashState.isShow()) {
this.visible = false
}
},
mounted() {
splashState.notifyMounted();
splashState.on('splash:show', () => {
this.visible = true;
this.leaving = false;
const init = splashState.getProgress()
if (init.percent > this.percent) this.percent = init.percent
});
splashState.on('splash:hide', (info) => {
if (this.percent < 100) {
this.percent = 100;
}
const fast = info && info.mode === 'fast'
this._leave(fast)
});
splashState.onProgress((info) => {
if (info.percent !== this.percent) this.percent = info.percent
});
},
beforeDestroy() {
splashState.off('splash:show');
splashState.off('splash:hide');
splashState.offProgress();
splashState.notifyUnmounted();
if (this._leaveTimer) {
clearTimeout(this._leaveTimer);
this._leaveTimer = null;
}
},
methods: {
_leave(fast) {
this.leaving = true;
if (this._leaveTimer) clearTimeout(this._leaveTimer);
const delay = fast ? 80 : 480
this._leaveTimer = setTimeout(() => {
this.visible = false;
this.leaving = false;
}, delay);
}
}
};
</script>
<style lang="scss" scoped>
$accent: #6366F1;
$accent-light: #A5B4FC;
.splash-screen {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 99999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #ffffff;
overflow: hidden;
opacity: 1;
transition: opacity 0.45s cubic-bezier(0.4, 0, 0.2, 1);
// 启动屏是纯视觉装饰(轨道/粒子/进度条),无任何可点击元素。
// 关闭 pointer-events,让首页在 boot 完成前即可滚动/点击,
// 否则 z-index:99999 全屏 fixed 会把触摸事件全部吞掉(含 480ms 渐隐期)。
pointer-events: none;
&--leaving {
opacity: 0;
}
}
.splash-screen__progress {
position: absolute;
bottom: 120rpx;
left: 50%;
transform: translateX(-50%);
width: 480rpx;
max-width: 80vw;
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
opacity: 0;
animation: progress-fade-in 0.2s ease-out 0.1s forwards;
}
.splash-screen__progress-bar {
position: relative;
width: 100%;
height: 4rpx;
background: rgba($accent, 0.08);
border-radius: 2rpx;
overflow: hidden;
}
.splash-screen__progress-fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
background: linear-gradient(90deg, $accent-light, $accent);
border-radius: 2rpx;
transition: width 0.45s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 0 12rpx rgba($accent, 0.4);
}
.splash-screen__progress-percent {
font-size: 24rpx;
font-variant-numeric: tabular-nums;
font-weight: 500;
color: $accent;
}
@keyframes progress-fade-in {
from { opacity: 0; transform: translateX(-50%) translateY(20rpx); }
to { opacity: 1; transform: translateX(-50%) translateY(0); }
}
.splash-screen__field {
position: relative;
width: 480rpx;
height: 480rpx;
display: flex;
align-items: center;
justify-content: center;
animation: field-enter 0.4s cubic-bezier(0.34, 1.4, 0.64, 1) both;
}
.splash-screen__aura {
position: absolute;
width: 480rpx;
height: 480rpx;
border-radius: 50%;
background: radial-gradient(circle,
rgba($accent-light, 0.35) 0%,
rgba($accent, 0.18) 25%,
rgba($accent, 0.05) 50%,
rgba($accent, 0) 75%);
box-shadow: 0 0 200rpx rgba($accent, 0.15);
animation: aura-breathe 3.6s ease-in-out infinite;
// 提升为独立合成层:200rpx 的 box-shadow 与 radial-gradient 只光栅化一次,
// 之后 aura-breathe 的 scale/opacity 全由 GPU 合成,不再逐帧重绘。视觉不变。
will-change: transform, opacity;
transform: translateZ(0);
backface-visibility: hidden;
}
.splash-screen__core {
position: relative;
width: 36rpx;
height: 36rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
animation: core-pulse 2.4s ease-in-out infinite;
will-change: transform;
backface-visibility: hidden;
}
.splash-screen__core-halo {
position: absolute;
width: 120rpx;
height: 120rpx;
border-radius: 50%;
background: radial-gradient(circle,
rgba($accent, 0.5) 0%,
rgba($accent, 0) 60%);
animation: halo-expand 2.4s ease-in-out infinite;
will-change: transform, opacity;
backface-visibility: hidden;
}
.splash-screen__core-body {
position: relative;
width: 36rpx;
height: 36rpx;
border-radius: 50%;
background: radial-gradient(circle at 35% 30%,
#EEF2FF 0%,
$accent-light 35%,
$accent 70%,
#4338CA 100%);
box-shadow:
0 0 24rpx rgba($accent, 0.6),
0 0 48rpx rgba($accent, 0.3),
inset -2rpx -3rpx 6rpx rgba(#4338CA, 0.5),
inset 2rpx 2rpx 4rpx rgba(255, 255, 255, 0.5);
}
.splash-screen__orbit {
position: absolute;
top: 50%;
left: 50%;
border-radius: 50%;
pointer-events: none;
transform-origin: center;
// 提升为合成层:内部 particle 的 box-shadow 随图层一起绘制一次,
// orbit-rotate 旋转时 GPU 直接变换整张位图,不重绘阴影。视觉不变。
will-change: transform;
backface-visibility: hidden;
&--1 {
width: 160rpx;
height: 160rpx;
margin-top: -80rpx;
margin-left: -80rpx;
border: 1rpx solid rgba($accent, 0.18);
animation: orbit-rotate 6s linear infinite;
}
&--2 {
width: 260rpx;
height: 260rpx;
margin-top: -130rpx;
margin-left: -130rpx;
border: 1rpx solid rgba($accent, 0.1);
animation: orbit-rotate 10s linear infinite reverse;
}
&--3 {
width: 360rpx;
height: 360rpx;
margin-top: -180rpx;
margin-left: -180rpx;
border: 1rpx solid rgba($accent, 0.06);
animation: orbit-rotate 16s linear infinite;
}
}
.splash-screen__particle {
position: absolute;
top: 50%;
left: 50%;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%,
#EEF2FF 0%,
$accent-light 40%,
$accent 100%);
&--1 {
width: 18rpx;
height: 18rpx;
margin-top: -9rpx;
margin-left: -9rpx;
transform: translateY(-80rpx);
box-shadow:
0 0 24rpx rgba($accent, 0.7),
0 0 8rpx rgba(255, 255, 255, 0.6);
}
&--2 {
width: 14rpx;
height: 14rpx;
margin-top: -7rpx;
margin-left: -7rpx;
transform: translateY(-130rpx);
box-shadow:
0 0 18rpx rgba($accent, 0.5),
0 0 4rpx rgba(255, 255, 255, 0.5);
}
&--3 {
width: 10rpx;
height: 10rpx;
margin-top: -5rpx;
margin-left: -5rpx;
transform: translateY(-180rpx);
box-shadow: 0 0 14rpx rgba($accent, 0.4);
}
}
@keyframes field-enter {
0% {
opacity: 0;
transform: scale(0.85);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes aura-breathe {
0%, 100% {
transform: scale(1);
opacity: 0.85;
}
50% {
transform: scale(1.08);
opacity: 1;
}
}
@keyframes core-pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
@keyframes halo-expand {
0%, 100% {
transform: scale(1);
opacity: 0.7;
}
50% {
transform: scale(1.5);
opacity: 0.3;
}
}
@keyframes orbit-rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>