微信小程序 navigateTo 栈超过多层后会失效

发布于:2025-09-03 ⋅ 阅读:(18) ⋅ 点赞:(0)

背景。自定义导航栏。体验版发布后。产品进行测试。发现底部导航切换多了以后会失效,所有uni.navigateTo都会失效。

  • 真机测试不会报错。不会提示

解决方案:上代码

  • 原理:检测栈的长度。超过了使用reLauch进行跳转便清空了navigateTo的栈空间,再重新继续使用navigateTo。也可以都是用reLauch,但体验度不好,会白屏后加载。
<template>
  <view class="custom-tabbar">
    <!-- 彩色装饰条 - 按设计稿三色比例 -->
    <view class="color-bars">
      <view class="color-bar bar-orange"></view>
      <view class="color-bar bar-brown"></view>
      <view class="color-bar bar-red"></view>
    </view>

    <!-- tabBar内容 -->
    <view class="tabbar-content">
      <!-- 首页 -->
      <view class="tab-item" @click="switchTab('home')" :class="{ 'tab-active': activeTab === 'home' }">
        <image
          :src="activeTab === 'home' ? '/static/img/figma/首页.png' : '/static/img/figma/首页1.png'"
          class="tab-icon"
          :class="{ 'icon-active': activeTab === 'home' }"
          mode="aspectFit" />
        <text class="tab-text" :class="{ active: activeTab === 'home' }">首页</text>
      </view>

      <!-- 动态 -->
      <view class="tab-item" @click="switchTab('dynamic')" :class="{ 'tab-active': activeTab === 'dynamic' }">
        <image
          :src="activeTab === 'dynamic' ? '/static/img/figma/动态.png' : '/static/img/figma/动态1.png'"
          class="tab-icon"
          :class="{ 'icon-active': activeTab === 'dynamic' }"
          mode="aspectFit" />
        <text class="tab-text" :class="{ active: activeTab === 'dynamic' }">动态</text>
      </view>

      <!-- 项目 -->
      <view class="tab-item" @click="switchTab('project')" :class="{ 'tab-active': activeTab === 'project' }">
        <image
          :src="activeTab === 'project' ? '/static/img/figma/项目.png' : '/static/img/figma/项目1.png'"
          class="tab-icon"
          :class="{ 'icon-active': activeTab === 'project' }"
          mode="aspectFit" />
        <text class="tab-text" :class="{ active: activeTab === 'project' }">项目</text>
      </view>

      <!-- 个人中心 -->
      <view class="tab-item" @click="switchTab('profile')" :class="{ 'tab-active': activeTab === 'profile' }">
        <image
          :src="activeTab === 'profile' ? '/static/img/figma/个人中心.png' : '/static/img/figma/个人中心1.png'"
          class="tab-icon"
          :class="{ 'icon-active': activeTab === 'profile' }"
          mode="aspectFit" />
        <text class="tab-text" :class="{ active: activeTab === 'profile' }">个人中心</text>
      </view>
    </view>
    <!-- 帮助中心(大logo,不可点击) -->
    <view class="tab-item help-center">
      <image src="/static/bar/image10.png" class="help-icon" mode="aspectFit" />
      <text class="help-text">帮助中心</text>
    </view>
  </view>
</template>

