Skip to content

路由與頁面跳轉

概述

uni-app 提供了一套完整的路由系統,支援多種頁面跳轉方式。瞭解路由機制是開發 uni-app 應用的基礎。

路由配置

pages.json 配置

路由配置主要在 pages.json 檔案中進行:

json
{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首頁"
      }
    },
    {
      "path": "pages/detail/detail",
      "style": {
        "navigationBarTitleText": "詳情頁"
      }
    },
    {
      "path": "pages/user/user",
      "style": {
        "navigationBarTitleText": "使用者中心"
      }
    }
  ],
  "tabBar": {
    "color": "#7A7E83",
    "selectedColor": "#007AFF",
    "backgroundColor": "#ffffff",
    "list": [
      {
        "pagePath": "pages/index/index",
        "iconPath": "static/tabbar/home.png",
        "selectedIconPath": "static/tabbar/home-active.png",
        "text": "首頁"
      },
      {
        "pagePath": "pages/user/user",
        "iconPath": "static/tabbar/user.png",
        "selectedIconPath": "static/tabbar/user-active.png",
        "text": "我的"
      }
    ]
  }
}

路由規則

  • 首頁規則pages 陣列的第一個頁面為應用首頁
  • 路徑規則:路徑不包含副檔名,使用相對路徑
  • 樣式配置:每個頁面可以單獨配置導航欄樣式

頁面跳轉方式

1. 導航到新頁面

uni.navigateTo

保留當前頁面,跳轉到應用內的某個頁面。

javascript
// 基本用法
uni.navigateTo({
  url: '/pages/detail/detail'
})

// 帶引數跳轉
uni.navigateTo({
  url: '/pages/detail/detail?id=123&title=測試標題'
})

// 使用物件傳參
uni.navigateTo({
  url: '/pages/detail/detail',
  success: (res) => {
    console.log('跳轉成功')
  },
  fail: (err) => {
    console.error('跳轉失敗', err)
  }
})

引數接收

在目標頁面的 onLoad 生命週期中接收引數:

javascript
export default {
  onLoad(options) {
    console.log('頁面引數:', options)
    this.id = options.id
    this.title = options.title
  }
}

2. 重定向到新頁面

uni.redirectTo

關閉當前頁面,跳轉到應用內的某個頁面。

javascript
uni.redirectTo({
  url: '/pages/login/login'
})

適用場景

  • 登入頁面跳轉
  • 許可權驗證失敗跳轉
  • 表單提交後跳轉

3. 返回上一頁

uni.navigateBack

關閉當前頁面,返回上一頁面或多級頁面。

javascript
// 返回上一頁
uni.navigateBack()

// 返回指定層級
uni.navigateBack({
  delta: 2  // 返回上兩頁
})

// 帶引數返回(需要特殊處理)
uni.$emit('pageBack', { data: '返回資料' })
uni.navigateBack()

接收返回資料

在前一頁面監聽事件:

javascript
export default {
  onShow() {
    // 監聽返回事件
    uni.$on('pageBack', (data) => {
      console.log('收到返回資料:', data)
    })
  },
  
  onHide() {
    // 移除事件監聽
    uni.$off('pageBack')
  }
}

4. 切換 Tab 頁面

uni.switchTab

跳轉到 tabBar 頁面,並關閉其他所有非 tabBar 頁面。

javascript
uni.switchTab({
  url: '/pages/user/user'
})

注意事項

  • 只能跳轉到 tabBar 配置的頁面
  • 無法傳遞引數
  • 會關閉所有非 tabBar 頁面

5. 重新載入當前頁面

uni.reLaunch

關閉所有頁面,開啟應用內的某個頁面。

javascript
uni.reLaunch({
  url: '/pages/index/index'
})

適用場景

  • 應用重新初始化
  • 使用者登出後跳轉
  • 重大錯誤恢復

路由傳參方式

1. URL 查詢字串

最常用的傳參方式:

javascript
// 傳遞引數
uni.navigateTo({
  url: '/pages/detail/detail?id=123&name=張三&type=user'
})

// 接收引數
export default {
  onLoad(options) {
    this.id = options.id        // "123"
    this.name = options.name    // "張三"
    this.type = options.type    // "user"
  }
}

優點:簡單易用,支援所有跳轉方式 缺點:只能傳遞字串型別,長度有限制

2. 全域事件匯流排

使用 uni.$emit 和 uni.$on 進行通訊:

javascript
// 頁面A:傳遞資料
uni.$emit('pageData', {
  id: 123,
  name: '張三',
  data: { complex: 'object' }
})
uni.navigateTo({
  url: '/pages/detail/detail'
})

// 頁面B:接收資料
export default {
  onLoad() {
    uni.$once('pageData', (data) => {
      this.processData(data)
    })
  }
}

優點:支援複雜資料型別,靈活性高 缺點:需要手動管理事件監聽

3. 全域狀態管理

使用 Vuex 進行狀態管理:

javascript
// store/modules/page.js
export default {
  state: {
    pageParams: {}
  },
  mutations: {
    setPageParams(state, params) {
      state.pageParams = params
    },
    clearPageParams(state) {
      state.pageParams = {}
    }
  }
}

// 頁面A:設定引數
this.$store.commit('setPageParams', {
  id: 123,
  data: { complex: 'object' }
})
uni.navigateTo({
  url: '/pages/detail/detail'
})

// 頁面B:獲取引數
export default {
  onLoad() {
    const params = this.$store.state.page.pageParams
    this.processParams(params)
    // 清理引數
    this.$store.commit('clearPageParams')
  }
}

優點:資料持久化,支援複雜場景 缺點:需要配置 Vuex,稍顯複雜

4. 本地儲存

使用 uni.setStorage 進行資料傳遞:

javascript
// 頁面A:儲存資料
uni.setStorage({
  key: 'pageParams',
  data: {
    id: 123,
    name: '張三'
  }
})
uni.navigateTo({
  url: '/pages/detail/detail'
})

// 頁面B:讀取資料
export default {
  onLoad() {
    uni.getStorage({
      key: 'pageParams',
      success: (res) => {
        this.processData(res.data)
        // 清理資料
        uni.removeStorage({
          key: 'pageParams'
        })
      }
    })
  }
}

優點:資料持久化,支援離線場景 缺點:非同步操作,需要處理錯誤

路由守衛

自定義路由守衛

雖然 uni-app 沒有內建的路由守衛,但可以透過封裝實現類似功能:

javascript
// utils/router.js
export const router = {
  // 路由跳轉前的守衛
  beforeEach(callback) {
    this.beforeEachCallback = callback
  },
  
  // 封裝 navigateTo
  navigateTo(options) {
    if (this.beforeEachCallback) {
      const result = this.beforeEachCallback(options)
      if (result === false) {
        return Promise.reject(new Error('路由跳轉被阻止'))
      }
      if (typeof result === 'object') {
        options = { ...options, ...result }
      }
    }
    
    return new Promise((resolve, reject) => {
      uni.navigateTo({
        ...options,
        success: (res) => {
          resolve(res)
        },
        fail: (err) => {
          reject(err)
        }
      })
    })
  }
}

// 使用路由守衛
router.beforeEach((to) => {
  // 檢查登入狀態
  if (to.url.includes('/user/') && !isLogin()) {
    uni.showToast({
      title: '請先登入',
      icon: 'none'
    })
    // 跳轉到登入頁
    uni.redirectTo({
      url: '/pages/login/login'
    })
    return false
  }
  
  // 新增通用引數
  return {
    url: to.url + '?timestamp=' + Date.now()
  }
})

// 使用封裝後的路由
router.navigateTo({
  url: '/pages/user/profile'
})

許可權控制

