Files
hejiu/pages/main/main.vue
T
cg d335ec290a refactor(app): 重构应用主题和页面结构
- 移除动态主题切换功能,改为全局统一深色主题
- 实现自定义TabBar组件替换原生tabBar,解决iOS闪白问题
- 创建main容器页面统一管理三个tab页面的生命周期和状态
- 将所有页面的onShow/onHide等生命周期方法迁移到子组件内部
- 更新页面路径从index改为main,并调整路由跳转逻辑
- 移除theme-mixin和相关主题工具函数
- 统一页面背景色设置,优化用户体验一致性
2026-09-23 20:56:14 +08:00

148 lines
5.9 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>
<!-- 单页容器:三个 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>