<script>
export default {
  name: 'CustomTabBar',
  props: {
    // 当前激活的tab
    currentTab: {
      type: String,
      default: 'home'
    },
    // 是否在点击时自动导航。为false时仅切换高亮并抛出事件,不跳转页面
    navigateOnClick: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      activeTab: 'home',
      // 防重复点击,避免连续跳转
      isNavigating: false,
      // 页面栈软阈值,超过后改用reLaunch以清栈
      stackSoftLimit: 8
    };
  },
  watch: {
    currentTab: {
      handler(newVal) {
        this.activeTab = newVal;
      },
      immediate: true
    }
  },
  mounted() {
    // 组件挂载时,根据当前页面路径设置激活状态
    this.setActiveTabByCurrentPage();
  },
  methods: {
    /**
     * 切换tab页面
     * @param {string} tabName - tab名称
     */
    switchTab(tabName) {
      if (this.activeTab === tabName) {
        return; // 已经是当前tab,无需切换
      }

      // 添加触觉反馈
      // #ifdef APP-PLUS
      plus.device.vibrate(50);
      // #endif

      this.activeTab = tabName;

      // 若关闭自动导航,仅更新激活态并通知父级
      if (!this.navigateOnClick) {
        this.$emit('tabChange', tabName);
        return;
      }

      // 计算目标地址
      let targetUrl = '';
      switch (tabName) {
        case 'home':
          targetUrl = '/pages/index/index';
          break;
        case 'dynamic':
          targetUrl = '/pages/article/index';
          break;
        case 'project':
          targetUrl = '/pages/yzpg/index';
          break;
        case 'profile':
          targetUrl = '/pages/profile/center';
          break;
        default:
          console.warn('未知的tab名称:', tabName);
      }

      // 已在目标页则不跳转
      const pages = getCurrentPages();
      if (pages.length) {
        const currentRoute = '/' + pages[pages.length - 1].route;
        if (currentRoute === targetUrl) {
          return;
        }
      }

      // 优先使用navigateTo以获得更丝滑的切换;当接近栈上限时自动切换为reLaunch清栈
      this.navigateToSafe('auto', targetUrl);

      // 触发父组件事件
      this.$emit('tabChange', tabName);
    },

    /**
     * 安全导航(带防抖与自动方式)
     * @param {'auto'|'navigateTo'|'redirectTo'|'reLaunch'|'switchTab'} method
     * @param {string} url
     */
    navigateToSafe(method, url) {
      if (this.isNavigating) return;
      this.isNavigating = true;

      const release = () => {
        setTimeout(() => {
          this.isNavigating = false;
        }, 300);
      };

      try {
        if (method === 'auto') {
          const pages = getCurrentPages();
          const len = pages.length;
          // 近栈上限走reLaunch,正常走navigateTo
          if (len >= this.stackSoftLimit) {
            uni.reLaunch({ url, complete: release });
          } else {
            uni.navigateTo({ url, complete: release });
          }
        } else if (method === 'switchTab') {
          uni.switchTab({ url, complete: release });
        } else if (method === 'reLaunch') {
          uni.reLaunch({ url, complete: release });
        } else if (method === 'redirectTo') {
          uni.redirectTo({ url, complete: release });
        } else {
          uni.navigateTo({ url, complete: release });
        }
      } catch (e) {
        release();
      }
    },

    /**
     * 根据当前页面路径设置激活的tab
     */
    setActiveTabByCurrentPage() {
      const pages = getCurrentPages();
      if (pages.length === 0) return;

      const currentPage = pages[pages.length - 1];
      const currentPath = currentPage.route;

      // 根据页面路径设置对应的tab状态
      if (currentPath.includes('pages/index/index')) {
        this.activeTab = 'home';
      } else if (
        currentPath.includes('pages2agree/pages/article/list') ||
        currentPath.includes('pages/article/index')
      ) {
        this.activeTab = 'dynamic';
      } else if (currentPath.includes('pages/yzpg/index')) {
        this.activeTab = 'project';
      } else if (
        currentPath.includes('pages/profile/index') ||
        currentPath.includes('pages/profile/center') ||
        currentPath.includes('pages3profile/pages/profile/logged')
      ) {
        this.activeTab = 'profile';
      }
    }
  }
};
</script>

<style lang="scss" scoped>
.custom-tabbar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 999;
  background-color: #ffffff;
  box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
}

/* 彩色装饰条 - 按设计稿样式 */
.color-bars {
  display: flex;
  height: 10rpx; /* 按设计稿5px转换为10rpx */
  background-color: #ffffff;
}

.color-bar {
  height: 100%;
}

.bar-orange {
  background-color: #f2981f; /* 按设计稿颜色 */
  flex: 350; /* 按设计稿比例 350:248:92 */
}

.bar-brown {
  background-color: #ca5737; /* 按设计稿颜色 */
  flex: 248; /* 按设计稿比例 */
}

.bar-red {
  background-color: #d84a24; /* 按设计稿颜色 */
  flex: 92; /* 按设计稿比例 */
}

/* tabBar内容 */
.tabbar-content {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20rpx 32rpx 26rpx 32rpx; /* 按设计稿调整内边距 */
  background-color: #ffffff;
  position: relative; /* 为绝对定位的帮助中心图标提供定位上下文 */
  padding-right: 160rpx; /* 右侧留出空间给帮助中心大图标 */
}

