Forráskód Böngészése

增加回到顶部功能

calm 1 hónapja
szülő
commit
a1118fd83d

+ 177 - 0
components/customTabBar/index.vue

@@ -0,0 +1,177 @@
+<template>
+  <view class="custom-tabbar" :style="{ paddingBottom: safeAreaBottom + 'px' }">
+    <view
+      class="tabbar-item"
+      v-for="(item, index) in tabList"
+      :key="index"
+      @click="handleTabClick(item, index)"
+    >
+      <view class="icon-wrapper">
+        <!-- 首页特殊处理:滚动后显示向上箭头 -->
+        <template v-if="index === 0 && showBackTop && currentIndex === 0">
+          <view class="back-top-icon">
+            <uni-icons type="arrow-up" size="24" color="#F8C008"></uni-icons>
+          </view>
+        </template>
+        <template v-else>
+          <image
+            :src="currentIndex === index ? item.selectedIconPath : item.iconPath"
+            mode="aspectFit"
+            class="icon-img"
+          />
+        </template>
+      </view>
+      <text :class="['tabbar-text', { active: currentIndex === index || (index === 0 && showBackTop && currentIndex === 0) }]">
+        {{ index === 0 && showBackTop && currentIndex === 0 ? '顶部' : item.text }}
+      </text>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+
+const props = defineProps({
+  // 当前选中的 tab 索引
+  current: {
+    type: Number,
+    default: 0
+  },
+  // 是否显示回到顶部(由首页传入)
+  showBackTop: {
+    type: Boolean,
+    default: false
+  }
+});
+
+const emit = defineEmits(['tabChange', 'backTop']);
+
+// tabBar 配置
+const tabList = ref([
+  {
+    pagePath: '/pages/index/index',
+    iconPath: '/static/images/tabbar/1-001.png',
+    selectedIconPath: '/static/images/tabbar/1-003.png',
+    text: '首页'
+  },
+  {
+    pagePath: '/pages/goods_cate/goods_cate',
+    iconPath: '/static/images/2-001.png',
+    selectedIconPath: '/static/images/2-003.png',
+    text: '分类'
+  },
+  {
+    pagePath: '/pages/order_addcart/order_addcart',
+    iconPath: '/static/images/tabbar/3-001.png',
+    selectedIconPath: '/static/images/tabbar/3-003.png',
+    text: '购物车'
+  },
+  {
+    pagePath: '/pages/user/index',
+    iconPath: '/static/images/tabbar/4-001.png',
+    selectedIconPath: '/static/images/tabbar/4-003.png',
+    text: '我的'
+  }
+]);
+
+const currentIndex = computed(() => props.current);
+
+// 安全区域底部高度
+const safeAreaBottom = ref(0);
+
+onMounted(() => {
+  const systemInfo = uni.getSystemInfoSync();
+  safeAreaBottom.value = systemInfo.safeAreaInsets?.bottom || 0;
+});
+
+// 处理 tab 点击
+const handleTabClick = (item, index) => {
+  // 如果是首页且显示回到顶部状态,触发回到顶部
+  if (index === 0 && props.showBackTop && currentIndex.value === 0) {
+    emit('backTop');
+    return;
+  }
+  
+  // 如果点击的是当前页,不做处理
+  if (index === currentIndex.value) {
+    return;
+  }
+  
+  // 切换 tab
+  emit('tabChange', index);
+  uni.switchTab({
+    url: item.pagePath
+  });
+};
+</script>
+
+<style lang="scss" scoped>
+.custom-tabbar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  height: 160rpx;
+  background-color: #ffffff;
+  border-top: 1rpx solid #e5e5e5;
+  z-index: 9999;
+  
+  .tabbar-item {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 10rpx 0;
+    
+    .icon-wrapper {
+      width: 50rpx;
+      height: 50rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      
+      .icon-img {
+        width: 50rpx;
+        height: 50rpx;
+      }
+      
+      .back-top-icon {
+        width: 50rpx;
+        height: 50rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        animation: bounce 0.5s ease-in-out;
+        
+        .icon-img {
+          width: 50rpx;
+          height: 50rpx;
+        }
+      }
+    }
+    
+    .tabbar-text {
+      font-size: 22rpx;
+      color: #999999;
+      margin-top: 6rpx;
+      
+      &.active {
+        color: #F8C008;
+      }
+    }
+  }
+}
+
+@keyframes bounce {
+  0%, 100% {
+    transform: translateY(0);
+  }
+  50% {
+    transform: translateY(-5rpx);
+  }
+}
+</style>

