uni-app 路由管理
章节介绍
路由管理是单页应用开发中的重要组成部分,它负责管理页面之间的导航和跳转。uni-app 提供了一套完整的路由管理机制,基于 pages.json 配置文件和内置的导航 API,实现了跨平台的路由功能。本章节将详细介绍 uni-app 中的路由管理方法,包括路由配置、导航守卫、路由参数传递等核心知识点,以及如何实现权限控制路由,帮助你掌握 uni-app 应用中的路由管理技术。
核心知识点讲解
1. 路由配置
uni-app 的路由配置主要通过 pages.json 文件实现,该文件定义了应用的页面结构、导航栏样式、底部标签栏等。
基本路由配置
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": "详情页"
}
}
],
"tabBar": {
"color": "#999",
"selectedColor": "#007AFF",
"backgroundColor": "#fff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png"
},
{
"pagePath": "pages/mine/mine",
"text": "我的",
"iconPath": "static/tabbar/mine.png",
"selectedIconPath": "static/tabbar/mine-active.png"
}
]
}
}路由样式配置
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"navigationBarBackgroundColor": "#007AFF",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true,
"backgroundColor": "#f5f5f5",
"backgroundTextStyle": "dark"
}
}
]
}全局样式配置
{
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"pages": [
// 页面配置
]
}2. 导航 API
uni-app 提供了多种导航 API,用于实现页面之间的跳转和返回。
1. uni.navigateTo
保留当前页面,跳转到应用内的某个页面,使用 uni.navigateBack 可以返回到原页面。
uni.navigateTo({
url: '/pages/detail/detail?id=1&name=test',
success: function(res) {
console.log('跳转成功');
},
fail: function(err) {
console.error('跳转失败:', err);
}
});2. uni.redirectTo
关闭当前页面,跳转到应用内的某个页面。
uni.redirectTo({
url: '/pages/login/login',
success: function(res) {
console.log('跳转成功');
}
});3. uni.reLaunch
关闭所有页面,打开到应用内的某个页面。
uni.reLaunch({
url: '/pages/index/index',
success: function(res) {
console.log('跳转成功');
}
});4. uni.switchTab
跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。
uni.switchTab({
url: '/pages/index/index',
success: function(res) {
console.log('跳转成功');
}
});5. uni.navigateBack
关闭当前页面,返回上一页面或多级页面。
// 返回上一页
uni.navigateBack({
delta: 1
});
// 返回上两页
uni.navigateBack({
delta: 2
});3. 路由参数传递
1. 通过 URL 参数传递
// 跳转时传递参数
uni.navigateTo({
url: '/pages/detail/detail?id=1&name=test'
});
// 接收参数(在 detail 页面)
export default {
onLoad(options) {
console.log(options.id); // 1
console.log(options.name); // test
}
};2. 通过事件通道传递
对于复杂数据的传递,可以使用事件通道(EventChannel)。
// 跳转时传递事件通道
uni.navigateTo({
url: '/pages/detail/detail',
events: {
// 监听来自 detail 页面的事件
receiveDataFromDetail: function(data) {
console.log('接收到来自详情页的数据:', data);
}
},
success: function(res) {
// 向 detail 页面发送数据
res.eventChannel.emit('sendDataToDetail', {
data: '来自首页的数据'
});
}
});
// 接收事件通道(在 detail 页面)
export default {
onLoad() {
const eventChannel = this.getOpenerEventChannel();
// 监听来自首页的事件
eventChannel.on('sendDataToDetail', function(data) {
console.log('接收到来自首页的数据:', data);
});
// 向首页发送数据
eventChannel.emit('receiveDataFromDetail', {
data: '来自详情页的数据'
});
}
};3. 通过全局状态管理传递
对于需要在多个页面共享的数据,可以使用 Vuex 等状态管理工具。
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userInfo: null
},
mutations: {
setUserInfo(state, userInfo) {
state.userInfo = userInfo
}
},
actions: {
login({ commit }, userInfo) {
commit('setUserInfo', userInfo)
}
}
})
// 登录页面
this.$store.dispatch('login', userInfo)
// 其他页面
const userInfo = this.$store.state.userInfo4. 导航守卫
uni-app 提供了全局导航守卫,用于控制页面的访问权限和跳转逻辑。
全局前置守卫
在 main.js 中配置全局前置守卫:
// 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({
...App,
store
})
// 全局前置守卫
uni.addInterceptor('navigateTo', {
invoke(e) {
console.log('即将跳转到:', e.url);
// 可以在这里进行权限判断
},
success(e) {
console.log('跳转成功');
},
fail(e) {
console.log('跳转失败:', e);
}
});
app.$mount()页面级守卫
在页面的生命周期函数中实现页面级守卫:
export default {
onLoad() {
// 页面加载时的逻辑
},
onShow() {
// 页面显示时的逻辑
},
onHide() {
// 页面隐藏时的逻辑
},
onUnload() {
// 页面卸载时的逻辑
}
};5. 路由动画
uni-app 支持配置页面跳转的动画效果,提升用户体验。
全局路由动画
{
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8",
"app-plus": {
"animationType": "fade-in",
"animationDuration": 300
}
}
}页面级路由动画
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"app-plus": {
"animationType": "slide-in-right",
"animationDuration": 300
}
}
}
]
}实用案例分析
案例:实现权限控制路由
1. 路由配置
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/profile/profile",
"style": {
"navigationBarTitleText": "个人中心"
}
},
{
"path": "pages/settings/settings",
"style": {
"navigationBarTitleText": "设置"
}
}
],
"tabBar": {
"color": "#999",
"selectedColor": "#007AFF",
"backgroundColor": "#fff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png"
},
{
"pagePath": "pages/profile/profile",
"text": "我的",
"iconPath": "static/tabbar/profile.png",
"selectedIconPath": "static/tabbar/profile-active.png"
}
]
}
}2. 全局导航守卫实现
// 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({
...App,
store
})
// 需要登录权限的页面
const needAuthPages = [
'/pages/profile/profile',
'/pages/settings/settings'
];
// 全局前置守卫
uni.addInterceptor('navigateTo', {
invoke(e) {
const url = e.url;
const path = url.split('?')[0];
// 检查是否需要登录权限
if (needAuthPages.includes(path)) {
const isLoggedIn = store.state.user.isLoggedIn;
if (!isLoggedIn) {
// 未登录,跳转到登录页面
uni.navigateTo({
url: '/pages/login/login?redirect=' + encodeURIComponent(url)
});
// 阻止原跳转
return false;
}
}
}
});
// 对 switchTab 也添加拦截
uni.addInterceptor('switchTab', {
invoke(e) {
const url = e.url;
// 检查是否需要登录权限
if (needAuthPages.includes(url)) {
const isLoggedIn = store.state.user.isLoggedIn;
if (!isLoggedIn) {
// 未登录,跳转到登录页面
uni.navigateTo({
url: '/pages/login/login?redirect=' + encodeURIComponent(url)
});
// 阻止原跳转
return false;
}
}
}
});
app.$mount()3. 登录页面实现
<!-- pages/login/login.vue -->
<template>
<view class="container">
<view class="login-form">
<text class="title">登录</text>
<view class="form-item">
<input
type="text"
v-model="userInfo.username"
placeholder="请输入用户名"
class="input"
/>
</view>
<view class="form-item">
<input
type="password"
v-model="userInfo.password"
placeholder="请输入密码"
class="input"
/>
</view>
<text v-if="error" class="error-message">{{ error }}</text>
<button
@click="handleLogin"
:disabled="loading"
class="login-button"
>
{{ loading ? '登录中...' : '登录' }}
</button>
</view>
</view>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
data() {
return {
userInfo: {
username: '',
password: ''
},
redirectUrl: ''
}
},
computed: {
...mapState('user', ['loading', 'error', 'isLoggedIn'])
},
onLoad(options) {
// 获取重定向 URL
if (options.redirect) {
this.redirectUrl = decodeURIComponent(options.redirect);
}
},
watch: {
// 监听认证状态变化,登录成功后跳转到原页面或首页
isAuthenticated(newVal) {
if (newVal) {
if (this.redirectUrl) {
// 跳转到原页面
if (this.redirectUrl.includes('/pages/tabbar/')) {
uni.switchTab({
url: this.redirectUrl
});
} else {
uni.navigateTo({
url: this.redirectUrl
});
}
} else {
// 跳转到首页
uni.switchTab({
url: '/pages/index/index'
});
}
}
}
},
methods: {
...mapActions('user', ['login']),
handleLogin() {
if (!this.userInfo.username || !this.userInfo.password) {
uni.showToast({
title: '请输入用户名和密码',
icon: 'none'
});
return;
}
this.login(this.userInfo)
.catch(error => {
console.error('Login failed:', error);
});
}
}
}
</script>
<style scoped>
.container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx;
background-color: #f5f5f5;
}
.login-form {
width: 100%;
max-width: 500rpx;
background-color: #fff;
padding: 60rpx;
border-radius: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.title {
font-size: 36rpx;
font-weight: bold;
text-align: center;
margin-bottom: 60rpx;
color: #333;
}
.form-item {
margin-bottom: 40rpx;
}
.input {
width: 100%;
height: 80rpx;
border: 2rpx solid #e0e0e0;
border-radius: 10rpx;
padding: 0 20rpx;
font-size: 28rpx;
}
.error-message {
color: #ff4d4f;
font-size: 24rpx;
margin-bottom: 30rpx;
display: block;
}
.login-button {
width: 100%;
height: 80rpx;
background-color: #409eff;
color: #fff;
font-size: 28rpx;
font-weight: bold;
border-radius: 10rpx;
margin-top: 20rpx;
}
.login-button:disabled {
background-color: #c0c4cc;
}
</style>4. 个人中心页面实现
<!-- pages/profile/profile.vue -->
<template>
<view class="container">
<view class="user-info">
<image :src="userInfo.avatar || '/static/avatar/default.png'" class="avatar"></image>
<text class="username">{{ userInfo.username || '用户' }}</text>
</view>
<view class="menu-list">
<view class="menu-item" @click="navigateToSettings">
<text class="menu-text">设置</text>
<text class="menu-arrow">></text>
</view>
<view class="menu-item" @click="handleLogout">
<text class="menu-text">退出登录</text>
<text class="menu-arrow">></text>
</view>
</view>
</view>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
computed: {
...mapState('user', ['userInfo', 'isLoggedIn'])
},
onLoad() {
// 检查登录状态
if (!this.isLoggedIn) {
uni.navigateTo({
url: '/pages/login/login?redirect=/pages/profile/profile'
});
}
},
methods: {
...mapActions('user', ['logout']),
navigateToSettings() {
uni.navigateTo({
url: '/pages/settings/settings'
});
},
handleLogout() {
uni.showModal({
title: '退出登录',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
this.logout()
.then(() => {
uni.switchTab({
url: '/pages/index/index'
});
})
.catch(error => {
console.error('Logout failed:', error);
});
}
}
});
}
}
}
</script>
<style scoped>
.container {
flex: 1;
padding: 40rpx;
background-color: #f5f5f5;
}
.user-info {
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx;
background-color: #fff;
border-radius: 20rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.avatar {
width: 160rpx;
height: 160rpx;
border-radius: 50%;
margin-bottom: 20rpx;
}
.username {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.menu-list {
background-color: #fff;
border-radius: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.menu-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 40rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-text {
font-size: 28rpx;
color: #333;
}
.menu-arrow {
font-size: 24rpx;
color: #999;
}
</style>常见问题与解决方案
1. 路由跳转失败
问题:调用导航 API 时跳转失败,可能的原因包括路径错误、页面不存在、权限不足等。
解决方案:
- 检查
pages.json中是否正确配置了页面路径 - 确保页面文件存在且路径正确
- 检查是否有导航守卫阻止了跳转
- 对于需要权限的页面,确保用户已登录
2. 路由参数传递失败
问题:页面跳转时参数传递失败,可能的原因包括参数格式错误、参数过大、参数包含特殊字符等。
解决方案:
- 对于简单参数,使用 URL 查询参数传递
- 对于复杂参数,使用事件通道或全局状态管理传递
- 对于包含特殊字符的参数,使用
encodeURIComponent编码 - 对于大型数据,避免使用 URL 参数传递,改用其他方式
3. 导航守卫不生效
问题:配置的导航守卫不生效,可能的原因包括拦截器配置错误、拦截器返回值处理不当等。
解决方案:
- 确保正确配置了导航拦截器
- 对于需要阻止跳转的情况,返回
false - 对不同的导航 API(navigateTo、switchTab 等)分别添加拦截器
- 检查拦截器的执行顺序和逻辑
4. 页面栈溢出
问题:频繁使用 navigateTo 跳转页面,导致页面栈溢出。
解决方案:
- 合理使用不同的导航 API,对于不需要返回的页面使用
redirectTo - 对于需要重置页面栈的场景使用
reLaunch - 定期使用
navigateBack关闭不需要的页面 - 监控页面栈的深度,避免过度跳转
代码优化建议
1. 路由配置模块化
// 优化前:单一 pages.json 文件
{
"pages": [
// 大量页面配置
]
}
// 优化后:使用配置文件拆分
// config/routes.js
export const pages = [
{
path: "pages/index/index",
style: {
navigationBarTitleText: "首页"
}
},
// 其他页面配置
];
export const tabBar = {
color: "#999",
selectedColor: "#007AFF",
backgroundColor: "#fff",
borderStyle: "black",
list: [
// 标签栏配置
]
};
// 然后通过构建工具生成 pages.json2. 导航工具函数封装
// 优化前:直接调用导航 API
uni.navigateTo({ url: '/pages/detail/detail?id=1' });
// 优化后:封装导航工具函数
// utils/navigation.js
export const navigateTo = (url, params = {}) => {
// 构建带参数的 URL
const queryString = Object.keys(params)
.map(key => `${key}=${encodeURIComponent(params[key])}`)
.join('&');
const fullUrl = queryString ? `${url}?${queryString}` : url;
uni.navigateTo({ url: fullUrl });
};
export const navigateToWithAuth = (url, params = {}) => {
// 检查登录状态
const isLoggedIn = store.state.user.isLoggedIn;
if (!isLoggedIn) {
uni.navigateTo({
url: `/pages/login/login?redirect=${encodeURIComponent(url)}`
});
return;
}
navigateTo(url, params);
};
// 使用
import { navigateTo, navigateToWithAuth } from '@/utils/navigation';
navigateTo('/pages/detail/detail', { id: 1, name: 'test' });
navigateToWithAuth('/pages/profile/profile');3. 路由参数解析优化
// 优化前:直接使用 onLoad 中的 options
onLoad(options) {
const id = options.id;
const name = options.name;
// 处理参数
}
// 优化后:封装参数解析工具
// utils/params.js
export const parseParams = (options) => {
const params = {};
Object.keys(options).forEach(key => {
let value = options[key];
// 尝试解析 JSON 字符串
try {
value = JSON.parse(value);
} catch (e) {
// 不是 JSON 字符串,保持原值
}
params[key] = value;
});
return params;
};
// 使用
import { parseParams } from '@/utils/params';
onLoad(options) {
const params = parseParams(options);
const id = params.id;
const name = params.name;
// 处理参数
}4. 导航守卫集中管理
// 优化前:分散的导航守卫
uni.addInterceptor('navigateTo', {
invoke(e) {
// 处理逻辑
}
});
uni.addInterceptor('switchTab', {
invoke(e) {
// 处理逻辑
}
});
// 优化后:集中管理导航守卫
// utils/routeGuard.js
import store from '@/store';
// 需要登录权限的页面
const needAuthPages = [
'/pages/profile/profile',
'/pages/settings/settings'
];
// 检查权限
const checkAuth = (url) => {
const path = url.split('?')[0];
if (needAuthPages.includes(path)) {
const isLoggedIn = store.state.user.isLoggedIn;
if (!isLoggedIn) {
uni.navigateTo({
url: '/pages/login/login?redirect=' + encodeURIComponent(url)
});
return false;
}
}
return true;
};
// 注册导航守卫
export const registerRouteGuards = () => {
// 对 navigateTo 添加拦截
uni.addInterceptor('navigateTo', {
invoke(e) {
return checkAuth(e.url);
}
});
// 对 switchTab 添加拦截
uni.addInterceptor('switchTab', {
invoke(e) {
return checkAuth(e.url);
}
});
// 对 redirectTo 添加拦截
uni.addInterceptor('redirectTo', {
invoke(e) {
return checkAuth(e.url);
}
});
};
// 使用
// main.js
import { registerRouteGuards } from '@/utils/routeGuard';
// 注册导航守卫
registerRouteGuards();章节总结
本章节详细介绍了 uni-app 中的路由管理方法,包括:
- 路由配置:通过
pages.json文件配置应用的页面结构、导航栏样式和底部标签栏 - 导航 API:使用
uni.navigateTo、uni.redirectTo、uni.reLaunch、uni.switchTab和uni.navigateBack等 API 实现页面跳转 - 路由参数传递:通过 URL 参数、事件通道和全局状态管理传递参数
- 导航守卫:使用全局拦截器和页面生命周期函数实现路由守卫
- 路由动画:配置页面跳转的动画效果,提升用户体验
- 实用案例:通过实现权限控制路由,展示了路由管理在实际应用中的使用
- 常见问题与解决方案:针对路由跳转失败、参数传递失败、导航守卫不生效和页面栈溢出等问题提供了解决方案
- 代码优化建议:提供了路由配置模块化、导航工具函数封装、路由参数解析优化和导航守卫集中管理的优化建议
通过本章节的学习,你应该能够掌握 uni-app 中的路由管理方法,有效地配置和使用路由功能,实现页面之间的流畅跳转和权限控制。在实际开发中,应根据应用的具体需求,合理选择和使用不同的路由管理策略,确保应用的导航体验流畅、可靠。