| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- <template>
- <view class="tab-bar">
- <view
- v-for="(tab, index) in tabs"
- :key="index"
- class="tab-item"
- @click="switchTab(tab, index)"
- >
- <view class="tab-inner" :class="{ active: current === index }">
- <view class="tab-icon">
- <view class="icon-stack">
- <AppIcon class="icon-layer base" :name="tab.icon" :size="24" :color="inactiveColor" />
- <AppIcon class="icon-layer active-layer" :name="tab.icon" :size="24" :color="activeColor" />
- </view>
- </view>
- <text class="tab-text" :class="{ active: current === index }">{{ tab.text }}</text>
- </view>
- </view>
- </view>
- </template>
- <script setup>
- import { ref, watch, onMounted, getCurrentInstance } from 'vue'
- import { hapticMedium } from '../utils/haptics.js'
- const instance = getCurrentInstance()
- const activeColor = '#C6171E'
- const inactiveColor = '#8C8C8C'
- const tabs = [
- { pagePath: '/pages/index/index', text: '首页', icon: 'home' },
- { pagePath: '/pages/order/list', text: '订单', icon: 'clipboard' },
- { pagePath: '/pages/device/list', text: '设备', icon: 'monitor' },
- { pagePath: '/pages/finance/index',text: '财务', icon: 'dollar' },
- ]
- const current = ref(0)
- const getCurrentRoute = () => {
- const pages = getCurrentPages()
- if (pages.length > 0) {
- const route = '/' + pages[pages.length - 1].route
- const idx = tabs.findIndex(t => t.pagePath === route)
- if (idx !== -1) current.value = idx
- }
- }
- onMounted(() => getCurrentRoute())
- watch(() => instance?.proxy?.$route, () => getCurrentRoute(), { immediate: true })
- const switchTab = (tab, index) => {
- // 重复点击当前 Tab 不切换、不触发触感
- if (current.value === index) return
- hapticMedium()
- uni.switchTab({ url: tab.pagePath })
- }
- </script>
- <style scoped>
- .tab-bar {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- display: flex;
- align-items: stretch;
- background: #FFFFFF;
- height: 104rpx;
- padding: 8rpx 16rpx;
- padding-bottom: calc(8rpx + env(safe-area-inset-bottom));
- box-shadow: 0 -2px 20px rgba(0, 0, 0, 0.06);
- z-index: 999;
- }
- .tab-item {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: transform 0.12s cubic-bezier(0.4, 0, 1, 1);
- }
- .tab-item:active {
- transform: scale(0.92);
- }
- .tab-inner {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 4rpx;
- padding: 8rpx 24rpx;
- border-radius: 20rpx;
- transition: background 0.25s cubic-bezier(0.4, 0, 0.2, 1);
- min-width: 120rpx;
- }
- .tab-inner.active {
- background: rgba(198, 23, 30, 0.06);
- }
- .tab-icon {
- display: flex;
- align-items: center;
- justify-content: center;
- }
- /* 双层图标:灰色/红色各渲染一份,src 永不切换,避免 image 重新解码导致的闪烁 */
- .icon-stack {
- position: relative;
- width: 48rpx;
- height: 48rpx;
- }
- .icon-layer {
- position: absolute;
- left: 0;
- top: 0;
- transition: opacity 0.22s ease, transform 0.32s cubic-bezier(0.34, 1.56, 0.64, 1);
- }
- .icon-layer.active-layer {
- opacity: 0;
- transform: scale(0.55);
- }
- .tab-inner.active .icon-layer.base {
- opacity: 0;
- }
- .tab-inner.active .icon-layer.active-layer {
- opacity: 1;
- transform: scale(1);
- }
- .tab-text {
- font-size: 22rpx;
- font-weight: 600;
- color: #8C8C8C;
- transition: color 0.25s;
- line-height: 1;
- }
- .tab-text.active {
- color: #C6171E;
- font-weight: 700;
- }
- </style>
|