+ 6 - 1
pages/goods_cate/goods_cate.vue

@@ -45,6 +45,9 @@
         <view :style='"height:"+(height-300)+"rpx;"' v-if="number<15"></view>
       </scroll-view>
     </view>
+    
+    <!-- 自定义 tabBar -->
+    <customTabBar :current="1" :showBackTop="false" />
   </view>
 </template>
 
@@ -52,6 +55,7 @@
 import { ref, onMounted, nextTick } from 'vue';
 import { onLoad, onShow } from '@dcloudio/uni-app';
 import { productCategory } from '@/api/merchant.js';
+import customTabBar from "@/components/customTabBar/index.vue";
 
 // 响应式数据
 const productList = ref([]);
@@ -178,7 +182,8 @@ onShow(() => {
 
 // 页面显示
 onShow(() => {
-  // 可以在这里添加页面显示时的逻辑
+  // 隐藏原生 tabBar
+  uni.hideTabBar();
 });
 </script>
 

+ 35 - 1
pages/index/index.vue

@@ -16,6 +16,13 @@
       </template>
       <template #right></template>
     </up-navbar>
+    
+    <!-- 自定义 tabBar -->
+    <customTabBar 
+      :current="0" 
+      :showBackTop="showBackTop" 
+      @backTop="scrollToTop"
+    />
     <!-- <view class="page-index" :class="{ bgf: navIndex > 0 }"> -->
     <view class="page-index">
       <view class="top-box">
@@ -182,11 +189,14 @@ import { useStoreRights } from "@/stores/rights";
 import { useRealtimeTimestamp } from "@/utils/useRealtimeTimestamp.js";
 import { getUserLevelInfo } from "@/api/user";
 import { useAppStore } from "@/stores/app";
+import { useTabbarStore } from "@/stores/tabbar";
 import { goShopDetail } from "@/libs/order.js";
 import { HTTP_REQUEST_URL_IMG } from "@/config/app";
+import customTabBar from "@/components/customTabBar/index.vue";
 
 // 全局状态和时间
 const appStore = useAppStore();
+const tabbarStore = useTabbarStore();
 const rightsStore = useStoreRights();
 import {
   getGroomList as getGroomListApi,
@@ -235,6 +245,11 @@ const isFirstLoad = ref(true);
 
 const navBgColor = ref('rgba(255,255,255,0)');
 
+// 是否显示回到顶部
+const showBackTop = ref(false);
+// 滚动阈值
+const SCROLL_THRESHOLD = 300;
+
 const calculatedProducts = computed(() => {
   // 计算逻辑与原代码一致,但基于响应式数据 products
   return goodsList.value.map((product) => {
@@ -280,6 +295,9 @@ onLoad(async (options) => {
 
 });
 onShow(async () => {
+  // 隐藏原生 tabBar,使用自定义 tabBar
+  uni.hideTabBar();
+  
   const isLogin = appStore.isLogin;
   if(isLogin){
     rightsStore.getUserBenefits(appStore.userInfo?.userId);
@@ -507,7 +525,23 @@ onPageScroll((e) => {
   }else{
     navBgColor.value ='rgba(252,255,255,0)';
   }
+  
+  // 判断是否显示回到顶部
+  if (e.scrollTop > SCROLL_THRESHOLD) {
+    showBackTop.value = true;
+  } else {
+    showBackTop.value = false;
+  }
 })
+
+// 回到顶部
+const scrollToTop = () => {
+  uni.pageScrollTo({
+    scrollTop: 0,
+    duration: 350
+  });
+  showBackTop.value = false;
+};
 onReachBottom(() => {
   getGroomList();
 });
@@ -1091,7 +1125,7 @@ page {
 
 .index-product-wrapper {
   padding: 0 30rpx;
-  margin-bottom: 110rpx;
+  margin-bottom: 200rpx; /* 为自定义 tabBar 留出空间 */
   min-height: 700rpx;
   //background: #fff;
 

+ 7 - 0
pages/order_addcart/order_addcart.vue

@@ -343,6 +343,9 @@
     <!-- #ifdef MP -->
     <!-- <authorize :isAuto="isAuto" :isShowAuth="isShowAuth" @authColse="authColse"></authorize> -->
     <!-- #endif -->
+    
+    <!-- 自定义 tabBar -->
+    <customTabBar :current="2" :showBackTop="false" />
   </view>
 </template>
 
@@ -361,6 +364,7 @@ import { getProductHot, collectAll, getProductDetail } from "@/api/store.js";
 import { toLogin } from "@/libs/login.js";
 import recommend from "@/components/recommend";
 import productWindow from "@/components/productWindow";
+import customTabBar from "@/components/customTabBar/index.vue";
 import util from "@/utils/util";
 import { getPreOrder } from "@/libs/order";
 import { useToast } from "@/hooks/useToast";
@@ -429,6 +433,9 @@ onLoad((options) => {
 });
 
 onShow(() => {
+  // 隐藏原生 tabBar
+  uni.hideTabBar();
+  
   canShow.value = false;
   if (isLogin.value) {
     hotPage.value = 1;

+ 7 - 0
pages/user/index.vue

@@ -225,6 +225,9 @@
         </view>
       </view>
     </up-popup>
+    
+    <!-- 自定义 tabBar -->
+    <customTabBar :current="3" :showBackTop="false" />
   </view>
 </template>
 
@@ -240,6 +243,7 @@ import { getMetalBalance } from "@/api/vault";
 import { footprintList } from "@/api/merchant.js";
 import { getCustomerServiceList } from "@/api/user";
 import { toLogin } from "@/libs/login.js";
+import customTabBar from "@/components/customTabBar/index.vue";
 
 const showIcons = ref(false);
 const appStore = useAppStore();
@@ -371,6 +375,9 @@ const params = ref({
 
 // 页面加载
 onShow(async () => {
+  // 隐藏原生 tabBar
+  uni.hideTabBar();
+  
   if (appStore.isLogin) {
     await appStore.USERINFO();
     logoutShow.value = true;

+ 43 - 0
stores/tabbar.js

@@ -0,0 +1,43 @@
+import { defineStore } from 'pinia';
+import { ref } from 'vue';
+
+export const useTabbarStore = defineStore('tabbar', () => {
+  // 当前选中的 tab 索引
+  const currentIndex = ref(0);
+  
+  // 首页是否显示回到顶部
+  const showBackTop = ref(false);
+  
+  // 滚动阈值(超过这个值显示回到顶部)
+  const scrollThreshold = 300;
+  
+  // 设置当前 tab 索引
+  const setCurrentIndex = (index) => {
+    currentIndex.value = index;
+  };
+  
+  // 设置是否显示回到顶部
+  const setShowBackTop = (show) => {
+    showBackTop.value = show;
+  };
+  
+  // 根据页面路径获取 tab 索引
+  const getIndexByPath = (path) => {
+    const tabPaths = [
+      '/pages/index/index',
+      '/pages/goods_cate/goods_cate',
+      '/pages/order_addcart/order_addcart',
+      '/pages/user/index'
+    ];
+    return tabPaths.findIndex(p => path.includes(p.replace('/pages/', '')));
+  };
+  
+  return {
+    currentIndex,
+    showBackTop,
+    scrollThreshold,
+    setCurrentIndex,
+    setShowBackTop,
+    getIndexByPath
+  };
+});