Skip to content

导航组件

导航组件是用于页面导航和布局的组件,在 uni-app 中提供了多种导航组件,帮助开发者构建应用的导航结构。

navigator 组件类似于 HTML 中的 <a> 标签,用于页面跳转。该组件会自动根据系统平台调用对应的跳转方式。

属性说明

属性名类型默认值说明
urlString应用内的跳转链接,值为相对路径或绝对路径
open-typeStringnavigate跳转方式,可选值:navigate、redirect、switchTab、reLaunch、navigateBack
deltaNumber1当 open-type 为 'navigateBack' 时有效,表示回退的层数
animation-typeStringpop-in/out当 open-type 为 navigate、navigateBack 时有效,窗口的显示/关闭动画效果
animation-durationNumber300当 open-type 为 navigate、navigateBack 时有效,窗口显示/关闭动画的持续时间
hover-classStringnavigator-hover指定点击时的样式类,当 hover-class="none" 时,没有点击态效果

open-type 有效值

说明平台差异说明
navigate对应 uni.navigateTo 的功能
redirect对应 uni.redirectTo 的功能
switchTab对应 uni.switchTab 的功能
reLaunch对应 uni.reLaunch 的功能
navigateBack对应 uni.navigateBack 的功能
exit退出小程序微信小程序、QQ小程序

示例代码

vue
<template>
  <view>
    <!-- 普通跳转 -->
    <navigator url="/pages/detail/detail?id=1">跳转到详情页</navigator>
    
    <!-- 使用 redirect 方式跳转 -->
    <navigator url="/pages/about/about" open-type="redirect">重定向到关于页面</navigator>
    
    <!-- 跳转到 tabBar 页面 -->
    <navigator url="/pages/index/index" open-type="switchTab">切换到首页</navigator>
    
    <!-- 返回上一页 -->
    <navigator open-type="navigateBack">返回上一页</navigator>
    
    <!-- 自定义点击态样式 -->
    <navigator url="/pages/detail/detail" hover-class="custom-hover">
      自定义点击态效果
    </navigator>
  </view>
</template>

<style>
.custom-hover {
  background-color: #f0f0f0;
  opacity: 0.8;
}
</style>

页面导航 API

除了使用 navigator 组件进行页面导航外,uni-app 还提供了一系列导航 API,可以在 JavaScript 中实现页面跳转。

uni.navigateTo(OBJECT)

保留当前页面,跳转到应用内的某个页面,使用 uni.navigateBack 可以返回到原页面。

javascript
// 跳转到详情页
uni.navigateTo({
  url: '/pages/detail/detail?id=1',
  success: function(res) {
    console.log('跳转成功');
  },
  fail: function(err) {
    console.error('跳转失败', err);
  },
  complete: function() {
    console.log('跳转完成');
  }
});

uni.redirectTo(OBJECT)

关闭当前页面,跳转到应用内的某个页面。

javascript
// 重定向到关于页面
uni.redirectTo({
  url: '/pages/about/about'
});

uni.reLaunch(OBJECT)

关闭所有页面,打开到应用内的某个页面。

javascript
// 重新启动到首页
uni.reLaunch({
  url: '/pages/index/index'
});

uni.switchTab(OBJECT)

跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。

javascript
// 切换到首页 Tab
uni.switchTab({
  url: '/pages/index/index'
});

uni.navigateBack(OBJECT)

关闭当前页面,返回上一页面或多级页面。

javascript
// 返回上一页
uni.navigateBack();

// 返回到指定层级页面
uni.navigateBack({
  delta: 2 // 返回的页面数,如果 delta 大于现有页面数,则返回到首页
});

页面栈

uni-app 的页面栈最多十层,当页面栈达到十层后,uni.navigateTo 不能正常跳转,此时可以使用 uni.redirectTo 或 uni.reLaunch 等方法替代。

可以使用 getCurrentPages() 函数获取当前页面栈的实例,以数组形式按栈的顺序给出,第一个元素为首页,最后一个元素为当前页面。

