uni-app 页面开发基础

章节概述

本章节将详细介绍 uni-app 页面开发的基础知识,包括如何创建新页面、页面间的导航跳转方式、参数传递的方法等。通过本章节的学习,您将掌握 uni-app 页面开发的核心技能,能够创建和管理多个页面,并实现页面间的导航和数据传递。

核心知识点

1. 页面创建

创建新页面的步骤

  1. 在 pages 目录下创建页面目录

    • 在项目的 pages 目录下创建一个新的目录,目录名称建议与页面功能相关
    • 例如:创建 detail 目录用于存放详情页
  2. 创建页面文件

    • 在新创建的目录中创建 .vue 文件,文件名建议与目录名称一致
    • 例如:在 detail 目录中创建 detail.vue 文件
  3. 配置页面路由

    • 打开 pages.json 文件
    • pages 数组中添加新页面的配置
    • 配置页面路径和导航栏样式等

页面文件结构

一个标准的 uni-app 页面文件包含以下三个部分:

<template>
  <!-- 页面结构 -->
</template>

<script>
export default {
  // 页面逻辑
}
</script>

<style>
/* 页面样式 */
</style>

示例:创建详情页

  1. 创建目录结构

    pages/
    ├── index/         # 首页
    └── detail/        # 详情页
        └── detail.vue # 详情页文件
  2. 配置 pages.json

    {
      "pages": [
        {
          "path": "pages/index/index",
          "style": {
            "navigationBarTitleText": "首页"
          }
        },
        {
          "path": "pages/detail/detail",
          "style": {
            "navigationBarTitleText": "详情页"
          }
        }
      ]
    }
  3. 创建 detail.vue 文件

    <template>
      <view class="detail-page">
        <view class="detail-content">
          <text>详情页内容</text>
        </view>
      </view>
    </template>
    
    <script>

export default {
onLoad(options) {
console.log('详情页加载', options)
}
}


### 2. 导航跳转

#### uni-app 提供的导航 API

uni-app 提供了以下导航 API 用于页面跳转:

| API 名称 | 功能描述 | 适用场景 |
|---------|---------|----------|
| uni.navigateTo | 保留当前页面,跳转到应用内的某个页面 | 从列表页跳转到详情页 |
| uni.redirectTo | 关闭当前页面,跳转到应用内的某个页面 | 登录成功后跳转到首页 |
| uni.reLaunch | 关闭所有页面,打开到应用内的某个页面 | 退出登录后重新打开应用 |
| uni.switchTab | 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面 | 从非 tabBar 页面跳转到 tabBar 页面 |
| uni.navigateBack | 关闭当前页面,返回上一页面或多级页面 | 从详情页返回列表页 |

#### 导航 API 的使用方法

