- 移除动态主题切换功能,改为全局统一深色主题 - 实现自定义TabBar组件替换原生tabBar,解决iOS闪白问题 - 创建main容器页面统一管理三个tab页面的生命周期和状态 - 将所有页面的onShow/onHide等生命周期方法迁移到子组件内部 - 更新页面路径从index改为main,并调整路由跳转逻辑 - 移除theme-mixin和相关主题工具函数 - 统一页面背景色设置,优化用户体验一致性
148 lines
5.9 KiB
Vue
148 lines
5.9 KiB
Vue
<template>
|
||
<!-- 单页容器:三个 tab 页作为子组件常驻挂载,用 v-show 切换(只改 display),
|
||
不再走原生 tabBar 的 switchTab 页面过渡,从根源消除 iOS WKWebView 重新栅格化导致的闪白。
|
||
页面级生命周期(onLoad/onShow/onHide/onUnload/分享)由本容器统一接收,
|
||
再通过 ref 分发给当前活跃的子组件。 -->
|
||
<view class="main-page" :style="rootStyle">
|
||
<HomeTab v-show="current === 0" ref="home" @switch-tab="onTabChange" />
|
||
<CircleTab v-show="current === 1" ref="circle" @switch-tab="onTabChange" />
|
||
<ProfileTab v-show="current === 2" ref="profile" @switch-tab="onTabChange" />
|
||
|
||
<!-- 自绘底部导航:切换只改 current,不触发任何原生页面过渡 -->
|
||
<TabBar :current="current" @change="onTabChange" />
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import HomeTab from '../index/index.vue'
|
||
import CircleTab from '../circle/circle.vue'
|
||
import ProfileTab from '../profile/profile.vue'
|
||
import TabBar from '../../components/TabBar.vue'
|
||
|
||
// 三个子组件的 ref 名,与 tab 索引一一对应
|
||
const CHILD_REFS = ['home', 'circle', 'profile']
|
||
|
||
export default {
|
||
components: { HomeTab, CircleTab, ProfileTab, TabBar },
|
||
data() {
|
||
// 底部安全区(iPhone 全面屏手势条)
|
||
let safeBottom = 0
|
||
try {
|
||
const sys = uni.getSystemInfoSync()
|
||
if (sys.safeAreaInsets && typeof sys.safeAreaInsets.bottom === 'number') {
|
||
safeBottom = sys.safeAreaInsets.bottom
|
||
} else if (sys.safeArea && sys.windowHeight) {
|
||
safeBottom = Math.max(0, sys.windowHeight - sys.safeArea.bottom)
|
||
}
|
||
} catch (e) { safeBottom = 0 }
|
||
return {
|
||
current: 0,
|
||
// 自绘 tabBar 总高度(内容 100rpx + 安全区),经 CSS 变量下发给子组件计算底部留白/可视高度
|
||
tabBarHeight: `calc(100rpx + ${safeBottom}px)`,
|
||
// 首屏是否就绪(onReady 后置 true),用于区分首屏初始化与后续 onShow 刷新
|
||
inited: false,
|
||
// onLoad 参数,首屏就绪后转发给初始 tab(如 index 处理 inviteCode)
|
||
loadOptions: {},
|
||
// 各 tab 的页面级滚动位置记忆(home=0/circle=1/profile=2)。
|
||
// index/profile 共享 main 页面同一条滚动条,切换时须按 tab 记忆并恢复,否则位置互相串扰;
|
||
// circle 为内部 scroll-view 滚动,页面级滚动恒 0,其 scroll-view 位置由 v-show 常驻自动保留。
|
||
savedScroll: [0, 0, 0],
|
||
// tab 切换进行中标志:期间暂停滚动记录,避免 v-show 改变文档高度引发的 clamp 污染目标 tab 记忆
|
||
scrollSwitching: false
|
||
}
|
||
},
|
||
computed: {
|
||
rootStyle() {
|
||
return { '--tabbar-h': this.tabBarHeight }
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
this.loadOptions = options || {}
|
||
// 支持外部通过 ?tab=x 指定初始 tab(分享落地 / reLaunch 跳转)
|
||
const t = parseInt(this.loadOptions.tab, 10)
|
||
if (t >= 0 && t <= 2) this.current = t
|
||
},
|
||
onReady() {
|
||
// 子组件已挂载,初始化当前活跃 tab(首次加载数据 + 处理邀请码)
|
||
this.inited = true
|
||
const child = this.activeChild()
|
||
if (!child) return
|
||
if (child.pageLoad) child.pageLoad(this.loadOptions)
|
||
if (child.pageShow) child.pageShow()
|
||
},
|
||
onShow() {
|
||
// 首次 onShow 早于 onReady(子组件尚未挂载),跳过;
|
||
// 之后从其它页面返回时,刷新当前活跃 tab
|
||
if (!this.inited) return
|
||
const child = this.activeChild()
|
||
if (child && child.pageShow) child.pageShow()
|
||
},
|
||
onHide() {
|
||
const child = this.activeChild()
|
||
if (child && child.pageHide) child.pageHide()
|
||
},
|
||
onPageScroll(e) {
|
||
// 实时记录当前活跃 tab 的页面滚动位置,供切回时恢复;
|
||
// 切换进行中(scrollSwitching)忽略,避免文档高度变化引发的 clamp 污染记忆
|
||
if (this.scrollSwitching) return
|
||
this.savedScroll[this.current] = e.scrollTop
|
||
},
|
||
onUnload() {
|
||
// 页面卸载:通知所有子组件解绑(circle 需解绑 WebSocket 事件、清理定时器)
|
||
CHILD_REFS.forEach(name => {
|
||
const c = this.$refs[name]
|
||
if (c && c.pageUnload) c.pageUnload()
|
||
})
|
||
},
|
||
onShareAppMessage() {
|
||
const child = this.activeChild()
|
||
if (child && child.getShareAppMessage) {
|
||
const res = child.getShareAppMessage()
|
||
if (res) return res
|
||
}
|
||
return { title: '碰盏日记 - 让每一杯酒都有迹可循', path: '/pages/main/main?tab=0' }
|
||
},
|
||
onShareTimeline() {
|
||
const child = this.activeChild()
|
||
if (child && child.getShareTimeline) {
|
||
const res = child.getShareTimeline()
|
||
if (res) return res
|
||
}
|
||
return { title: '碰盏日记 - 让每一杯酒都有迹可循' }
|
||
},
|
||
methods: {
|
||
// 当前活跃 tab 对应的子组件实例
|
||
activeChild() {
|
||
return this.$refs[CHILD_REFS[this.current]]
|
||
},
|
||
// tab 切换:来自自绘 TabBar 的 @change 或子组件的 @switch-tab
|
||
onTabChange(i) {
|
||
if (i === this.current) return
|
||
const prev = this.activeChild()
|
||
// 进入切换态:暂停滚动记录,防止 v-show 改变文档高度引发的 clamp 污染记忆
|
||
this.scrollSwitching = true
|
||
this.current = i
|
||
// 旧 tab 转后台
|
||
if (prev && prev.pageHide) prev.pageHide()
|
||
// 新 tab 转前台(等 v-show 生效后再触发)
|
||
this.$nextTick(() => {
|
||
if (this.current !== i) return
|
||
// 恢复目标 tab 的页面滚动位置(circle 恒 0;index/profile 为各自记忆值)
|
||
uni.pageScrollTo({ scrollTop: this.savedScroll[i] || 0, duration: 0 })
|
||
const next = this.activeChild()
|
||
if (next && next.pageShow) next.pageShow()
|
||
// 待 pageScrollTo 引发的滚动事件平息后再恢复记录
|
||
setTimeout(() => { this.scrollSwitching = false }, 80)
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.main-page {
|
||
min-height: 100vh;
|
||
background-color: $bg-base;
|
||
}
|
||
</style>
|