tabbar适配安卓端

This commit is contained in:
future
2026-06-11 14:51:07 +08:00
parent c94be9bc38
commit c2a09a5057
9 changed files with 308 additions and 304 deletions
+57 -51
View File
@@ -3,75 +3,81 @@
<GlobalLoading />
</view>
</template>
<script>
import GlobalLoading from '@/components/global/GlobalLoading.vue'
import GlobalLoading from '@/components/global/GlobalLoading.vue'
export default {
components: {
GlobalLoading
},
onLaunch: function() {
console.log('App Launch')
this.preloadTabData()
},
onShow: function() {
console.log('App Show')
// #ifdef APP-PLUS
this.hideNativeTabBar()
// #endif
},
onHide: function() {
console.log('App Hide')
},
methods: {
// 隐藏原生 TabBar - 这里是核心修复
hideNativeTabBar() {
// 尝试多次执行,确保执行成功
const tryHide = (times = 0) => {
if (times > 10) return // 最多尝试10次
uni.hideTabBar({
animation: false,
success: () => {
console.log('✅ 原生TabBar隐藏成功')
// 强制 CSS 覆盖(双重保险)
this.forceCSSHide()
},
fail: (err) => {
console.log(`❌ 第${times+1}次隐藏失败,1秒后重试`, err)
setTimeout(() => tryHide(times + 1), 1000)
}
})
}
// 延迟 300ms 执行,确保页面挂载完成
setTimeout(() => tryHide(), 300)
},
// 强制 CSS 覆盖(最终保险)
forceCSSHide() {
// #ifdef APP-PLUS
const style = document.createElement('style')
style.innerHTML = `
uni-tabbar,
uni-tabbar .uni-tabbar,
.uni-tabbar,
uni-tabbar > .uni-tabbar,
[class*="uni-tabbar"] {
display: none !important;
height: 0 !important;
opacity: 0 !important;
visibility: hidden !important;
pointer-events: none !important;
}
`
document.head.appendChild(style)
console.log('✅ CSS 强制覆盖已注入')
// #endif
},
// 预加载所有 Tab 页面的核心数据
preloadTabData() {
// 延迟执行,不阻塞首屏
setTimeout(() => {
// 预加载课程数据
// #ifdef MP-WEIXIN
// 小程序端预请求数据
// #endif
// 预加载训练数据
// 预加载课程数据等...
}, 1000)
}
}
}
</script>
<style lang="scss">
@import 'common/style/base.css';
/* 全局骨架屏样式 */
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
}
@keyframes skeleton-loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* 页面切换动画 */
.page-enter-active,
.page-leave-active {
transition: opacity 0.2s ease, transform 0.2s ease;
}
.page-enter-from {
opacity: 0;
transform: translateX(30rpx);
}
.page-leave-to {
opacity: 0;
transform: translateX(-30rpx);
}
.app-container {
width: 100%;
min-height: 100vh;
max-width: 430px;
margin: 0 auto;
background-color: var(--bg-light);
position: relative;
overflow-x: hidden;
}
</style>
</script>
+60 -23
View File
@@ -1,21 +1,23 @@
<!-- components/TabBar.vue -->
<template>
<view v-if="shouldShowTabBar" class="tab-bar">
<view
v-for="(tab, index) in tabs"
:key="tab.path"
:class="['tab-item', { active: currentActiveIndex === index }]"
hover-class="tab-item--hover"
@tap.stop="onTabTap(index)"
>
<!-- 判断是否使用字体图标我的页面用字体其他用图片 -->
<text
v-if="tab.useFontIcon"
:class="['iconfont', tab.icon]"
class="tab-icon-font"
:style="{ fontSize: tab.fontSize}"
></text>
<text class="tab-label">{{ tab.label }}</text>
<view v-if="shouldShowTabBar" class="tab-bar-wrapper">
<view class="tab-bar">
<view
v-for="(tab, index) in tabs"
:key="tab.path"
:class="['tab-item', { active: currentActiveIndex === index }]"
hover-class="tab-item--hover"
@tap.stop="onTabTap(index)"
>
<!-- 判断是否使用字体图标我的页面用字体其他用图片 -->
<text
v-if="tab.useFontIcon"
:class="['iconfont', tab.icon]"
class="tab-icon-font"
:style="{ fontSize: tab.fontSize}"
></text>
<text class="tab-label">{{ tab.label }}</text>
</view>
</view>
</view>
</template>
@@ -98,15 +100,22 @@ function checkShouldShow() {
let routeWatcher = null
let appRouteCallback = null
let isNavigating = false
onMounted(() => {
syncActiveState()
checkShouldShow()
// #ifdef APP-PLUS
// #ifndef MP-WEIXIN
// H5和其他平台使用轮询监听路由变化
routeWatcher = setInterval(() => {
syncActiveState()
// 导航期间不更新状态,避免覆盖用户点击的索引
if (isNavigating) return
const newIndex = getActiveIndexFromRoute()
if (newIndex !== currentActiveIndex.value) {
currentActiveIndex.value = newIndex
}
checkShouldShow()
}, 300)
}, 200)
// #endif
// #ifdef MP-WEIXIN
if (typeof uni.onAppRoute === 'function') {
@@ -122,12 +131,17 @@ onMounted(() => {
})
onBeforeUnmount(() => {
// #ifdef APP-PLUS
if (routeWatcher) { clearInterval(routeWatcher) }
// #ifndef MP-WEIXIN
// H5和其他平台清理定时器
if (routeWatcher) {
clearInterval(routeWatcher)
routeWatcher = null
}
// #endif
// #ifdef MP-WEIXIN
if (appRouteCallback && typeof uni.offAppRoute === 'function') {
uni.offAppRoute(appRouteCallback)
appRouteCallback = null
}
// #endif
})
@@ -184,9 +198,12 @@ function onTabTap(index) {
const currentPath = TAB_ROUTES[currentActiveIndex.value]
if (targetPath === currentPath) return
console.log('Tab 点击:', index, targetPath)
// 立即更新状态
currentActiveIndex.value = index
emit('update:active', index)
emit('tab-change', index)
// 设置导航标志,阻止轮询覆盖状态
isNavigating = true
let timer = setTimeout(() => {
uni.showLoading({ title: '加载中...', mask: true })
}, 50)
@@ -203,7 +220,10 @@ function onTabTap(index) {
uni.hideLoading()
setTimeout(() => {
isSwitching = false
isNavigating = false
// #ifdef MP-WEIXIN
syncActiveState()
// #endif
checkShouldShow()
}, 100)
}
@@ -215,11 +235,21 @@ function onTabTap(index) {
// 引入字体图标 CSS(定义 @font-face
@import '/common/style/tabbar_icon/tabbar.css';
.tab-bar {
// 固定容器 - 确保TabBar始终在屏幕底部
.tab-bar-wrapper {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 9999;
pointer-events: none;
}
.tab-bar-wrapper .tab-bar {
pointer-events: auto;
}
.tab-bar {
height: 120rpx;
background: white;
backdrop-filter: blur(24px);
@@ -231,7 +261,14 @@ function onTabTap(index) {
padding-bottom: env(safe-area-inset-bottom);
box-shadow: 0 -4rpx 24rpx var(--tabbar-shadow);
border-radius: 32rpx 32rpx 0 0;
z-index: 999;
/* 防闪烁优化 */
transform: translateZ(0);
-webkit-transform: translateZ(0);
will-change: transform;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
perspective: 1000;
-webkit-perspective: 1000;
}
.tab-item {
+5 -3
View File
@@ -1,6 +1,6 @@
{
"name" : "gym-manage-uniapp",
"appid" : "__UNI__1F1874C",
"name" : "活氧舱",
"appid" : "__UNI__52E2F0D",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
@@ -41,7 +41,9 @@
]
},
/* ios */
"ios" : {},
"ios" : {
"dSYMs" : false
},
/* SDK */
"sdkConfigs" : {}
}
+1 -1
View File
@@ -284,7 +284,7 @@
},
"uniIdRouter": {},
"tabBar": {
"custom": true, // 启用自定义 tabBar
"custom": true,
"list": [
{ "pagePath": "pages/index/index", "text": "首页" },
{ "pagePath": "pages/course/index", "text": "课程" },
+50 -57
View File
@@ -1,40 +1,43 @@
<!-- pages/course/index.vue -->
<template>
<!-- <view class="bg-wrapper">
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/wave_top.png" mode="widthFix" class="wave-bg wave-top" />
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/wave_bottom.png" mode="widthFix" class="wave-bg wave-bottom" />
</view> -->
<view class="tab-page">
<view class="tab-page__header">
<text class="tab-page__title">课程</text>
<text class="tab-page__subtitle">精品团课 · 私教 · 线上课</text>
</view>
<!-- 骨架屏 -->
<view v-if="loading" class="skeleton-container">
<view class="skeleton-item" v-for="i in 3" :key="i">
<view class="skeleton-img"></view>
<view class="skeleton-text"></view>
<!-- 滚动内容区域 -->
<scroll-view scroll-y="false" class="scroll-container">
<view class="tab-page">
<view class="tab-page__header">
<text class="tab-page__title">课程</text>
<text class="tab-page__subtitle">精品团课 · 私教 · 线上课</text>
</view>
</view>
<!-- 真实内容 -->
<template v-else>
<RecommendCourses :data="courseData" />
<view class="tab-page__actions">
<view class="tab-page__btn" hover-class="tab-page__btn--hover" @tap="goCourseList">
<text class="tab-page__btn-text">预约课程</text>
</view>
<view class="tab-page__btn tab-page__btn--ghost" hover-class="tab-page__btn--hover" @tap="goMyCourses">
<text class="tab-page__btn-text tab-page__btn-text--ghost">我的课程</text>
<!-- 骨架屏 -->
<view v-if="loading" class="skeleton-container">
<view class="skeleton-item" v-for="i in 3" :key="i">
<view class="skeleton-img"></view>
<view class="skeleton-text"></view>
</view>
</view>
</template>
<view class="bottom-placeholder"></view>
</view>
<!-- 真实内容 -->
<template v-else>
<RecommendCourses :data="courseData" />
<view class="tab-page__actions">
<view class="tab-page__btn" hover-class="tab-page__btn--hover" @tap="goCourseList">
<text class="tab-page__btn-text">预约课程</text>
</view>
<view class="tab-page__btn tab-page__btn--ghost" hover-class="tab-page__btn--hover" @tap="goMyCourses">
<text class="tab-page__btn-text tab-page__btn-text--ghost">我的课程</text>
</view>
</view>
</template>
<view class="bottom-placeholder"></view>
</view>
</scroll-view>
<!-- 固定 TabBar -->
<view class="tabbar-fixed">
<TabBar @update:active="handleTabActive" />
</view>
</template>
<script setup>
@@ -128,34 +131,6 @@ function goMyCourses() {
</script>
<style lang="scss" scoped>
.bg-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
pointer-events: none;
}
.wave-bg {
position: fixed;
left: 0;
width: 100%;
pointer-events: none;
opacity: 0.6;
}
.wave-top {
top: 0;
opacity: 0.5;
}
.wave-bottom {
bottom: 100rpx;
opacity: 0.35;
}
.tab-page {
min-height: 100vh;
padding-bottom: 160rpx;
@@ -259,4 +234,22 @@ function goMyCourses() {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* 滚动容器 */
.scroll-container {
position: relative;
z-index: 1;
height: 100vh;
width: 100%;
}
/* 固定 TabBar */
.tabbar-fixed {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: transparent;
}
</style>
+49 -55
View File
@@ -1,34 +1,37 @@
<template>
<!-- <view class="bg-wrapper">
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/wave_top.png" mode="widthFix" class="wave-bg wave-top" />
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/wave_bottom.png" mode="widthFix" class="wave-bg wave-bottom" />
</view> -->
<view class="tab-page">
<view class="tab-page__header">
<text class="tab-page__title">发现</text>
<text class="tab-page__subtitle">活动 · 资讯 · 今日推荐</text>
<!-- 滚动内容区域 -->
<scroll-view scroll-y class="scroll-container">
<view class="tab-page">
<view class="tab-page__header">
<text class="tab-page__title">发现</text>
<text class="tab-page__subtitle">活动 · 资讯 · 今日推荐</text>
</view>
<TodayRecommend />
<view class="discover-links">
<view class="discover-link" hover-class="discover-link--hover" @tap="goReferral">
<text class="discover-link__title">邀请好友</text>
<text class="discover-link__desc">邀请注册/购课双方得积分</text>
</view>
<view class="discover-link" hover-class="discover-link--hover" @tap="goCouponCenter">
<text class="discover-link__title">领券中心</text>
<text class="discover-link__desc">限时优惠券先到先得</text>
</view>
<view class="discover-link" hover-class="discover-link--hover" @tap="goPointsMall">
<text class="discover-link__title">积分商城</text>
<text class="discover-link__desc">积分兑换好礼</text>
</view>
</view>
<view class="bottom-placeholder"></view>
</view>
<TodayRecommend />
<view class="discover-links">
<view class="discover-link" hover-class="discover-link--hover" @tap="goReferral">
<text class="discover-link__title">邀请好友</text>
<text class="discover-link__desc">邀请注册/购课双方得积分</text>
</view>
<view class="discover-link" hover-class="discover-link--hover" @tap="goCouponCenter">
<text class="discover-link__title">领券中心</text>
<text class="discover-link__desc">限时优惠券先到先得</text>
</view>
<view class="discover-link" hover-class="discover-link--hover" @tap="goPointsMall">
<text class="discover-link__title">积分商城</text>
<text class="discover-link__desc">积分兑换好礼</text>
</view>
</view>
<view class="bottom-placeholder"></view>
</view>
</scroll-view>
<!-- 固定 TabBar -->
<view class="tabbar-fixed">
<TabBar :active="3" />
</view>
</template>
<script setup>
@@ -50,33 +53,6 @@ function goPointsMall() {
</script>
<style lang="scss" scoped>
.bg-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
pointer-events: none;
}
.wave-bg {
position: fixed;
left: 0;
width: 100%;
pointer-events: none;
opacity: 0.6;
}
.wave-top {
top: 0;
opacity: 0.5;
}
.wave-bottom {
bottom: 100rpx;
opacity: 0.35;
}
.tab-page {
min-height: 100vh;
padding-bottom: 160rpx;
@@ -139,4 +115,22 @@ function goPointsMall() {
.bottom-placeholder {
height: 40rpx;
}
/* 滚动容器 */
.scroll-container {
position: relative;
z-index: 1;
height: 100vh;
width: 100%;
}
/* 固定 TabBar */
.tabbar-fixed {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: transparent;
}
</style>
+21 -57
View File
@@ -1,9 +1,4 @@
<template>
<!-- 水波纹背景 - 顶层显示 -->
<!-- <view class="bg-wrapper">
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/wave_top.png" mode="widthFix" class="wave-bg wave-top" />
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/wave_bottom.png" mode="widthFix" class="wave-bg wave-bottom" />
</view> -->
<!-- 固定白色块滚动时显示 -->
<view class="hand" :style="{height : handHeight + 'rpx'}" v-show="isShow"></view>
@@ -61,24 +56,11 @@ const handleScroll = (e) => {
const distance = e.detail.scrollTop
scrollDistance.value = distance
// 获取滚动容器的高度(可视区域高度)
const scrollViewHeight = e.detail.scrollHeight
scrollContentHeight.value = scrollViewHeight
console.log(`滚动距离: ${distance}`)
// 计算滚动百分比
// 滚动百分比 = 当前滚动距离 / (内容总高度 - 可视区域高度) * 100
const maxScrollDistance = scrollContentHeight.value - windowHeight.value
let scrollPercent = 0
if (maxScrollDistance > 0) {
scrollPercent = (distance / maxScrollDistance) * 100
}
console.log(`滚动距离: ${distance}, 滚动百分比: ${scrollPercent.toFixed(2)}%`)
// 当滚动超过 10% 时显示白色块(你可以调整这个百分比)
const SHOW_THRESHOLD = 40 // 10% 的阈值,可以根据需要调整
isShow.value = scrollPercent > SHOW_THRESHOLD
// 当滚动超过一定距离时显示白色块(200px = 400rpx
const SHOW_THRESHOLD = 50 // 滚动超过200px时显示白条
isShow.value = distance > SHOW_THRESHOLD
}
// 下拉刷新处理
@@ -118,16 +100,27 @@ onMounted(() => {
loading.value = false
}, 1500)
// 获取胶囊按钮高度
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
const navTotalHeight = menuButtonInfo.top + menuButtonInfo.height
handHeight.value = navTotalHeight * 2.5
// 获取可视窗口高度
// 获取系统信息
uni.getSystemInfo({
success: (res) => {
windowHeight.value = res.windowHeight
console.log('可视窗口高度:', windowHeight.value)
console.log('平台:', res.platform)
// #ifdef MP-WEIXIN
// 微信小程序使用胶囊按钮高度(保持原有逻辑)
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
const navTotalHeight = menuButtonInfo.top + menuButtonInfo.height
handHeight.value = navTotalHeight * 2.5
console.log('微信小程序胶囊按钮高度:', handHeight.value)
// #endif
// #ifndef MP-WEIXIN
// H5和安卓App只显示状态栏高度
const statusBarHeight = res.statusBarHeight || 0
handHeight.value = statusBarHeight * 2 // 转换为rpx1px = 2rpx
console.log('非微信小程序状态栏高度:', handHeight.value, 'rpx')
// #endif
}
})
@@ -145,35 +138,6 @@ onMounted(() => {
</script>
<style lang="scss" scoped>
/* 背景包装器 - 固定在最底层 */
.bg-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
pointer-events: none;
}
.wave-bg {
position: fixed;
left: 0;
width: 100%;
pointer-events: none;
opacity: 0.6;
}
.wave-top {
top: 0;
opacity: 0.5;
}
.wave-bottom {
bottom: 100rpx;
opacity: 0.35;
}
/* 滚动容器 */
.scroll-container {
position: relative;
@@ -1,5 +1,6 @@
<template>
<view class="scroll-container theme-light">
<!-- 滚动内容区域 -->
<scroll-view scroll-y class="scroll-container theme-light">
<view class="member-page">
<MemberInfoHeader
:user-info="userInfo"
@@ -44,7 +45,10 @@
</view>
</view>
</view>
<!-- TabBar -->
</scroll-view>
<!-- 固定 TabBar -->
<view class="tabbar-fixed">
<TabBar />
</view>
</template>
@@ -238,4 +242,14 @@ export default {
@import '@/common/style/memberInfo/member-info-referral.css';
@import '@/common/style/memberInfo/member-info-settings.css';
@import '@/common/style/memberInfo/member-info-logout.css';
/* 固定 TabBar */
.tabbar-fixed {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: transparent;
}
</style>
+49 -55
View File
@@ -1,36 +1,39 @@
<template>
<!-- <view class="bg-wrapper">
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/wave_top.png" mode="widthFix" class="wave-bg wave-top" />
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/wave_bottom.png" mode="widthFix" class="wave-bg wave-bottom" />
</view> -->
<view class="tab-page">
<view class="tab-page__header">
<text class="tab-page__title">训练</text>
<text class="tab-page__subtitle">记录每一次进步</text>
</view>
<!-- 滚动内容区域 -->
<scroll-view scroll-y class="scroll-container">
<view class="tab-page">
<view class="tab-page__header">
<text class="tab-page__title">训练</text>
<text class="tab-page__subtitle">记录每一次进步</text>
</view>
<view class="train-cards">
<view class="train-card" hover-class="train-card--hover" @tap="goTrainReport">
<text class="train-card__title">训练报告</text>
<text class="train-card__desc">查看本周/本月运动数据与趋势</text>
<view class="train-cards">
<view class="train-card" hover-class="train-card--hover" @tap="goTrainReport">
<text class="train-card__title">训练报告</text>
<text class="train-card__desc">查看本周/本月运动数据与趋势</text>
</view>
<view class="train-card" hover-class="train-card--hover" @tap="goBodyTest">
<text class="train-card__title">智能体测</text>
<text class="train-card__desc">连接设备获取专业体测报告</text>
</view>
<view class="train-card" hover-class="train-card--hover" @tap="goBodyHistory">
<text class="train-card__title">体测报告</text>
<text class="train-card__desc">历史记录与对比分析</text>
</view>
<view class="train-card" hover-class="train-card--hover" @tap="goCheckIn">
<text class="train-card__title">签到记录</text>
<text class="train-card__desc">到店打卡与训练频次</text>
</view>
</view>
<view class="train-card" hover-class="train-card--hover" @tap="goBodyTest">
<text class="train-card__title">智能体测</text>
<text class="train-card__desc">连接设备获取专业体测报告</text>
</view>
<view class="train-card" hover-class="train-card--hover" @tap="goBodyHistory">
<text class="train-card__title">体测报告</text>
<text class="train-card__desc">历史记录与对比分析</text>
</view>
<view class="train-card" hover-class="train-card--hover" @tap="goCheckIn">
<text class="train-card__title">签到记录</text>
<text class="train-card__desc">到店打卡与训练频次</text>
</view>
</view>
<view class="bottom-placeholder"></view>
</view>
<view class="bottom-placeholder"></view>
</view>
</scroll-view>
<!-- 固定 TabBar -->
<view class="tabbar-fixed">
<TabBar :active="2" />
</view>
</template>
<script setup>
@@ -55,33 +58,6 @@ function goCheckIn() {
</script>
<style lang="scss" scoped>
.bg-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2;
pointer-events: none;
}
.wave-bg {
position: fixed;
left: 0;
width: 100%;
pointer-events: none;
opacity: 0.6;
}
.wave-top {
top: 0;
opacity: 0.5;
}
.wave-bottom {
bottom: 100rpx;
opacity: 0.35;
}
.tab-page {
min-height: 100vh;
padding-bottom: 160rpx;
@@ -144,4 +120,22 @@ function goCheckIn() {
.bottom-placeholder {
height: 40rpx;
}
/* 滚动容器 */
.scroll-container {
position: relative;
z-index: 1;
height: 100vh;
width: 100%;
}
/* 固定 TabBar */
.tabbar-fixed {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: transparent;
}
</style>