javascript
// 获取页面栈
const pages = getCurrentPages();
// 获取当前页面
const currentPage = pages[pages.length - 1];
// 获取当前页面的路由
const currentRoute = currentPage.route;
// 获取当前页面的参数
const currentOptions = currentPage.options;

console.log('当前页面路由:', currentRoute);
console.log('当前页面参数:', currentOptions);

自定义导航栏

在某些场景下,我们可能需要自定义导航栏来实现特定的设计需求。uni-app 支持通过配置 pages.json 来设置导航栏样式或隐藏原生导航栏,然后使用自定义组件实现导航栏。

隐藏原生导航栏

pages.json 中配置:

json
{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationStyle": "custom" // 隐藏原生导航栏
      }
    }
  ]
}

自定义导航栏组件示例

vue
<template>
  <view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
    <view class="nav-content">
      <view class="nav-left" @click="goBack" v-if="showBack">
        <text class="iconfont icon-back"></text>
      </view>
      <view class="nav-title">
        <text>{{ title }}</text>
      </view>
      <view class="nav-right">
        <slot name="right"></slot>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      default: '标题'
    },
    showBack: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      statusBarHeight: 0
    }
  },
  created() {
    // 获取状态栏高度
    const systemInfo = uni.getSystemInfoSync();
    this.statusBarHeight = systemInfo.statusBarHeight;
  },
  methods: {
    goBack() {
      uni.navigateBack({
        delta: 1
      });
    }
  }
}
</script>

<style scoped>
.custom-nav {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  background-color: #ffffff;
  z-index: 999;
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.1);
}
.nav-content {
  display: flex;
  align-items: center;
  height: 44px;
  padding: 0 15px;
}
.nav-left {
  width: 60px;
  display: flex;
  align-items: center;
}
.nav-title {
  flex: 1;
  text-align: center;
  font-size: 16px;
  font-weight: bold;
}
.nav-right {
  width: 60px;
  display: flex;
  justify-content: flex-end;
  align-items: center;
}
.icon-back {
  font-size: 20px;
}
</style>

使用自定义导航栏:

vue
<template>
  <view class="container">
    <custom-nav title="自定义导航栏">
      <template #right>
        <text class="iconfont icon-share" @click="share"></text>
      </template>
    </custom-nav>
    
    <view class="content" :style="{ paddingTop: navHeight + 'px' }">
      <!-- 页面内容 -->
      <view>页面内容区域</view>
    </view>
  </view>
</template>

<script>
import CustomNav from '@/components/CustomNav.vue';

export default {
  components: {
    CustomNav
  },
  data() {
    return {
      navHeight: 0
    }
  },
  created() {
    // 计算导航栏高度
    const systemInfo = uni.getSystemInfoSync();
    this.navHeight = systemInfo.statusBarHeight + 44;
  },
  methods: {
    share() {
      uni.showToast({
        title: '分享功能'
      });
    }
  }
}
</script>

TabBar 导航

TabBar 是移动应用常用的底部导航栏,uni-app 支持通过配置 pages.json 来设置应用的 TabBar。

TabBar 配置示例

json
{
  "tabBar": {
    "color": "#7A7E83",
    "selectedColor": "#3cc51f",
    "borderStyle": "black",
    "backgroundColor": "#ffffff",
    "list": [
      {
        "pagePath": "pages/index/index",
        "iconPath": "static/images/home.png",
        "selectedIconPath": "static/images/home-active.png",
        "text": "首页"
      },
      {
        "pagePath": "pages/category/category",
        "iconPath": "static/images/category.png",
        "selectedIconPath": "static/images/category-active.png",
        "text": "分类"
      },
      {
        "pagePath": "pages/cart/cart",
        "iconPath": "static/images/cart.png",
        "selectedIconPath": "static/images/cart-active.png",
        "text": "购物车"
      },
      {
        "pagePath": "pages/user/user",
        "iconPath": "static/images/user.png",
        "selectedIconPath": "static/images/user-active.png",
        "text": "我的"
      }
    ]
  }
}

TabBar 属性说明

