refactor(app): 重构应用主题和页面结构

- 移除动态主题切换功能,改为全局统一深色主题
- 实现自定义TabBar组件替换原生tabBar,解决iOS闪白问题
- 创建main容器页面统一管理三个tab页面的生命周期和状态
- 将所有页面的onShow/onHide等生命周期方法迁移到子组件内部
- 更新页面路径从index改为main,并调整路由跳转逻辑
- 移除theme-mixin和相关主题工具函数
- 统一页面背景色设置,优化用户体验一致性
This commit is contained in:
cg
2026-09-23 20:56:14 +08:00
parent a628b73a80
commit d335ec290a
24 changed files with 399 additions and 357 deletions
+2 -5
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container agreement-page">
<view class="page-container agreement-page">
<!-- 自定义导航栏(避让胶囊) -->
<view class="agree-nav" :style="{ paddingTop: navPaddingTop }">
<view class="agree-nav-row" :style="{ paddingRight: navPaddingRight }">
@@ -27,10 +27,7 @@
</template>
<script>
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
data() {
const menuBtn = uni.getMenuButtonBoundingClientRect()
const sysInfo = uni.getSystemInfoSync()
@@ -57,7 +54,7 @@ export default {
if (pages.length > 1) {
uni.navigateBack()
} else {
uni.switchTab({ url: '/pages/index/index' })
uni.reLaunch({ url: '/pages/main/main?tab=0' })
}
},
userSections() {
+5 -7
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container card-page">
<view class="page-container card-page">
<view class="card-nav" :style="{ paddingTop: (statusBarHeight + 12) + 'px' }">
<view class="step-back" @click="goBack">
<text class="step-back-arrow">‹</text>
@@ -42,12 +42,10 @@ import DrinkCard from '../../components/DrinkCard.vue'
import client from '../../common/api'
import { calcStandardCups, unitToMl } from '../../common/utils'
import { DRINK_CATEGORIES, FOOD_CATEGORIES, DRINK_QUOTES, FEELINGS } from '../../common/constants'
import themeMixin from '../../common/theme-mixin'
const UNIT_NAMES = { ml: 'ml', liang: '两', bottle: '瓶', cup: '杯', can: '听', shot: 'shot' }
export default {
mixins: [themeMixin],
components: { DrinkCard },
data() {
const sysInfo = uni.getSystemInfoSync()
@@ -761,7 +759,7 @@ export default {
})
},
closeToHome() {
uni.switchTab({ url: '/pages/index/index' })
uni.reLaunch({ url: '/pages/main/main?tab=0' })
},
_doBack() {
console.log('_doBack called, pages:', getCurrentPages().length)
@@ -769,12 +767,12 @@ export default {
uni.navigateBack({
fail: (err) => {
console.error('navigateBack fail:', err)
uni.reLaunch({ url: '/pages/index/index' })
uni.reLaunch({ url: '/pages/main/main?tab=0' })
}
})
} else {
console.log('no pages to go back to, reLaunch to index')
uni.reLaunch({ url: '/pages/index/index' })
uni.reLaunch({ url: '/pages/main/main?tab=0' })
}
}
},
@@ -788,7 +786,7 @@ export default {
: '今晚的酒局记录 - 碰盏日记'
return {
title,
path: '/pages/index/index',
path: '/pages/main/main?tab=0',
imageUrl: this.posterPath || undefined
}
},
+2 -4
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container chatlist-page">
<view class="page-container chatlist-page">
<!-- 自定义导航栏 -->
<view class="chatlist-nav" :style="{ paddingTop: headerPaddingTop }">
<view class="chatlist-nav-inner">
@@ -68,11 +68,9 @@
import EmptyState from '../../components/EmptyState.vue'
import { GetConversations } from '../../common/api'
import wsManager from '../../common/websocket'
import themeMixin from '../../common/theme-mixin'
import { isValidAvatar } from '../../common/utils'
export default {
mixins: [themeMixin],
components: { EmptyState },
data() {
const menuBtn = uni.getMenuButtonBoundingClientRect()
@@ -119,7 +117,7 @@ export default {
uni.navigateBack()
},
goCircle() {
uni.switchTab({ url: '/pages/circle/circle' })
uni.reLaunch({ url: '/pages/main/main?tab=1' })
},
// === WebSocket 实时更新 ===
+1 -3
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container chat-page">
<view class="page-container chat-page">
<!-- 自定义导航栏 -->
<view class="chat-nav" :style="{ paddingTop: headerPaddingTop }">
<view class="chat-nav-inner">
@@ -121,10 +121,8 @@
<script>
import { GetMessages, MarkRead, GetConversations } from '../../common/api'
import wsManager from '../../common/websocket'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
data() {
const menuBtn = uni.getMenuButtonBoundingClientRect()
return {
+4 -12
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container detail-page">
<view class="page-container detail-page">
<!-- 顶部导航 -->
<view class="detail-nav" :style="{ paddingTop: navPaddingTop }">
<view class="detail-nav-inner">
@@ -66,10 +66,8 @@ import FeedCard from '../../components/FeedCard.vue'
import CommentItem from '../../components/CommentItem.vue'
import EmptyState from '../../components/EmptyState.vue'
import { GetFeedComments, AddComment, LikeFeed, UnlikeFeed, DeleteFeed, requireLogin } from '../../common/api'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
components: { FeedCard, CommentItem, EmptyState },
data() {
const menuBtn = uni.getMenuButtonBoundingClientRect()
@@ -98,17 +96,11 @@ export default {
if (getCurrentPages().length > 1) {
uni.navigateBack({
fail: () => {
uni.switchTab({
url: '/pages/index/index',
fail: () => uni.reLaunch({ url: '/pages/index/index' })
})
uni.reLaunch({ url: '/pages/main/main?tab=0' })
}
})
} else {
uni.switchTab({
url: '/pages/index/index',
fail: () => uni.reLaunch({ url: '/pages/index/index' })
})
uni.reLaunch({ url: '/pages/main/main?tab=0' })
}
},
async loadFeed() {
@@ -219,7 +211,7 @@ export default {
}
return {
title: '酒友圈 - 碰盏日记',
path: '/pages/index/index'
path: '/pages/main/main?tab=0'
}
}
}
+1 -3
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container publish-page">
<view class="page-container publish-page">
<!-- 顶部导航 -->
<view class="publish-nav" :style="{ paddingTop: navPaddingTop, paddingRight: navPaddingRight }">
<view class="publish-nav-inner">
@@ -81,10 +81,8 @@
<script>
import { PublishFeed, UploadImage } from '../../common/api'
import client from '../../common/api'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
data() {
const menuBtn = uni.getMenuButtonBoundingClientRect()
const sysInfo = uni.getSystemInfoSync()
+31 -32
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container circle-page">
<view class="page-container circle-page">
<!-- 顶部导航 -->
<view class="circle-header" :style="{ paddingTop: headerPaddingTop }">
<view class="circle-title-row" :style="{ paddingRight: headerPaddingRight }">
@@ -81,10 +81,8 @@ import EmptyState from '../../components/EmptyState.vue'
import { GetCircleFeeds, LikeFeed, UnlikeFeed, DeleteFeed, GetFriends, GetFriendRequests, GetEvents, DeleteEvent, GetUnreadCount, isLoggedIn, requireLogin } from '../../common/api'
import wsManager from '../../common/websocket'
import { getMyInviteCode } from '../../common/invite'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
components: { FeedCard, FriendItem, EventCard, EmptyState },
data() {
const menuBtn = uni.getMenuButtonBoundingClientRect()
@@ -119,33 +117,34 @@ export default {
isGuest: false
}
},
onShow() {
this.isGuest = !isLoggedIn()
if (this.isGuest) {
// 游客态不发起鉴权请求,展示空态与登录引导
this.feeds = []
this.loading = false
} else {
this.loadFeeds(true)
}
// 消息入口已隐藏,未读角标暂不展示,空转请求先停掉(恢复入口时打开)
// this.loadUnread()
// this.bindWsEvents()
// 读取当前用户ID(删除自己的动态时用)
try {
const user = JSON.parse(uni.getStorageSync('user_info') || '{}')
this.myUserId = user.id !== undefined && user.id !== null ? String(user.id) : ''
} catch (e) { this.myUserId = '' }
// 页面显示时确保 WS 已连接(登录后/断线后兼容)
wsManager.connect()
},
onHide() {
this.unbindWsEvents()
},
onUnload() {
this.unbindWsEvents()
},
methods: {
// === 子组件生命周期(由 main.vue 容器通过 ref 调用)===
pageShow() {
this.isGuest = !isLoggedIn()
if (this.isGuest) {
// 游客态不发起鉴权请求,展示空态与登录引导
this.feeds = []
this.loading = false
} else {
this.loadFeeds(true)
}
// 消息入口已隐藏,未读角标暂不展示,空转请求先停掉(恢复入口时打开)
// this.loadUnread()
// this.bindWsEvents()
// 读取当前用户ID(删除自己的动态时用)
try {
const user = JSON.parse(uni.getStorageSync('user_info') || '{}')
this.myUserId = user.id !== undefined && user.id !== null ? String(user.id) : ''
} catch (e) { this.myUserId = '' }
// 页面显示时确保 WS 已连接(登录后/断线后兼容)
wsManager.connect()
},
pageHide() {
this.unbindWsEvents()
},
pageUnload() {
this.unbindWsEvents()
},
// === WebSocket 事件 ===
bindWsEvents() {
if (this._wsBound) return
@@ -380,7 +379,7 @@ export default {
const parts = []
if (this.inviteCode) parts.push(`inviteCode=${encodeURIComponent(this.inviteCode)}`)
if (user.nickname) parts.push(`inviterNick=${encodeURIComponent(user.nickname)}`)
const query = parts.length ? `?${parts.join('&')}` : ''
const query = parts.length ? `&${parts.join('&')}` : ''
const nick = user.nickname ? `「${user.nickname}」` : ''
// 本次分享已用掉当前邀请码,异步生成新码供下次使用
this.inviteCode = ''
@@ -388,7 +387,7 @@ export default {
this.shareMode = 'default'
return {
title: `${nick}邀请你成为酒友,一起记录饮酒生活 🍻`,
path: `/pages/index/index${query}`
path: `/pages/main/main?tab=0${query}`
}
}
const feed = this.shareFeed
@@ -405,7 +404,7 @@ export default {
}
return {
title: '酒友圈 - 碰盏日记',
path: '/pages/index/index'
path: '/pages/main/main?tab=1'
}
},
onShareTimeline() {
+1 -3
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container create-page">
<view class="page-container create-page">
<!-- 顶部导航 -->
<view class="create-nav" :style="{ paddingTop: navPaddingTop }">
<view class="create-nav-inner">
@@ -109,10 +109,8 @@
<script>
import { CreateEvent, UpdateEvent, GetEventDetail } from '../../common/api'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
data() {
const menuBtn = uni.getMenuButtonBoundingClientRect()
const now = new Date()
+1 -3
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container evt-detail-page">
<view class="page-container evt-detail-page">
<!-- 顶部导航 -->
<view class="evt-nav" :style="{ paddingTop: navPaddingTop }">
<view class="evt-nav-inner">
@@ -169,10 +169,8 @@
<script>
import { GetEventDetail, JoinEvent, QuitEvent, CheckInEvent, DeleteEvent, ApproveEvent } from '../../common/api'
import wsManager from '../../common/websocket'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
data() {
const menuBtn = uni.getMenuButtonBoundingClientRect()
return {
+3 -5
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container friends-page">
<view class="page-container friends-page">
<!-- 顶部导航 -->
<view class="friends-nav" :style="{ paddingTop: navPaddingTop }">
<view class="friends-nav-inner">
@@ -96,10 +96,8 @@ import FriendItem from '../../components/FriendItem.vue'
import EmptyState from '../../components/EmptyState.vue'
import { GetFriends, GetFriendRequests, AcceptFriendRequest, RemoveFriend } from '../../common/api'
import { savePendingInvite, handlePendingInvite, getMyInviteCode } from '../../common/invite'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
components: { FriendItem, EmptyState },
data() {
const menuBtn = uni.getMenuButtonBoundingClientRect()
@@ -204,13 +202,13 @@ export default {
const parts = []
if (this.inviteCode) parts.push(`inviteCode=${encodeURIComponent(this.inviteCode)}`)
if (user.nickname) parts.push(`inviterNick=${encodeURIComponent(user.nickname)}`)
const query = parts.length ? `?${parts.join('&')}` : ''
const query = parts.length ? `&${parts.join('&')}` : ''
const nick = user.nickname ? `「${user.nickname}」` : ''
// 本次分享已用掉当前邀请码,异步生成新码供下次分享
this.refreshInviteCode()
return {
title: `${nick}邀请你成为酒友,一起记录饮酒生活 🍻`,
path: `/pages/index/index${query}`
path: `/pages/main/main?tab=0${query}`
}
}
}
+1 -4
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container games-page">
<view class="page-container games-page">
<!-- 顶部导航 -->
<view class="games-nav" :style="{ paddingTop: navPaddingTop }">
<view class="nav-back" @click="goBack">
@@ -259,10 +259,7 @@
</template>
<script>
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
data() {
let navPaddingTop = '20px'
try {
+39 -40
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container home">
<view class="page-container home">
<!-- 顶部问候 -->
<view class="greeting-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="greeting-left">
@@ -153,10 +153,8 @@ import { GetCalendarReq, DeleteRecordReq, CreateRecordReq, GetRecordDetailReq }
import { getGreeting, formatDate, getWeekStart, getCatIcon, isIconPath, calcLocalStreak } from '../../common/utils'
import { FEELINGS, DRINK_CATEGORIES, DRINK_UNITS } from '../../common/constants'
import { savePendingInvite, handlePendingInvite } from '../../common/invite'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
components: { DrinkCalendar },
data() {
const now = new Date()
@@ -189,44 +187,45 @@ export default {
return this.selectedDate === formatDate(new Date(), 'YYYY-MM-DD')
}
},
onLoad(options) {
// 分享邀请落地:携带 inviteCode 时暂存邀请,已登录则立即接受邀请
if (options && options.inviteCode) {
savePendingInvite(options.inviteCode, options.inviterNick)
if (uni.getStorageSync('is_logged_in') === 'true') {
handlePendingInvite()
}
}
},
onShow() {
// 首次启动跳转引导页由 App.vue onLaunch 统一处理,
// 这里不再重复 reLaunch,避免两个跳转并发导致 reLaunch:fail timeout
const isFirst = uni.getStorageSync('is_first_launch')
if (isFirst === 'true') {
return
}
// 游客可直接浏览首页:不发起鉴权请求,展示空数据;
// 仅在用户主动点击记录等功能时才引导登录(微信审核要求:先浏览后登录)
this.isGuest = !isLoggedIn()
if (this.isGuest) {
this.initGuestView()
return
}
this.loadData()
},
onShareAppMessage() {
return {
title: '碰盏日记 - 让每一杯酒都有迹可循',
path: '/pages/index/index'
}
},
onShareTimeline() {
return {
title: '碰盏日记 - 让每一杯酒都有迹可循',
query: ''
}
},
methods: {
// === 子组件生命周期(由 main.vue 容器通过 ref 调用)===
pageLoad(options) {
// 分享邀请落地:携带 inviteCode 时暂存邀请,已登录则立即接受邀请
if (options && options.inviteCode) {
savePendingInvite(options.inviteCode, options.inviterNick)
if (uni.getStorageSync('is_logged_in') === 'true') {
handlePendingInvite()
}
}
},
pageShow() {
// 首次启动跳转引导页由 App.vue onLaunch 统一处理,
// 这里不再重复 reLaunch,避免两个跳转并发导致 reLaunch:fail timeout
const isFirst = uni.getStorageSync('is_first_launch')
if (isFirst === 'true') {
return
}
// 游客可直接浏览首页:不发起鉴权请求,展示空数据;
// 仅在用户主动点击记录等功能时才引导登录(微信审核要求:先浏览后登录)
this.isGuest = !isLoggedIn()
if (this.isGuest) {
this.initGuestView()
return
}
this.loadData()
},
getShareAppMessage() {
return {
title: '碰盏日记 - 让每一杯酒都有迹可循',
path: '/pages/main/main?tab=0'
}
},
getShareTimeline() {
return {
title: '碰盏日记 - 让每一杯酒都有迹可循',
query: 'tab=0'
}
},
// 游客浏览视图:问候语与零数据,页面结构可完整浏览,不发起任何鉴权请求
initGuestView() {
this.greeting = getGreeting()
+4 -6
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container login-page">
<view class="page-container login-page">
<!-- 顶部品牌区 -->
<view class="brand-area">
<view class="brand-glow"></view>
@@ -94,13 +94,11 @@
</template>
<script>
import themeMixin from '../../common/theme-mixin'
import client, { saveAuthTokens, UploadImage, UpdateUserAvatar } from '../../common/api'
import { handlePendingInvite } from '../../common/invite'
import wsManager from '../../common/websocket'
export default {
mixins: [themeMixin],
data() {
return {
showNotice: true,
@@ -114,7 +112,7 @@ export default {
onShareAppMessage() {
return {
title: '碰盏日记 - 让每一杯酒都有迹可循',
path: '/pages/index/index'
path: '/pages/main/main?tab=0'
}
},
onShareTimeline() {
@@ -132,7 +130,7 @@ export default {
enterGuest() {
uni.setStorageSync('is_first_launch', 'false')
uni.setStorageSync('is_guest', 'true')
uni.switchTab({ url: '/pages/index/index' })
uni.reLaunch({ url: '/pages/main/main?tab=0' })
},
// ===== 微信新规范:头像选择 =====
@@ -282,7 +280,7 @@ export default {
} catch (e) {
console.warn('补发邀请失败,不影响登录:', e)
}
uni.switchTab({ url: '/pages/index/index' })
uni.reLaunch({ url: '/pages/main/main?tab=0' })
},
// ===== 协议查看:跳转协议页(user=用户协议 / privacy=隐私政策)=====
+147
View File
@@ -0,0 +1,147 @@
<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>
+3 -6
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container onboarding">
<view class="page-container onboarding">
<!-- 跳过按钮 -->
<view class="skip-bar">
<text class="btn-text" @click="goLogin">跳过</text>
@@ -63,10 +63,7 @@
</template>
<script>
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
data() {
return {
currentSlide: 0,
@@ -101,7 +98,7 @@ export default {
onShareAppMessage() {
return {
title: '碰盏日记 - 让每一杯酒都有迹可循',
path: '/pages/index/index'
path: '/pages/main/main?tab=0'
}
},
onShareTimeline() {
@@ -121,7 +118,7 @@ export default {
},
goLogin() {
uni.setStorageSync('is_first_launch', 'false')
uni.reLaunch({ url: '/pages/index/index' })
uni.reLaunch({ url: '/pages/main/main' })
}
}
}
+24 -23
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container profile-page">
<view class="page-container profile-page">
<!-- 沉浸式头部 -->
<view class="hero-header" :style="{ paddingTop: headerPaddingTop }">
<view class="hero-bg">
@@ -176,11 +176,11 @@ import AchievementBadge from '../../components/AchievementBadge.vue'
import client, { clearAuth, GetAchievements, isLoggedIn, requireLogin } from '../../common/api'
import { checkAchievements, getCatIcon, isIconPath, calcLocalStreak, isValidAvatar } from '../../common/utils'
import { ACHIEVEMENTS, DRINK_CATEGORIES } from '../../common/constants'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
components: { AchievementBadge },
// Vue3 组件事件必须显式声明
emits: ['switch-tab'],
data() {
// 获取胶囊按钮位置,动态计算分享按钮偏移和头部内边距
const sysInfo = uni.getSystemInfoSync()
@@ -252,25 +252,26 @@ export default {
return '百天老友,酒中见真章'
}
},
onShow() {
this.checkLoginState()
this.loadData()
},
onShareAppMessage() {
const name = this.user.nickname || '我'
return {
title: `${name}的饮酒名片 - 碰盏日记`,
path: `/pages/share-profile/share-profile?${this.buildShareQuery()}`
}
},
onShareTimeline() {
const name = this.user.nickname || '我'
return {
title: `${name}的饮酒名片 - 碰盏日记`,
query: this.buildShareQuery()
}
},
methods: {
// === 子组件生命周期(由 main.vue 容器通过 ref 调用)===
pageShow() {
this.checkLoginState()
this.loadData()
},
getShareAppMessage() {
const name = this.user.nickname || '我'
return {
title: `${name}的饮酒名片 - 碰盏日记`,
path: `/pages/share-profile/share-profile?${this.buildShareQuery()}`
}
},
getShareTimeline() {
const name = this.user.nickname || '我'
return {
title: `${name}的饮酒名片 - 碰盏日记`,
query: this.buildShareQuery()
}
},
isValidAvatar,
isIconPath,
// 构建分享名片的 query 参数(携带公开档案数据)
@@ -382,13 +383,13 @@ export default {
return ACHIEVEMENTS[index % ACHIEVEMENTS.length].icon
},
goCircle() {
uni.switchTab({ url: '/pages/circle/circle' })
this.$emit('switch-tab', 1)
},
goGames() {
uni.navigateTo({ url: '/pages/games/games' })
},
goHome() {
uni.switchTab({ url: '/pages/index/index' })
this.$emit('switch-tab', 0)
},
goRecord() {
if (!requireLogin()) return
+3 -5
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container record-page safe-bottom">
<view class="page-container record-page safe-bottom">
<!-- 顶部进度 -->
<view class="step-header" :style="{ paddingTop: (statusBarHeight + 12) + 'px' }">
<view class="step-back" @click="handleBack">
@@ -347,10 +347,8 @@ import {
} from '../../common/constants'
import { calcStandardCups, unitToMl, formatDate, getCatIcon, isIconPath } from '../../common/utils'
import client, { isLoggedIn } from '../../common/api'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
data() {
const sysInfo = uni.getSystemInfoSync()
return {
@@ -458,7 +456,7 @@ export default {
} else {
uni.navigateBack({
fail: () => {
uni.switchTab({ url: '/pages/index/index' })
uni.reLaunch({ url: '/pages/main/main?tab=0' })
}
})
}
@@ -469,7 +467,7 @@ export default {
onShareAppMessage() {
return {
title: '碰盏日记 - 记录每一杯酒',
path: '/pages/index/index'
path: '/pages/main/main?tab=0'
}
},
onShareTimeline() {
+3 -8
View File
@@ -1,5 +1,5 @@
<template>
<view :class="themeClass" class="page-container share-page">
<view class="page-container share-page">
<!-- 氛围背景 -->
<view class="ambient">
<view class="glow glow-1"></view>
@@ -80,11 +80,9 @@
</template>
<script>
import themeMixin from '../../common/theme-mixin'
import { DRINK_CATEGORIES } from '../../common/constants'
export default {
mixins: [themeMixin],
data() {
return {
info: {
@@ -178,15 +176,12 @@ export default {
enterApp() {
const isLoggedIn = uni.getStorageSync('is_logged_in')
if (isLoggedIn === 'true') {
uni.switchTab({ url: '/pages/index/index' })
uni.reLaunch({ url: '/pages/main/main?tab=0' })
} else {
// 未登录以游客身份进入首页浏览,不强制跳登录页
uni.setStorageSync('is_first_launch', 'false')
uni.setStorageSync('is_guest', 'true')
uni.switchTab({
url: '/pages/index/index',
fail: () => uni.reLaunch({ url: '/pages/index/index' })
})
uni.reLaunch({ url: '/pages/main/main?tab=0' })
}
}
}