Pārlūkot izejas kodu

✨ feat(tabbar): 实现底部导航栏功能

陈雪 3 nedēļas atpakaļ
vecāks
revīzija
fccdd017bf

+ 5 - 5
pages.config.ts

@@ -30,31 +30,31 @@ export default defineUniPages({
       {
         icon: '/static/tabbar/home.png',
         pagePath: 'pages/index/index',
-        text: '%tabbar.home%',
+        text: 'tabbar.home',
         iconType: 'local',
       },
       {
         icon: '/static/tabbar/category.png',
         pagePath: 'pages/category/category',
-        text: '%tabbar.category%',
+        text: 'tabbar.category',
         iconType: 'local',
       },
       {
         icon: '/static/tabbar/cart.png',
         pagePath: 'pages/cart/cart',
-        text: '%tabbar.cart%',
+        text: 'tabbar.cart',
         iconType: 'local',
       },
       {
         icon: '/static/tabbar/community.png',
         pagePath: 'pages/community/community',
-        text: '%tabbar.community%',
+        text: 'tabbar.community',
         iconType: 'local',
       },
       {
         icon: '/static/tabbar/person.png',
         pagePath: 'pages/person/person',
-        text: '%tabbar.person%',
+        text: 'tabbar.person',
         iconType: 'local',
       },
     ],

+ 181 - 0
src/components/shop-tabbar/shop-tabbar.vue

@@ -0,0 +1,181 @@
+<template>
+  <view class="shop-tabbar-container">
+    <view
+      class="tabbar-item"
+      @click="selectTabBar(category.path, 1)"
+      :class="{ 'tabbar-active': tabbarStore.curIdx === 1 }"
+    >
+      <view class="tabbar-item-wrapper">
+        <image :src="category.icon" class="tabbar-item-img" mode="widthFix"></image>
+      </view>
+      <view class="tabbar-item-text">{{ category.text }}</view>
+    </view>
+    <view
+      class="tabbar-item"
+      @click="selectTabBar(cate.path, 2)"
+      :class="{ 'tabbar-active': tabbarStore.curIdx === 2 }"
+    >
+      <view class="tabbar-item-wrapper">
+        <image :src="cate.icon" class="tabbar-item-img" mode="widthFix"></image>
+      </view>
+      <view class="tabbar-item-text">{{ cate.text }}</view>
+    </view>
+    <view
+      class="home-item"
+      @click="selectTabBar(home.path, 0)"
+      :class="{ 'home-active': tabbarStore.curIdx === 0 }"
+    >
+      <view class="home-item-wrapper">
+        <image :src="home.icon" class="home-item-img" mode="widthFix"></image>
+      </view>
+      <view class="home-item-text">{{ home.text }}</view>
+    </view>
+    <view
+      class="tabbar-item"
+      @click="selectTabBar(community.path, 3)"
+      :class="{ 'tabbar-active': tabbarStore.curIdx === 3 }"
+    >
+      <view class="tabbar-item-wrapper">
+        <image :src="community.icon" class="tabbar-item-img" mode="widthFix"></image>
+      </view>
+      <view class="tabbar-item-text">{{ community.text }}</view>
+    </view>
+    <view
+      class="tabbar-item"
+      @click="selectTabBar(person.path, 4)"
+      :class="{ 'tabbar-active': tabbarStore.curIdx === 4 }"
+    >
+      <view class="tabbar-item-wrapper">
+        <image :src="person.icon" class="tabbar-item-img" mode="widthFix"></image>
+      </view>
+      <view class="tabbar-item-text">{{ person.text }}</view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+  import { tabBar } from '@/pages.json'
+  import { t } from '@/locale'
+  import { useTabbarStore } from '@/store/tabbar'
+
+  const tabbarStore = useTabbarStore()
+
+  /** tabbarList 里面的 path 从 pages.config.ts 得到 */
+  const [home, category, cate, community, person] = tabBar.list.map((item) => ({
+    ...item,
+    path: `/${item.pagePath}`,
+    text: t(item.text),
+  }))
+
+  function selectTabBar(url: string, index: number) {
+    tabbarStore.setCurIdx(index)
+    uni.switchTab({ url })
+  }
+
+  onLoad(() => {
+    // #ifdef APP-PLUS | H5
+    uni.hideTabBar({
+      fail(err) {
+        console.log('hideTabBar fail: ', err)
+      },
+      success(res) {
+        console.log('hideTabBar success: ', res)
+      },
+    })
+    // #endif
+  })
+</script>
+<style lang="scss" scoped>
+  .shop-tabbar-container {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    display: flex;
+    justify-content: space-between;
+    width: 100vw;
+    height: 144rpx;
+    overflow: hidden;
+    background-color: transparent;
+    background-image: url(@/static/images/tabbar-bg.svg);
+    background-size: cover;
+
+    .tabbar-item {
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-end;
+      width: 110rpx;
+      text-align: center;
+
+      .tabbar-item-text {
+        font-family: 'PingFang SC';
+        font-size: 20rpx;
+        font-style: normal;
+        font-weight: 600;
+        line-height: 30rpx; /* 180% */
+        color: var(--333, #333);
+      }
+
+      .tabbar-item-wrapper {
+        width: 66rpx;
+        height: 66rpx;
+        margin: 0 auto;
+        border-radius: 50%;
+
+        .tabbar-item-img {
+          width: 56rpx;
+          margin: 5rpx auto;
+        }
+      }
+
+      &.tabbar-active {
+        .tabbar-item-text {
+          color: #ff4c1b;
+        }
+        .tabbar-item-wrapper {
+          background: radial-gradient(#ff4c1b, transparent);
+        }
+      }
+    }
+
+    .home-item {
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-end;
+      width: 96rpx;
+      text-align: center;
+
+      .home-item-text {
+        font-family: 'PingFang SC';
+        font-size: 20rpx;
+        font-style: normal;
+        font-weight: 600;
+        line-height: 36rpx; /* 180% */
+        color: var(--333, #333);
+      }
+
+      .home-item-wrapper {
+        display: flex;
+        justify-content: center;
+        width: 96rpx;
+        height: 96rpx;
+        background-color: #f2f2f2;
+        border-radius: 24rpx;
+
+        .home-item-img {
+          width: 80rpx;
+          margin-top: 10rpx;
+        }
+      }
+
+      &.home-active {
+        .home-item-wrapper {
+          background-color: #ff4c1b;
+        }
+
+        .home-item-text {
+          color: #ff4c1b;
+        }
+      }
+    }
+  }
+</style>

+ 18 - 0
src/layouts/tabbar.vue

@@ -0,0 +1,18 @@
+<template>
+  <wd-config-provider :themeVars="themeVars">
+    <slot />
+    <shop-tabbar />
+    <wd-toast />
+    <wd-message-box />
+  </wd-config-provider>
+</template>
+
+<script lang="ts" setup>
+  import type { ConfigProviderThemeVars } from 'wot-design-uni'
+
+  const themeVars: ConfigProviderThemeVars = {
+    // colorTheme: 'red',
+    // buttonPrimaryBgColor: '#07c160',
+    // buttonPrimaryColor: '#07c160',
+  }
+</script>

+ 10 - 5
src/pages.json

@@ -27,31 +27,31 @@
       {
         "icon": "/static/tabbar/home.png",
         "pagePath": "pages/index/index",
-        "text": "%tabbar.home%",
+        "text": "tabbar.home",
         "iconType": "local"
       },
       {
         "icon": "/static/tabbar/category.png",
         "pagePath": "pages/category/category",
-        "text": "%tabbar.category%",
+        "text": "tabbar.category",
         "iconType": "local"
       },
       {
         "icon": "/static/tabbar/cart.png",
         "pagePath": "pages/cart/cart",
-        "text": "%tabbar.cart%",
+        "text": "tabbar.cart",
         "iconType": "local"
       },
       {
         "icon": "/static/tabbar/community.png",
         "pagePath": "pages/community/community",
-        "text": "%tabbar.community%",
+        "text": "tabbar.community",
         "iconType": "local"
       },
       {
         "icon": "/static/tabbar/person.png",
         "pagePath": "pages/person/person",
-        "text": "%tabbar.person%",
+        "text": "tabbar.person",
         "iconType": "local"
       }
     ]
@@ -60,6 +60,7 @@
     {
       "path": "pages/index/index",
       "type": "home",
+      "layout": "tabbar",
       "style": {
         "navigationStyle": "custom",
         "navigationBarTitleText": "首页"
@@ -75,6 +76,7 @@
     {
       "path": "pages/cart/cart",
       "type": "page",
+      "layout": "tabbar",
       "style": {
         "navigationBarTitleText": "%tabbar.cart%"
       }
@@ -82,6 +84,7 @@
     {
       "path": "pages/category/category",
       "type": "page",
+      "layout": "tabbar",
       "style": {
         "navigationBarTitleText": "%tabbar.category%"
       }
@@ -89,6 +92,7 @@
     {
       "path": "pages/community/community",
       "type": "page",
+      "layout": "tabbar",
       "style": {
         "navigationBarTitleText": "%tabbar.community%"
       }
@@ -96,6 +100,7 @@
     {
       "path": "pages/person/person",
       "type": "page",
+      "layout": "tabbar",
       "style": {
         "navigationBarTitleText": "%tabbar.person%"
       }

+ 1 - 0
src/pages/cart/cart.vue

@@ -1,5 +1,6 @@
 <route lang="json5">
 {
+  layout: 'tabbar',
   style: {
     navigationBarTitleText: '%tabbar.cart%',
   },

+ 1 - 0
src/pages/category/category.vue

@@ -1,5 +1,6 @@
 <route lang="json5">
 {
+  layout: 'tabbar',
   style: {
     navigationBarTitleText: '%tabbar.category%',
   },

+ 1 - 0
src/pages/community/community.vue

@@ -1,5 +1,6 @@
 <route lang="json5">
 {
+  layout: 'tabbar',
   style: {
     navigationBarTitleText: '%tabbar.community%',
   },

+ 1 - 0
src/pages/index/index.vue

@@ -1,6 +1,7 @@
 <!-- 使用 type="home" 属性设置首页,其他页面不需要设置,默认为page;推荐使用json5,更强大,且允许注释 -->
 <route lang="json5" type="home">
 {
+  layout: 'tabbar',
   style: {
     navigationStyle: 'custom',
     navigationBarTitleText: '首页',

+ 1 - 0
src/pages/person/person.vue

@@ -1,5 +1,6 @@
 <route lang="json5">
 {
+  layout: 'tabbar',
   style: {
     navigationBarTitleText: '%tabbar.person%',
   },

+ 16 - 2
src/static/images/tabbar-bg.svg

@@ -1,3 +1,17 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="375" height="75" viewBox="0 0 375 75" fill="none">
-  <path d="M375 0.514268V75L0 75V0.514268C0 14.1036 60.5186 21.9788 154.84 25.0494C156.685 25.1091 158.27 26.3742 158.52 28.1946C161.018 46.3606 153.46 65.5697 188.5 65.5697C222.088 65.5697 216.066 45.5504 217.14 28.7222C217.269 26.7017 218.986 25.1947 221.02 25.2186C258.158 25.6635 375 19.2764 375 0.514268Z" fill="white"/>
+<svg width="375" height="80" viewBox="0 0 375 80" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_d_2208_314)">
+<path d="M375 -3.8287V77.4284L0 77.4285V-3.8287C0 10.9961 60.5186 19.5871 154.84 22.9369C156.685 23.0021 158.27 24.3821 158.52 26.3681C161.018 46.1855 154.96 60.4285 189 60.4285C220.588 60.4285 216.066 45.3016 217.14 26.9436C217.269 24.7394 218.986 23.0954 221.02 23.1215C258.158 23.6068 375 16.6391 375 -3.8287Z" fill="white"/>
+</g>
+<defs>
+<filter id="filter0_d_2208_314" x="-4" y="-9.82874" width="383" height="89.2572" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dy="-2"/>
+<feGaussianBlur stdDeviation="2"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2208_314"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2208_314" result="shape"/>
+</filter>
+</defs>
 </svg>

+ 16 - 0
src/store/tabbar.ts

@@ -0,0 +1,16 @@
+import { defineStore } from 'pinia'
+
+export const useTabbarStore = defineStore('tabbar', {
+  persist: true,
+  state: () => ({
+    tabbarIndex: uni.getStorageSync('app-tabbar-index') || 0,
+  }),
+  actions: {
+    setCurIdx(idx: number) {
+      this.tabbarIndex = idx
+    },
+  },
+  getters: {
+    curIdx: ({ tabbarIndex }) => tabbarIndex,
+  },
+})