属性类型必填默认值说明
colorHexColortab 上的文字默认颜色
selectedColorHexColortab 上的文字选中时的颜色
backgroundColorHexColortab 的背景色
borderStyleStringblacktabbar 上边框的颜色,可选值:black、white
listArraytab 的列表,最少 2 个、最多 5 个 tab
positionStringbottomtabBar 的位置,可选值:bottom、top
customBooleanfalse是否自定义 tabBar

自定义 TabBar

对于需要更复杂交互或特殊样式的 TabBar,uni-app 支持自定义 TabBar。

  1. 首先在 pages.json 中配置 custom: true
json
{
  "tabBar": {
    "custom": true,
    "color": "#7A7E83",
    "selectedColor": "#3cc51f",
    "borderStyle": "black",
    "backgroundColor": "#ffffff",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页"
      },
      {
        "pagePath": "pages/category/category",
        "text": "分类"
      },
      {
        "pagePath": "pages/cart/cart",
        "text": "购物车"
      },
      {
        "pagePath": "pages/user/user",
        "text": "我的"
      }
    ]
  }
}
  1. 创建自定义 TabBar 组件:
vue
<!-- components/CustomTabBar.vue -->
<template>
  <view class="custom-tab-bar">
    <view 
      v-for="(item, index) in tabList" 
      :key="index" 
      class="tab-item" 
      :class="{ active: current === index }"
      @click="switchTab(item.pagePath, index)"
    >
      <view class="icon-wrapper">
        <image :src="current === index ? item.selectedIconPath : item.iconPath" class="icon"></image>
        <view v-if="item.badge" class="badge">{{item.badge}}</view>
      </view>
      <text class="text">{{item.text}}</text>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      current: 0,
      tabList: [
        {
          pagePath: '/pages/index/index',
          iconPath: '/static/images/home.png',
          selectedIconPath: '/static/images/home-active.png',
          text: '首页',
          badge: ''
        },
        {
          pagePath: '/pages/category/category',
          iconPath: '/static/images/category.png',
          selectedIconPath: '/static/images/category-active.png',
          text: '分类',
          badge: ''
        },
        {
          pagePath: '/pages/cart/cart',
          iconPath: '/static/images/cart.png',
          selectedIconPath: '/static/images/cart-active.png',
          text: '购物车',
          badge: '5'
        },
        {
          pagePath: '/pages/user/user',
          iconPath: '/static/images/user.png',
          selectedIconPath: '/static/images/user-active.png',
          text: '我的',
          badge: ''
        }
      ]
    }
  },
  methods: {
    switchTab(path, index) {
      if (this.current !== index) {
        uni.switchTab({
          url: path
        });
        this.current = index;
      }
    },
    setTabBarBadge(index, text) {
      this.tabList[index].badge = text;
    }
  },
  created() {
    // 获取当前页面路径,设置当前选中的 tab
    const pages = getCurrentPages();
    const page = pages[pages.length - 1];
    const currentPath = '/' + page.route;
    
    this.tabList.forEach((item, index) => {
      if (item.pagePath === currentPath) {
        this.current = index;
      }
    });
  }
}
</script>

<style scoped>
.custom-tab-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: 50px;
  background-color: #ffffff;
  display: flex;
  box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.1);
}
.tab-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.icon-wrapper {
  position: relative;
}
.icon {
  width: 24px;
  height: 24px;
}
.badge {
  position: absolute;
  top: -8px;
  right: -8px;
  background-color: #ff4d4f;
  color: #ffffff;
  font-size: 12px;
  min-width: 16px;
  height: 16px;
  line-height: 16px;
  text-align: center;
  border-radius: 8px;
  padding: 0 4px;
}
.text {
  font-size: 12px;
  margin-top: 2px;
}
.active .text {
  color: #3cc51f;
}
</style>
  1. 在每个 TabBar 页面中引入自定义 TabBar 组件:
vue
<template>
  <view class="container">
    <!-- 页面内容 -->
    <view class="content">
      <text>首页内容</text>
    </view>
    
    <!-- 自定义 TabBar -->
    <custom-tab-bar></custom-tab-bar>
  </view>
</template>

<script>
import CustomTabBar from '@/components/CustomTabBar.vue';

