Skip to content

页面和路由 - 小程序是怎么跳转的?

🚀 页面跳转是小程序的基本功能,今天我们来学习如何在页面之间自由穿梭

什么是路由?

简单来说,路由就是页面之间的跳转规则

想象一下:

  • 你在首页点击"商品详情"按钮 → 跳转到商品详情页
  • 在商品详情页点击"返回"按钮 → 回到首页
  • 点击底部的"我的"标签 → 跳转到个人中心

这些跳转过程就是路由!

UniApp中的路由方式

UniApp提供了几种不同的跳转方式,每种都有不同的用途:

1. uni.navigateTo() - 普通跳转

用途:跳转到新页面,保留当前页面 特点:可以返回上一页

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

// 带参数跳转
uni.navigateTo({
  url: '/pages/detail/detail?id=123&name=商品名称'
})

// 带回调
uni.navigateTo({
  url: '/pages/detail/detail',
  success: function(res) {
    console.log('跳转成功')
  },
  fail: function(err) {
    console.log('跳转失败')
  }
})

2. uni.redirectTo() - 替换跳转

用途:关闭当前页面,跳转到新页面 特点:不能返回上一页

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

使用场景

  • 登录成功后跳转到首页
  • 支付完成后跳转到结果页

3. uni.reLaunch() - 重启应用

用途:关闭所有页面,打开指定页面 特点:清空页面栈

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

使用场景

  • 退出登录回到首页
  • 切换用户身份

4. uni.switchTab() - 切换标签页

用途:跳转到tabBar页面 特点:只能跳转到配置了tabBar的页面

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

5. uni.navigateBack() - 返回上一页

用途:返回上一页或指定页面

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

// 返回指定层数
uni.navigateBack({
  delta: 2  // 返回2层
})

参数传递

1. URL参数传递

发送参数

javascript
uni.navigateTo({
  url: '/pages/detail/detail?id=123&name=iPhone&price=5999'
})

接收参数

javascript
export default {
  onLoad(options) {
    console.log('商品ID:', options.id)      // 123
    console.log('商品名称:', options.name)   // iPhone
    console.log('商品价格:', options.price)  // 5999
  }
}

2. 复杂数据传递

对于复杂的数据(对象、数组),需要先转换:

发送复杂数据

javascript
const product = {
  id: 123,
  name: 'iPhone',
  specs: ['64GB', '128GB', '256GB']
}

uni.navigateTo({
  url: '/pages/detail/detail?data=' + encodeURIComponent(JSON.stringify(product))
})

接收复杂数据

javascript
export default {
  onLoad(options) {
    if (options.data) {
      const product = JSON.parse(decodeURIComponent(options.data))
      console.log(product)
    }
  }
}

实际案例:商品列表和详情

让我们做一个完整的例子:

1. 商品列表页面

vue
<template>
  <view class="product-list">
    <view 
      class="product-item" 
      v-for="product in products" 
      :key="product.id"
      @click="goToDetail(product)"
    >
      <image :src="product.image" class="product-image"></image>
      <view class="product-info">
        <text class="product-name">\{\{ product.name \}\}</text>
        <text class="product-price">¥\{\{ product.price \}\}</text>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      products: [
        { id: 1, name: 'iPhone 15', price: 5999, image: '/static/iphone.jpg' },
        { id: 2, name: 'iPad Air', price: 4599, image: '/static/ipad.jpg' },
        { id: 3, name: 'MacBook Pro', price: 14999, image: '/static/macbook.jpg' }
      ]
    }
  },
  methods: {
    goToDetail(product) {
      // 方式1:简单参数
      uni.navigateTo({
        url: `/pages/detail/detail?id=\${product.id}&name=\${product.name}&price=\${product.price}`
      })
      
      // 方式2:复杂数据
      // uni.navigateTo({
      //   url: '/pages/detail/detail?data=' + encodeURIComponent(JSON.stringify(product))
      // })
    }
  }
}
</script>

<style>
.product-list {
  padding: 20px;
}
.product-item {
  display: flex;
  padding: 15px;
  border-bottom: 1px solid #eee;
}
.product-image {
  width: 80px;
  height: 80px;
  margin-right: 15px;
}
.product-info {
  flex: 1;
}
.product-name {
  font-size: 16px;
  font-weight: bold;
  display: block;
  margin-bottom: 10px;
}
.product-price {
  color: #ff6b6b;
  font-size: 18px;
  font-weight: bold;
}
</style>