javascript
// 許可權檢查函數
function checkPermission(pageUrl) {
  const publicPages = ['/pages/index/index', '/pages/login/login']
  
  // 公開頁面直接放行
  if (publicPages.includes(pageUrl)) {
    return true
  }
  
  // 檢查使用者許可權
  const userInfo = getStorage('userInfo')
  if (!userInfo) {
    return false
  }
  
  // 根據使用者角色檢查許可權
  const userRole = userInfo.role
  const pagePermissions = getPagePermissions(pageUrl)
  
  return pagePermissions.includes(userRole)
}

// 路由守衛
router.beforeEach((to) => {
  if (!checkPermission(to.url)) {
    uni.showToast({
      title: '無許可權訪問',
      icon: 'none'
    })
    return false
  }
})

路由動畫

配置頁面動畫

pages.json 中配置頁面切換動畫:

json
{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首頁",
        "animationType": "slide-in-right",
        "animationDuration": 300
      }
    }
  ],
  "globalStyle": {
    "animationType": "pop-in",
    "animationDuration": 300
  }
}

動畫型別

  • slide-in-right:從右側滑入
  • slide-in-left:從左側滑入
  • slide-in-top:從頂部滑入
  • slide-in-bottom:從底部滑入
  • fade-in:淡入
  • zoom-out:縮放淡出
  • zoom-fade-out:縮放淡出
  • pop-in:彈出

自定義動畫

javascript
// 使用 CSS 動畫
.custom-animation {
  animation: slideIn 0.3s ease-out;
}

@keyframes slideIn {
  from {
    transform: translateX(100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

路由最佳實踐

1. 路由管理

javascript
// utils/router.js
const routes = {
  HOME: '/pages/index/index',
  LOGIN: '/pages/login/login',
  USER_PROFILE: '/pages/user/profile',
  PRODUCT_DETAIL: '/pages/product/detail'
}

export const navigateTo = (route, params = {}) => {
  let url = routes[route] || route
  
  if (params && Object.keys(params).length > 0) {
    const queryString = new URLSearchParams(params).toString()
    url += '?' + queryString
  }
  
  return uni.navigateTo({ url })
}

// 使用
navigateTo('PRODUCT_DETAIL', { id: 123, from: 'home' })

2. 錯誤處理

javascript
async function safeNavigate(options) {
  try {
    const result = await uni.navigateTo(options)
    return result
  } catch (error) {
    console.error('路由跳轉失敗:', error)
    
    // 錯誤處理
    if (error.errMsg.includes('fail page')) {
      uni.showToast({
        title: '頁面不存在',
        icon: 'error'
      })
    } else if (error.errMsg.includes('network')) {
      uni.showToast({
        title: '網路錯誤',
        icon: 'error'
      })
    }
    
    throw error
  }
}

3. 效能優化

javascript
// 防抖跳轉
let lastNavigateTime = 0
const NAVIGATE_DEBOUNCE = 500

function debounceNavigate(options) {
  const now = Date.now()
  if (now - lastNavigateTime < NAVIGATE_DEBOUNCE) {
    return Promise.resolve()
  }
  
  lastNavigateTime = now
  return uni.navigateTo(options)
}

// 預載入頁面
function preloadPage(url) {
  // 提前載入頁面資源
  uni.preloadPage({ url })
}

常見問題

Q: navigateTo 和 redirectTo 有什麼區別?

A: navigateTo 保留當前頁面(可返回),redirectTo 關閉當前頁面(不可返回)。

Q: 如何傳遞複雜物件?

A: 可以使用全域事件、Vuex 或本地儲存傳遞複雜物件。

Q: 如何實現頁面間通訊?

A: 可以使用 uni.$emit/uni.$on 事件匯流排或 Vuex 狀態管理。

Q: 路由跳轉有數量限制嗎?

A: 是的,小程式平臺通常有10層頁面棧限制。


掌握路由系統是開發高效uni-app應用的關鍵!

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