export default {
  components: {
    CustomTabBar
  }
}
</script>

<style>
.container {
  padding-bottom: 50px; /* 为 TabBar 预留空间 */
}
</style>

侧滑导航

对于一些内容丰富的应用,侧滑导航是一种常见的导航方式。uni-app 没有内置侧滑导航组件,但可以通过自定义组件实现。

侧滑导航示例

vue
<template>
  <view class="container">
    <!-- 遮罩层 -->
    <view 
      class="mask" 
      :class="{ show: isOpen }" 
      @click="toggleDrawer"
      @touchmove.stop.prevent
    ></view>
    
    <!-- 侧滑菜单 -->
    <view class="drawer" :class="{ open: isOpen }">
      <view class="drawer-header">
        <image class="avatar" src="/static/images/avatar.png"></image>
        <view class="user-info">
          <text class="username">用户名</text>
          <text class="desc">个人简介</text>
        </view>
      </view>
      
      <view class="drawer-content">
        <view 
          v-for="(item, index) in menuList" 
          :key="index" 
          class="menu-item" 
          :class="{ active: activeMenu === index }"
          @click="selectMenu(item, index)"
        >
          <text class="iconfont" :class="item.icon"></text>
          <text class="menu-text">{{item.text}}</text>
        </view>
      </view>
      
      <view class="drawer-footer">
        <view class="menu-item" @click="logout">
          <text class="iconfont icon-logout"></text>
          <text class="menu-text">退出登录</text>
        </view>
      </view>
    </view>
    
    <!-- 主内容区 -->
    <view class="main-content">
      <view class="header">
        <text class="iconfont icon-menu" @click="toggleDrawer"></text>
        <text class="title">{{title}}</text>
        <text class="placeholder"></text>
      </view>
      
      <view class="content">
        <slot></slot>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      default: '标题'
    }
  },
  data() {
    return {
      isOpen: false,
      activeMenu: 0,
      menuList: [
        { text: '首页', icon: 'icon-home', path: '/pages/index/index' },
        { text: '消息', icon: 'icon-message', path: '/pages/message/message' },
        { text: '收藏', icon: 'icon-star', path: '/pages/favorite/favorite' },
        { text: '设置', icon: 'icon-setting', path: '/pages/setting/setting' }
      ]
    }
  },
  methods: {
    toggleDrawer() {
      this.isOpen = !this.isOpen;
    },
    selectMenu(item, index) {
      this.activeMenu = index;
      this.isOpen = false;
      
      // 跳转到对应页面
      uni.navigateTo({
        url: item.path
      });
    },
    logout() {
      uni.showModal({
        title: '提示',
        content: '确定要退出登录吗?',
        success: (res) => {
          if (res.confirm) {
            // 执行退出登录操作
            uni.showToast({
              title: '已退出登录'
            });
            
            // 跳转到登录页
            setTimeout(() => {
              uni.reLaunch({
                url: '/pages/login/login'
              });
            }, 1500);
          }
        }
      });
    }
  }
}
</script>

<style scoped>
.container {
  position: relative;
  width: 100%;
  height: 100vh;
  overflow: hidden;
}

/* 遮罩层 */
.mask {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  z-index: 998;
  opacity: 0;
  visibility: hidden;
  transition: all 0.3s;
}
.mask.show {
  opacity: 1;
  visibility: visible;
}

/* 侧滑菜单 */
.drawer {
  position: fixed;
  top: 0;
  left: -80%;
  width: 80%;
  height: 100%;
  background-color: #ffffff;
  z-index: 999;
  transition: all 0.3s;
  display: flex;
  flex-direction: column;
}
.drawer.open {
  left: 0;
}

.drawer-header {
  padding: 40px 20px 20px;
  background-color: #3cc51f;
  display: flex;
  align-items: center;
}
.avatar {
  width: 60px;
  height: 60px;
  border-radius: 30px;
  margin-right: 15px;
}
.user-info {
  color: #ffffff;
}
.username {
  font-size: 18px;
  font-weight: bold;
}
.desc {
  font-size: 14px;
  margin-top: 5px;
}