2. 商品详情页面

vue
<template>
  <view class="product-detail">
    <image :src="productImage" class="detail-image"></image>
    <view class="detail-info">
      <text class="detail-name">\{\{ productName \}\}</text>
      <text class="detail-price">¥\{\{ productPrice \}\}</text>
      <view class="detail-actions">
        <button @click="addToCart">加入购物车</button>
        <button @click="buyNow" type="primary">立即购买</button>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      productId: '',
      productName: '',
      productPrice: '',
      productImage: '/static/default.jpg'
    }
  },
  onLoad(options) {
    // 接收参数
    this.productId = options.id
    this.productName = options.name
    this.productPrice = options.price
    
    // 根据ID加载更多商品信息
    this.loadProductDetail()
  },
  methods: {
    loadProductDetail() {
      // 模拟加载商品详情
      console.log('加载商品详情,ID:', this.productId)
      // 这里可以调用API获取完整的商品信息
    },
    addToCart() {
      uni.showToast({
        title: '已加入购物车',
        icon: 'success'
      })
    },
    buyNow() {
      // 跳转到订单页面
      uni.navigateTo({
        url: `/pages/order/order?productId=\${this.productId}`
      })
    }
  }
}
</script>

<style>
.product-detail {
  padding: 20px;
}
.detail-image {
  width: 100%;
  height: 300px;
  margin-bottom: 20px;
}
.detail-name {
  font-size: 20px;
  font-weight: bold;
  display: block;
  margin-bottom: 10px;
}
.detail-price {
  color: #ff6b6b;
  font-size: 24px;
  font-weight: bold;
  display: block;
  margin-bottom: 30px;
}
.detail-actions {
  display: flex;
  gap: 15px;
}
.detail-actions button {
  flex: 1;
}
</style>

配置TabBar

如果你的应用需要底部导航,可以在pages.json中配置:

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

路由拦截

有时候我们需要在跳转前做一些检查,比如登录验证:

javascript
// 在App.vue中添加全局路由拦截
export default {
  onLaunch() {
    // 重写navigateTo方法
    const originalNavigateTo = uni.navigateTo
    uni.navigateTo = function(options) {
      // 需要登录的页面列表
      const needLoginPages = ['/pages/user/user', '/pages/order/order']
      
      if (needLoginPages.includes(options.url.split('?')[0])) {
        // 检查是否已登录
        const token = uni.getStorageSync('token')
        if (!token) {
          // 未登录,跳转到登录页
          uni.navigateTo({
            url: '/pages/login/login'
          })
          return
        }
      }
      
      // 执行原始跳转
      originalNavigateTo(options)
    }
  }
}

常见问题和解决方案

1. 页面栈溢出

问题:连续跳转太多页面导致栈溢出 解决:使用redirectToreLaunch替代navigateTo

2. 参数丢失

问题:页面跳转时参数丢失 解决:检查URL编码,使用encodeURIComponent

3. TabBar跳转失败

问题:使用navigateTo跳转TabBar页面失败 解决:使用switchTab跳转TabBar页面

小结

今天我们学习了:

  • ✅ 5种不同的页面跳转方式
  • ✅ 参数传递的方法
  • ✅ TabBar的配置
  • ✅ 路由拦截的实现
  • ✅ 完整的商品列表和详情案例

记住这个口诀

  • navigateTo:普通跳转,可返回
  • redirectTo:替换跳转,不可返回
  • reLaunch:重启应用
  • switchTab:切换标签页
  • navigateBack:返回上一页

下一篇预告

下一篇我们将学习《生命周期详解 - 页面什么时候加载,什么时候销毁?》,深入了解页面和应用的生命周期。

练习作业

  1. 创建一个新闻列表页面,点击新闻跳转到详情页
  2. 在详情页显示新闻标题和内容
  3. 添加返回按钮
  4. 尝试配置一个简单的TabBar

路由是应用的骨架,掌握了路由,你就能构建复杂的多页面应用了!