##### 1. uni.navigateTo
```javascript
// 在起始页面跳转到详情页并传递参数
uni.navigateTo({
  url: '/pages/detail/detail?id=1&name=uni-app'
});

// 在详情页接收参数
export default {
  onLoad(options) {
 console.log(options.id); // 输出:1
 console.log(options.name); // 输出:uni-app
  }
}
2. uni.redirectTo
// 关闭当前页面,跳转到登录页
uni.redirectTo({
  url: '/pages/login/login'
});
3. uni.reLaunch
// 关闭所有页面,跳转到首页
uni.reLaunch({
  url: '/pages/index/index'
});
4. uni.switchTab
// 跳转到 tabBar 页面
uni.switchTab({
  url: '/pages/home/home'
});
5. uni.navigateBack
// 返回上一页
uni.navigateBack({
  delta: 1
});

// 返回上两页
uni.navigateBack({
  delta: 2
});

导航动画

uni-app 提供了导航动画效果,可以通过配置 animationTypeanimationDuration 参数来控制:

uni.navigateTo({
  url: '/pages/detail/detail',
  animationType: 'slide-in-right', // 动画类型
  animationDuration: 300 // 动画持续时间(毫秒)
});

3. 参数传递

1. 通过 URL 传递参数

这是最常用的参数传递方式,适用于从列表页跳转到详情页等场景。

传递参数

// 传递单个参数
uni.navigateTo({
  url: '/pages/detail/detail?id=1'
});

// 传递多个参数
uni.navigateTo({
  url: '/pages/detail/detail?id=1&name=uni-app&price=99.9'
});

// 传递对象参数(需要编码)
const userInfo = { id: 1, name: '张三', age: 25 };
uni.navigateTo({
  url: '/pages/detail/detail?userInfo=' + encodeURIComponent(JSON.stringify(userInfo))
});

接收参数

export default {
  onLoad(options) {
    // 接收单个参数
    const id = options.id;
    console.log('id:', id);
    
    // 接收多个参数
    const { id, name, price } = options;
    console.log('id:', id);
    console.log('name:', name);
    console.log('price:', price);
    
    // 接收对象参数(需要解码)
    if (options.userInfo) {
      const userInfo = JSON.parse(decodeURIComponent(options.userInfo));
      console.log('userInfo:', userInfo);
    }
  }
}

2. 通过全局变量传递参数

适用于需要在多个页面间共享数据的场景。

在 App.vue 中定义全局变量

// App.vue
export default {
  globalData: {
    userInfo: null,
    token: ''
  }
}

设置全局变量

// 在登录页设置用户信息
getApp().globalData.userInfo = { id: 1, name: '张三' };
getApp().globalData.token = 'token123456';

获取全局变量

// 在其他页面获取用户信息
const userInfo = getApp().globalData.userInfo;
const token = getApp().globalData.token;
console.log('userInfo:', userInfo);
console.log('token:', token);

3. 通过 Vuex 传递参数

适用于复杂应用中多个组件和页面间的数据共享。

创建 store

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    userInfo: null,
    token: ''
  },
  mutations: {
    setUserInfo(state, userInfo) {
      state.userInfo = userInfo;
    },
    setToken(state, token) {
      state.token = token;
    }
  },
  actions: {
    login({ commit }, userInfo) {
      // 模拟登录请求
      return new Promise(resolve => {
        setTimeout(() => {
          commit('setUserInfo', userInfo);
          commit('setToken', 'token123456');
          resolve();
        }, 1000);
      });
    }
  }
});

在 main.js 中引入

import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false
App.mpType = 'app'

const app = new Vue({
  store,
  ...App
})
app.$mount()

使用 Vuex

// 在登录页
this.$store.dispatch('login', { id: 1, name: '张三' });

// 在其他页面
const userInfo = this.$store.state.userInfo;
const token = this.$store.state.token;
console.log('userInfo:', userInfo);
console.log('token:', token);

4. 通过事件总线传递参数

适用于兄弟组件或跨页面的数据传递。

创建事件总线

// utils/eventBus.js
import Vue from 'vue'
export default new Vue();

发送事件

// 在页面 A 中发送事件
import eventBus from '@/utils/eventBus';

eventBus.$emit('updateUser', { id: 1, name: '张三' });

接收事件

// 在页面 B 中接收事件
import eventBus from '@/utils/eventBus';

export default {
  onLoad() {
    // 监听事件
    eventBus.$on('updateUser', (userInfo) => {
      console.log('userInfo:', userInfo);
    });
  },
  onUnload() {
    // 移除事件监听
    eventBus.$off('updateUser');
  }
};

实用案例分析

实现页面间导航和数据传递

案例目标

创建一个包含首页和详情页的应用,实现从首页跳转到详情页并传递商品信息的功能。

实现步骤

  1. 创建项目结构

    • 创建首页 pages/index/index.vue
    • 创建详情页 pages/detail/detail.vue
    • 配置 pages.json 文件
  2. 实现首页

    • 展示商品列表
    • 实现跳转详情页功能
    • 传递商品参数
  3. 实现详情页

    • 接收首页传递的参数
    • 展示商品详情
    • 实现返回首页功能

具体实现

1. 配置 pages.json
{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "商品列表"
      }
    },
    {
      "path": "pages/detail/detail",
      "style": {
        "navigationBarTitleText": "商品详情"
      }
    }
  ],
  "globalStyle": {
    "navigationBarTextStyle": "black",
    "navigationBarBackgroundColor": "#F8F8F8",
    "backgroundColor": "#F8F8F8"
  }
}
2. 实现首页(index.vue)
<template>
  <view class="index-page">
    <view class="goods-list">
      <view 
        v-for="goods in goodsList" 
        :key="goods.id" 
        class="goods-item"
        @tap="navigateToDetail(goods)"
      >
        <view class="goods-image">
          <image :src="goods.image" mode="aspectFill"></image>
        </view>
        <view class="goods-info">
          <text class="goods-name">{{ goods.name }}</text>
          <text class="goods-price">¥{{ goods.price }}</text>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      goodsList: [
        {
          id: 1,
          name: 'uni-app 开发实战',
          price: 99.9,
          image: 'https://img-cdn-tc.dcloud.net.cn/uploads/cover/007/762/762089b7-6c24-4010-a470-445d5b477f37.png',
description: 'uni-app 跨平台开发实战教程,从零开始学习 uni-app 开发'
        },
        {
          id: 2,
          name: 'Vue.js 核心原理',
          price: 89.9,
          image: 'https://img-cdn-tc.dcloud.net.cn/uploads/cover/007/762/762089b7-6c24-4010-a470-445d5b477f37.png',
description: '深入理解 Vue.js 核心原理,掌握前端框架精髓'
        },
        {
          id: 3,
          name: '微信小程序开发',
          price: 79.9,
          image: 'https://img-cdn-tc.dcloud.net.cn/uploads/cover/007/762/762089b7-6c24-4010-a470-445d5b477f37.png',
description: '微信小程序开发实战,从入门到精通'
        }
      ]
    };
  },
  methods: {
    navigateToDetail(goods) {
      // 传递商品信息
      uni.navigateTo({
        url: `/pages/detail/detail?id=${goods.id}&name=${encodeURIComponent(goods.name)}&price=${goods.price}&image=${encodeURIComponent(goods.image)}&description=${encodeURIComponent(goods.description)}`
      });
    }
  }
};
</script>

<style>
.index-page {
  padding: 20rpx;
}

.goods-list {
  display: flex;
  flex-direction: column;
  gap: 20rpx;
}

.goods-item {
  display: flex;
  background-color: #fff;
  border-radius: 10rpx;
  padding: 20rpx;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}

.goods-image {
  width: 160rpx;
  height: 160rpx;
  margin-right: 20rpx;
}

.goods-image image {
  width: 100%;
  height: 100%;
  border-radius: 8rpx;
}

.goods-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.goods-name {
  font-size: 32rpx;
  font-weight: 500;
  color: #333;
  margin-bottom: 10rpx;
  line-height: 1.4;
}

.goods-price {
  font-size: 36rpx;
  font-weight: bold;
  color: #ff4757;
}
</style>
3. 实现详情页(detail.vue)
<template>
  <view class="detail-page">
    <view class="goods-image">
      <image :src="goodsInfo.image" mode="aspectFill"></image>
    </view>
    <view class="goods-info">
      <text class="goods-name">{{ goodsInfo.name }}</text>
      <text class="goods-price">¥{{ goodsInfo.price }}</text>
      <text class="goods-description">{{ goodsInfo.description }}</text>
    </view>
    <view class="action-area">
      <button @tap="navigateBack" class="back-btn">返回首页</button>
      <button class="buy-btn">立即购买</button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      goodsInfo: {
        id: '',
        name: '',
        price: '',
        image: '',
description: ''
      }
    };
  },
  onLoad(options) {
    // 接收并解析参数
    this.goodsInfo = {
      id: options.id,
      name: decodeURIComponent(options.name),
      price: options.price,
      image: decodeURIComponent(options.image),
description: decodeURIComponent(options.description)
    };
    console.log('商品信息:', this.goodsInfo);
  },
  methods: {
    navigateBack() {
      // 返回上一页
      uni.navigateBack({
        delta: 1
      });
    }
  }
};
</script>

<style>
.detail-page {
  padding: 20rpx;
  background-color: #f5f5f5;
}

.goods-image {
  width: 100%;
  height: 400rpx;
  background-color: #fff;
  border-radius: 10rpx;
  overflow: hidden;
  margin-bottom: 20rpx;
}

.goods-image image {
  width: 100%;
  height: 100%;
}

.goods-info {
  background-color: #fff;
  border-radius: 10rpx;
  padding: 20rpx;
  margin-bottom: 20rpx;
}

.goods-name {
  font-size: 36rpx;
  font-weight: bold;
  color: #333;
  margin-bottom: 20rpx;
}

.goods-price {
  font-size: 40rpx;
  font-weight: bold;
  color: #ff4757;
  margin-bottom: 20rpx;
}

.goods-description {
  font-size: 28rpx;
  color: #666;
  line-height: 1.6;
}

.action-area {
  display: flex;
  gap: 20rpx;
}

.back-btn {
  flex: 1;
  padding: 20rpx;
  background-color: #f0f0f0;
  color: #333;
  border-radius: 10rpx;
  font-size: 32rpx;
}

.buy-btn {
  flex: 1;
  padding: 20rpx;
  background-color: #ff4757;
  color: #fff;
  border-radius: 10rpx;
  font-size: 32rpx;
}
</style>

运行效果

  1. 首页:显示商品列表,包含商品图片、名称和价格
  2. 点击商品:跳转到详情页,显示完整的商品信息
  3. 详情页:显示商品图片、名称、价格和描述,包含返回首页按钮
  4. 点击返回:回到首页

代码示例

1. 页面生命周期与参数传递结合示例

<template>
  <view class="life-cycle-params">
    <view class="title">页面生命周期与参数传递示例</view>
    <view class="params-info">
      <text>接收到的参数:</text>
      <text v-for="(value, key) in receivedParams" :key="key" class="param-item">
        {{ key }}: {{ value }}
      </text>
    </view>
    <view class="lifecycle-log">
      <text>生命周期执行顺序:</text>
      <text v-for="(log, index) in lifecycleLogs" :key="index" class="log-item">
        {{ index + 1 }}. {{ log }}
      </text>
    </view>
    <button @tap="navigateToNext" class="next-btn">跳转到下一页</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      receivedParams: {},
      lifecycleLogs: []
    };
  },
  onLoad(options) {
    this.lifecycleLogs.push('onLoad: 页面加载');
    this.receivedParams = options;
    console.log('页面加载,参数:', options);
  },
  onShow() {
    this.lifecycleLogs.push('onShow: 页面显示');
    console.log('页面显示');
  },
  onReady() {
    this.lifecycleLogs.push('onReady: 页面初次渲染完成');
    console.log('页面初次渲染完成');
  },
  onHide() {
    this.lifecycleLogs.push('onHide: 页面隐藏');
    console.log('页面隐藏');
  },
  onUnload() {
    this.lifecycleLogs.push('onUnload: 页面卸载');
    console.log('页面卸载');
  },
  methods: {
    navigateToNext() {
      uni.navigateTo({
        url: '/pages/next/next?from=page1&timestamp=' + Date.now()
      });
    }
  }
};
</script>

<style>
.life-cycle-params {
  padding: 20rpx;
}

.title {
  font-size: 36rpx;
  font-weight: bold;
  text-align: center;
  margin-bottom: 30rpx;
  color: #333;
}

.params-info {
  background-color: #f5f5f5;
  padding: 20rpx;
  border-radius: 10rpx;
  margin-bottom: 20rpx;
}

.param-item {
  display: block;
  margin-top: 10rpx;
  font-size: 28rpx;
  color: #666;
}

.lifecycle-log {
  background-color: #f5f5f5;
  padding: 20rpx;
  border-radius: 10rpx;
  margin-bottom: 30rpx;
}

.log-item {
  display: block;
  margin-top: 10rpx;
  font-size: 28rpx;
  color: #666;
}

.next-btn {
  padding: 20rpx;
  background-color: #007AFF;
  color: #fff;
  border-radius: 10rpx;
  font-size: 32rpx;
}
</style>

2. 多种导航方式示例

<template>
  <view class="navigation-demo">
    <view class="title">导航方式示例</view>
    <view class="nav-buttons">
      <button @tap="navigateTo" class="nav-btn">navigateTo</button>
      <button @tap="redirectTo" class="nav-btn">redirectTo</button>
      <button @tap="reLaunch" class="nav-btn">reLaunch</button>
      <button @tap="switchTab" class="nav-btn">switchTab</button>
      <button @tap="navigateBack" class="nav-btn">navigateBack</button>
    </view>
  </view>
</template>

<script>
export default {
  methods: {
    // 保留当前页面,跳转到应用内的某个页面
    navigateTo() {
      uni.navigateTo({
        url: '/pages/detail/detail?id=1&name=测试商品',
        animationType: 'slide-in-right',
        animationDuration: 300
      });
    },
    
    // 关闭当前页面,跳转到应用内的某个页面
    redirectTo() {
      uni.redirectTo({
        url: '/pages/detail/detail?id=2&name=重定向测试',
        animationType: 'fade-in',
        animationDuration: 300
      });
    },
    
    // 关闭所有页面,打开到应用内的某个页面
    reLaunch() {
      uni.reLaunch({
        url: '/pages/index/index'
      });
    },
    
    // 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
    switchTab() {
      uni.switchTab({
        url: '/pages/index/index'
      });
    },
    
    // 关闭当前页面,返回上一页面或多级页面
    navigateBack() {
      uni.navigateBack({
        delta: 1
      });
    }
  }
};
</script>

<style>
.navigation-demo {
  padding: 20rpx;
}

.title {
  font-size: 36rpx;
  font-weight: bold;
  text-align: center;
  margin-bottom: 30rpx;
  color: #333;
}

.nav-buttons {
  display: flex;
  flex-direction: column;
  gap: 20rpx;
}

.nav-btn {
  padding: 20rpx;
  background-color: #f0f0f0;
  color: #333;
  border-radius: 10rpx;
  font-size: 32rpx;
}
</style>

常见问题与解决方案

1. 页面跳转失败

  • 问题:调用导航 API 后页面没有跳转
  • 解决方案:检查目标页面路径是否正确,确保在 pages.json 中已配置

2. 参数传递失败

  • 问题:页面跳转后无法接收到参数
  • 解决方案:检查参数传递格式是否正确,确保参数名称与接收时一致

3. 中文参数乱码

  • 问题:传递中文参数时出现乱码
  • 解决方案:使用 encodeURIComponent() 编码参数,使用 decodeURIComponent() 解码参数

4. 对象参数传递失败

  • 问题:传递对象参数时接收不到完整数据
  • 解决方案:将对象转换为 JSON 字符串并编码后传递,接收时解码并解析

5. 页面栈溢出

  • 问题:多次使用 navigateTo 后页面栈溢出
  • 解决方案:合理使用不同的导航方式,对于不需要返回的页面使用 redirectTo

6. 导航动画不生效

  • 问题:配置了导航动画但不生效
  • 解决方案:检查动画类型是否正确,确保动画参数配置格式正确

学习总结

通过本章节的学习,您已经了解了:

  1. 页面创建:如何创建新页面、配置页面路由
  2. 导航跳转
    • uni.navigateTo:保留当前页面,跳转到新页面
    • uni.redirectTo:关闭当前页面,跳转到新页面
    • uni.reLaunch:关闭所有页面,跳转到新页面
    • uni.switchTab:跳转到 tabBar 页面
    • uni.navigateBack:返回上一页面
  3. 参数传递
    • 通过 URL 传递参数
    • 通过全局变量传递参数
    • 通过 Vuex 传递参数
    • 通过事件总线传递参数
  4. 页面生命周期:页面生命周期函数与参数传递的结合使用

现在您已经掌握了 uni-app 页面开发的基础知识,可以开始学习组件开发了。下一章节将详细介绍 uni-app 的组件开发方法,包括组件创建、组件通信和插槽使用等内容。

« 上一篇 uni-app 项目结构与配置 下一篇 » uni-app 组件开发