.drawer-content {
  flex: 1;
  padding: 10px 0;
}
.menu-item {
  height: 50px;
  display: flex;
  align-items: center;
  padding: 0 20px;
}
.menu-item.active {
  background-color: #f0f0f0;
}
.iconfont {
  font-size: 20px;
  margin-right: 10px;
}
.menu-text {
  font-size: 16px;
}

.drawer-footer {
  padding: 10px 0;
  border-top: 1px solid #f0f0f0;
}

/* 主内容区 */
.main-content {
  position: relative;
  width: 100%;
  height: 100%;
  transition: all 0.3s;
}
.header {
  height: 44px;
  background-color: #3cc51f;
  color: #ffffff;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 15px;
}
.title {
  font-size: 18px;
  font-weight: bold;
}
.placeholder {
  width: 24px;
}
.content {
  padding: 15px;
}
</style>

使用侧滑导航:

vue
<template>
  <drawer-layout title="首页">
    <view>
      <text>这是首页内容</text>
    </view>
  </drawer-layout>
</template>

<script>
import DrawerLayout from '@/components/DrawerLayout.vue';

export default {
  components: {
    DrawerLayout
  }
}
</script>

最佳实践

  1. 合理使用导航方式:根据应用的复杂度和用户习惯选择合适的导航方式,简单应用可以使用 TabBar,复杂应用可以考虑侧滑导航。

  2. 注意页面栈管理:避免页面栈过深导致无法继续跳转,合理使用 redirectTo 和 reLaunch。

  3. 优化导航体验:添加适当的过渡动画,提供清晰的导航提示,让用户知道当前位置和可以去哪里。

  4. 处理好返回逻辑:特别是在多级页面跳转后,确保用户可以方便地返回到合适的页面。

  5. 保持导航一致性:在整个应用中保持导航风格的一致性,避免用户困惑。

  6. 适配不同平台:注意导航组件在不同平台上的表现差异,做好兼容处理。

常见问题

1. 页面跳转参数传递

在页面跳转时,可以通过 URL 参数传递数据:

javascript
// 在 A 页面中跳转到 B 页面并传递参数
uni.navigateTo({
  url: '/pages/B/B?id=1&name=uniapp'
});

// 在 B 页面中接收参数
export default {
  onLoad(options) {
    console.log(options.id); // 1
    console.log(options.name); // uniapp
  }
}

对于复杂数据,可以使用 encodeURIComponentJSON.stringify 进行处理:

javascript
// 传递复杂数据
const data = { id: 1, list: [1, 2, 3], user: { name: 'uniapp' } };
uni.navigateTo({
  url: `/pages/B/B?data=${encodeURIComponent(JSON.stringify(data))}`
});

// 接收复杂数据
export default {
  onLoad(options) {
    if (options.data) {
      const data = JSON.parse(decodeURIComponent(options.data));
      console.log(data);
    }
  }
}

2. 导航栏样式自定义

除了完全自定义导航栏外,uni-app 也支持通过 pages.json 配置修改原生导航栏的样式:

json
{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首页",
        "navigationBarBackgroundColor": "#3cc51f",
        "navigationBarTextStyle": "white"
      }
    }
  ]
}

3. 动态设置导航栏标题

可以使用 uni.setNavigationBarTitle 方法动态设置当前页面的导航栏标题:

javascript
// 动态设置导航栏标题
uni.setNavigationBarTitle({
  title: '新标题'
});

4. 处理物理返回键

在 Android 平台,用户可能会使用物理返回键返回上一页。可以通过监听 onBackPress 事件来处理:

javascript
export default {
  onBackPress() {
    // 返回 true 表示自己处理返回逻辑,不向下传递
    // 返回 false 表示不处理,由系统处理
    if (this.needConfirm) {
      uni.showModal({
        title: '提示',
        content: '确定要返回吗?未保存的内容将丢失',
        success: (res) => {
          if (res.confirm) {
            uni.navigateBack();
          }
        }
      });
      return true; // 自己处理返回逻辑
    }
    return false; // 由系统处理返回逻辑
  }
}

相关链接

一次开发,多端部署 - 让跨平台开发更简单