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; // 由系統處理返回邏輯
  }
}

相關連結

一次開發,多端部署 - 讓跨平台開發更簡單