.tab-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: 1;
  position: relative;
  cursor: pointer;
  min-width: 0;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  transform-origin: center;
}

.tab-item:active {
  transform: scale(0.95);
}

.tab-item.tab-active {
  transform: scale(1.05);
}

.tab-icon {
  width: 56rpx; /* 按设计稿28px转换为56rpx */
  height: 56rpx; /* 按设计稿28px转换为56rpx */
  margin-bottom: 6rpx; /* 调整图标与文字间距 */
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  filter: grayscale(0);
}

.tab-icon.icon-active {
  filter: grayscale(0) brightness(1.1);
  transform: scale(1.1);
}

.tab-icon-uview {
  margin-bottom: 6rpx; /* 调整u-icon与文字间距 */
}

.help-icon {
  width: 128rpx; /* 按设计稿64px转换为128rpx */
  height: 128rpx; /* 按设计稿64px转换为128rpx */
  display: block;
  object-fit: contain;
  margin-bottom: 6rpx; /* 与其他图标保持一致的间距 */
}

.tab-text {
  font-size: 24rpx; /* 按设计稿12px转换为24rpx */
  color: #393939; /* 按设计稿未选中颜色 */
  line-height: 1;
  transition: color 0.3s ease;
  letter-spacing: 0.96rpx; /* 按设计稿letterSpacing 0.48转换 */
}

.tab-text.active {
  color: #e63e0d; /* 按设计稿选中状态颜色 */
  font-weight: 600; /* 选中状态稍微加粗 */
  transform: scale(1.05);
}

/* 帮助中心特殊样式 - 按设计稿64x64大图标 */
.help-center {
  position: absolute;
  right: 32rpx; /* 按设计稿调整距离右边缘的距离 */
  bottom: 26rpx; /* 与其他tab项对齐 */
  width: 128rpx; /* 按设计稿64px转换为128rpx */
  height: 156rpx; /* 为文字留出足够空间 */
  z-index: 15; /* 确保在最上层 */
  display: flex;
  flex-direction: column; /* 垂直排列 */
  align-items: center;
  justify-content: flex-start; /* 从顶部开始排列 */
}

.help-center .help-icon {
  position: relative;
  z-index: 10;
  opacity: 1;
  visibility: visible;
}

/* 帮助中心文字样式 */
.help-text {
  font-size: 24rpx; /* 与其他文字保持一致 */
  color: #393939; /* 与其他未选中文字保持一致 */
  line-height: 1;
  text-align: center;
  white-space: nowrap;
  letter-spacing: 0.96rpx; /* 与其他文字保持一致 */
}

/* 响应式适配 */
@media screen and (max-width: 750rpx) {
  .tab-text,
  .help-text {
    font-size: 20rpx; /* 小屏幕下稍微缩小字体 */
  }

  .tab-icon {
    width: 48rpx; /* 小屏幕下稍微缩小图标 */
    height: 48rpx;
  }

  .tab-icon-uview {
    margin-bottom: 4rpx; /* 小屏幕下调整间距 */
  }

  /* 小屏幕下调整u-icon大小 */
  .tab-item .tab-icon-uview /deep/ .u-icon {
    font-size: 48rpx !important; /* 小屏幕下图标大小 */
  }

  .help-center {
    width: 112rpx; /* 小屏幕下帮助中心图标容器 */
    height: 140rpx; /* 小屏幕下高度 */
    right: 24rpx; /* 小屏幕下距离边缘 */
  }

  .help-icon {
    width: 112rpx; /* 小屏幕下帮助中心图标 */
    height: 112rpx;
  }

  .tabbar-content {
    padding: 16rpx 24rpx 20rpx 24rpx; /* 小屏幕下调整内边距 */
    padding-right: 140rpx; /* 小屏幕下右侧空间 */
  }

  .color-bars {
    height: 8rpx; /* 小屏幕下装饰条稍微细一些 */
  }
}

/* 安全区域适配 */
@supports (padding-bottom: env(safe-area-inset-bottom)) {
  .tabbar-content {
    padding-bottom: calc(26rpx + env(safe-area-inset-bottom));
  }
}
</style>


网站公告

今日签到

点亮在社区的每一天
去签到