1. 抽离底部导航归属逻辑为纯函数resolveEntryTab,优化分包页面高亮规则,对齐iOS HIG规范 2. 新增页面Tab通配符映射,简化PAGE_TAB_MAP维护 3. 修复鸿蒙平台webview返回按钮适配问题 4. 新增pages.json校验与导航标题自动注入脚本 5. 优化启动页动画性能,提升合成渲染效率 6. 修复diy-picture组件v-for缺少key的警告 7. 清理鸿蒙平台多余的分包构建配置 8. 更新统计数据与store注释说明
376 lines
8.5 KiB
Vue
376 lines
8.5 KiB
Vue
<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> |