第2章 2.5 综合实战:个人主页(多页面跳转)

🎯 开场:为什么你的个人主页需要"会跳转"?

你有没有这种感觉:看了一个很棒的个人主页,点来点去,发现每个板块都能跳转——点头像进个人资料,点作品进作品集,点设置进设置页。但自己写的时候,所有内容全堆在一个页面上,往下滑半天找不到重点。

这就是多页面跳转的价值:让信息有序组织,用户想看什么就点什么,而不是被海量信息埋没。

上一章我们学会了把数据存到本地(uni.setStorage),但存了数据还得能跳转去不同页面展示吧?这一章我们就把「存」和「跳」串起来,做一个真正的多页面个人主页。

学完本文,你将能够:
- 说出页面跳转的原理(类比:快递从A站发到B站)
- 写出带参数跳转的完整代码
- 独立做出一个 3-4 个页面的个人主页 demo


🧱 基础:页面跳转核心概念(25分钟)

什么是页面跳转?

生活类比:页面就像房间,A房间的人想去B房间,得先「开门」再「走过去」。在 uniapp 里,这个「开门走过去」的动作就是 uni.nav\n\n![Simple tech illustration expla](https://blog.xxyye.com/wp-content/uploads/2026/06/3e775781ed78731.png)\n\n![AI comic creation scene, creat](https://blog.xxyye.com/wp-content/uploads/2026/06/f9dd8f5ce959980.png)\n\nigateTo

3种跳转方式对比

方式 适合场景 特点
navigateTo 一般跳转 能返回,保留上一页
redirectTo 替代当前页 关闭当前页,新页无法返回
reLaunch 清空所有页 相当于重启,什么都没有了

说白了
- navigateTo = 你去隔壁房间串门,随时能回来
- redirectTo = 你从这个门出去,把这门焊死
- reLaunch = 你直接把所有门都炸了,从头来过

核心代码:最简单的跳转

页面A(index.vue)

<template>
<view class="container">
<text class="title">我是主页</text>
<!-- 点击这个按钮,跳转到个人资料页 -->
<button @tap="goToProfile">查看个人资料</button>
</view>
</template>

<script>
export default {
data() {
return {
  username: "小明"
}
},
methods: {
goToProfile() {
  // 跳转到 profile 页面
  uni.navigateTo({
    url: '/pages/profile/profile?name=' + this.username
  })
}
}
}
</script>

页面B(profile.vue)

<template>
<view class="container">
<text class="title">个人资料</text>
<text>用户名:{{ username }}</text>
<button @tap="goBack">返回主页</button>
</view>
</template>

<script>
export default {
data() {
return {
  username: ""
}
},
onLoad(options) {
// 接收从主页传来的参数
this.username = options.name || "未填写"
},
methods: {
goBack() {
  uni.navigateBack()
}
}
}
</script>

这 20 行代码实现了什么?
- 点击主页按钮 → 自动拼接 URL 参数 ?name=小明 → 跳转到 profile 页
- profile 页在 onLoad 生命周期里,读取 URL 参数,显示「用户名:小明」
- 点击返回 → navigateBack 回到主页

传参的两种方式

方式1:URL 参数拼接(上面用的)

uni.navigateTo({
url: '/pages/profile/profile?name=小明&age=18'
})
// 获取:onLoad(options) { console.log(options.name) }

方式2:本地存储传参(更灵活)

// A页面:存
uni.setStorage({
key: 'userInfo',
data: { name: '小明', age: 18 }
})
uni.navigateTo({ url: '/pages/profile/profile' })

// B页面:取
onLoad() {
const res = uni.getStorageSync('userInfo')
console.log(res.name) // 小明
}

什么时候用哪种?
- 参数简单(就一两个字符串)→ URL 拼接
- 参数复杂(对象、数组、或者数据量大)→ 本地存储

页面栈是什么?

你可以理解为浏览器记录你走过哪些房间的清单

页面栈 = [首页, 个人资料页, 设置页]
      ↓
当前在设置页
  • navigateTo:往栈里加一页(栈长度 +1)
  • navigateBack弹出一页(栈长度 -1)
  • redirectTo替换当前页(栈长度不变)

reLaunch 最狠,直接清空栈,重新开始

TabBar 页面跳转(特殊但常用)

如果你的个人主页底部有「首页/个人/我的」这种 Tab 栏,用 switchTab

// 跳转到 tabBar 页面(不能用 navigateTo!!!)
uni.switchTab({
url: '/pages/index/index'
})

注意! tabBar 页面不能在 URL 里带参数,因为 switchTab 根本不支持传参。这时候就必须用本地存储:

// 跳转前存数据
uni.setStorage({ key: 'tabData', data: '要传的东西' })
uni.switchTab({ url: '/pages/index/index' })

// 目标 tabBar 页的 onLoad 里取
onLoad() {
const data = uni.getStorageSync('tabData')
}

🔥 实战:3个递进项目(35分钟)

项目1(5分钟):两个页面的互相跳转

目标:主页点击「关于我」跳转到关于页,关于页点「返回」回来。

项目结构

pages/
├── index/
│   └── index.vue      # 主页
└── about/
└── about.vue      # 关于页

主页代码(index.vue)

<template>
<view class="container">
<text class="big-title">🏠 个人主页</text>
<view class="info-box">
  <text>这里是主页内容</text>
</view>
<button type="primary" @tap="goAbout">关于我 →</button>
</view>
</template>

<script>
export default {
methods: {
goAbout() {
  uni.navigateTo({ url: '/pages/about/about' })
}
}
}
</script>

<style>
.container { padding: 40rpx; }
.big-title { font-size: 48rpx; font-weight: bold; }
.info-box { 
background: #f5f5f5; 
padding: 30rpx; 
margin: 30rpx 0;
border-radius: 16rpx;
}
</style>

关于页代码(about.vue)

<template>
<view class="container">
<text class="big-title">👤 关于我</text>
<view class="info-box">
  <text>我是uniapp学习者,正在做个人主页练习。</text>
</view>
<button type="warn" @tap="goBack">← 返回主页</button>
</view>
</template>

<script>
export default {
methods: {
goBack() {
  uni.navigateBack()
}
}
}
</script>

<style>
.container { padding: 40rpx; }
.big-title { font-size: 48rpx; font-weight: bold; }
.info-box { 
background: #fff3e0; 
padding: 30rpx; 
margin: 30rpx 0;
border-radius: 16rpx;
}
</style>

预期输出:主页显示「🏠 个人主页」→ 点击按钮 → 显示「👤 关于我」→ 点击返回 → 回到主页。

一句话解释:主页用 navigateTo 跳过去,关于页用 navigateBack 弹回来,这就是最基础的「来"回"。


项目2(15分钟):带用户信息的个人主页(3页面)

目标:做一个完整的个人主页,包含:首页、个人资料页、作品集页。

页面关系

首页 ←→ 个人资料(互跳)
↓
作品集

pages.json 配置(必须先配置!)

{
"pages": [
{ "path": "pages/index/index" },
{ "path": "pages/profile/profile" },
{ "path": "pages/works/works" }
],
"tabBar": {
"list": [
  { "text": "首页", "pagePath": "pages/index/index" },
  { "text": "资料", "pagePath": "pages/profile/profile" },
  { "text": "作品", "pagePath": "pages/works/works" }
]
}
}

首页(index.vue)

<template>
<view class="container">
<view class="header">
  <image class="avatar" src="/static/avatar.png" mode="aspectFill"></image>
  <text class="name">{{ userInfo.name }}</text>
  <text class="bio">{{ userInfo.bio }}</text>
</view>

<view class="section">
  <text class="section-title">📌 快捷入口</text>
  <view class="btn-row">
    <button size="mini" @tap="goProfile">编辑资料</button>
    <button size="mini" @tap="goWorks">查看作品</button>
  </view>
</view>
</view>
</template>

<script>
export default {
data() {
return {
  userInfo: {
    name: "小明",
    bio: "uniapp学习者 · 正在成长"
  }
}
},
onLoad() {
// 从本地存储读取最新资料
const info = uni.getStorageSync('userInfo')
if (info) {
  this.userInfo = info
}
},
methods: {
goProfile() {
  uni.switchTab({ url: '/pages/profile/profile' })
},
goWorks() {
  uni.switchTab({ url: '/pages/works/works' })
}
}
}
</script>

<style>
.container { padding: 40rpx; min-height: 100vh; background: #fafafa; }
.header { text-align: center; padding: 60rpx 0; }
.avatar { 
width: 160rpx; height: 160rpx; 
border-radius: 80rpx; background: #ddd;
}
.name { display: block; font-size: 40rpx; font-weight: bold; margin: 20rpx 0 10rpx; }
.bio { display: block; color: #666; font-size: 28rpx; }
.section { margin-top: 40rpx; }
.section-title { font-size: 32rpx; font-weight: bold; display: block; margin-bottom: 20rpx; }
.btn-row { display: flex; gap: 20rpx; }
</style>

个人资料页(profile.vue)

<template>
<view class="container">
<text class="title">👤 个人资料</text>

<view class="form-item">
  <text class="label">昵称</text>
  <input v-model="formData.name" placeholder="请输入昵称" />
</view>

<view class="form-item">
  <text class="label">简介</text>
  <textarea v-model="formData.bio" placeholder="介绍一下自己" />
</view>

<view class="form-item">
  <text class="label">城市</text>
  <input v-model="formData.city" placeholder="你所在的城市" />
</view>

<button type="primary" @tap="saveInfo">保存</button>
<text class="hint">保存后首页会同步更新</text>
</view>
</template>

<script>
export default {
data() {
return {
  formData: {
    name: "",
    bio: "",
    city: ""
  }
}
},
onLoad() {
// 读取已有数据
const info = uni.getStorageSync('userInfo')
if (info) {
  this.formData = info
}
},
methods: {
saveInfo() {
  uni.setStorage({
    key: 'userInfo',
    data: this.formData,
    success: () => {
      uni.showToast({ title: '保存成功!', icon: 'success' })
    }
  })
}
}
}
</script>

<style>
.container { padding: 40rpx; }
.title { font-size: 40rpx; font-weight: bold; display: block; margin-bottom: 40rpx; }
.form-item { margin-bottom: 40rpx; }
.label { display: block; font-size: 28rpx; color: #666; margin-bottom: 10rpx; }
input, textarea { 
border: 1px solid #ddd; 
padding: 20rpx; 
border-radius: 8rpx;
font-size: 32rpx;
}
textarea { height: 160rpx; }
.hint { display: block; text-align: center; color: #999; font-size: 24rpx; margin-top: 20rpx; }
</style>

作品集页(works.vue)

<template>
<view class="container">
<text class="title">🎨 我的作品</text>

<view class="works-list">
  <view v-for="(work, index) in works" :key="index" class="work-item">
    <image :src="work.image" mode="aspectFill" class="work-image"></image>
    <view class="work-info">
      <text class="work-title">{{ work.title }}</text>
      <text class="work-desc">{{ work.desc }}</text>
    </view>
  </view>
</view>

<view v-if="works.length === 0" class="empty">
  <text>还没有作品,先去创作吧!</text>
</view>
</view>
</template>

<script>
export default {
data() {
return {
  works: [
    { title: "待办清单App", desc: "用uniapp做的第一个完整应用", image: "/static/work1.png" },
    { title: "天气查询工具", desc: "调用免费API显示天气", image: "/static/work2.png" },
    { title: "个人主页", desc: "本章的综合实战项目", image: "/static/work3.png" }
  ]
}
}
}
</script>

<style>
.container { padding: 40rpx; }
.title { font-size: 40rpx; font-weight: bold; display: block; margin-bottom: 40rpx; }
.work-item { 
display: flex; 
gap: 20rpx; 
margin-bottom: 30rpx;
padding: 20rpx;
background: #fff;
border-radius: 12rpx;
}
.work-image { width: 120rpx; height: 120rpx; border-radius: 8rpx; background: #eee; }
.work-info { flex: 1; }
.work-title { display: block; font-size: 32rpx; font-weight: bold; }
.work-desc { display: block; font-size: 26rpx; color: #666; margin-top: 8rpx; }
.empty { text-align: center; color: #999; padding: 100rpx 0; }
</style>

预期输出
- 底部 Tab 栏:首页 / 资料 / 作品
- 首页显示头像和昵称
- 在资料页修改昵称保存 → 切到首页看到昵称已更新
- 作品页显示3个作品卡片

一句话解释:三个页面通过 Tab 栏串联,资料页修改数据后存到本地,首页读取显示——这就是「数据跟着页面跑」的感觉。


项目3(15分钟):给作品集加"收藏功能"

需求:在作品集页点击❤️收藏按钮,收藏状态存入本地,首页底部显示收藏数量。

改进 works.vue(加收藏功能)

<template>
<view class="container">
<text class="title">🎨 我的作品</text>

<view class="works-list">
  <view v-for="(work, index) in works" :key="index" class="work-item">
    <image :src="work.image" mode="aspectFill" class="work-image"></image>
    <view class="work-info">
      <text class="work-title">{{ work.title }}</text>
      <text class="work-desc">{{ work.desc }}</text>
    </view>
    <!-- 收藏按钮 -->
    <view class="action" @tap="toggleFavorite(index)">
      <text :class="work.favorited ? 'heart active' : 'heart'">
        {{ work.favorited ? '❤️' : '🤍' }}
      </text>
    </view>
  </view>
</view>
</view>
</template>

<script>
export default {
data() {
return {
  works: []
}
},
onLoad() {
// 读取作品列表
const savedWorks = uni.getStorageSync('myWorks')
if (savedWorks) {
  this.works = savedWorks
} else {
  // 默认作品
  this.works = [
    { title: "待办清单App", desc: "用uniapp做的第一个完整应用", image: "/static/work1.png", favorited: false },
    { title: "天气查询工具", desc: "调用免费API显示天气", image: "/static/work2.png", favorited: false },
    { title: "个人主页", desc: "本章的综合实战项目", image: "/static/work3.png", favorited: false }
  ]
}
},
methods: {
toggleFavorite(index) {
  // 反转收藏状态
  this.works[index].favorited = !this.works[index].favorited
  // 保存到本地
  uni.setStorage({
    key: 'myWorks',
    data: this.works
  })
  // 通知首页更新
  uni.$emit('worksUpdated', this.works)
}
}
}
</script>

改进 index.vue(显示收藏数量)

<template>
<view class="container">
<!-- 原有内容... -->
<view class="stats-bar">
  <text>作品数:{{ worksCount }}</text>
  <text>收藏数:{{ favoritesCount }}</text>
</view>
</view>
</template>

<script>
export default {
data() {
return {
  worksCount: 0,
  favoritesCount: 0
}
},
onLoad() {
this.loadData()
// 监听收藏变化
uni.$on('worksUpdated', (works) => {
  this.worksCount = works.length
  this.favoritesCount = works.filter(w => w.favorited).length
})
},
onShow() {
this.loadData()
},
methods: {
loadData() {
  const works = uni.getStorageSync('myWorks') || []
  this.worksCount = works.length
  this.favoritesCount = works.filter(w => w.favorited).length
}
}
}
</script>

<style>
.stats-bar { 
display: flex; 
justify-content: space-around;
margin-top: 40rpx;
padding: 30rpx;
background: #fff;
border-radius: 12rpx;
}
.heart.active { color: #f00; }
</style>

预期输出
- 作品卡片右侧有🤍按钮
- 点击后变成❤️
- 首页底部显示「收藏数:1」
- 退出再打开,收藏状态保留

一句话解释:作品数据存本地,状态变化实时同步到首页——这就是「本地存储 + 页面通信」的经典组合。


💪 进阶:常见坑 + 调试技巧(20分钟)

坑1:tabBar 页面用 navigateTo 跳转 ❌

// ❌ 错误:tabBar页面不支持navigateTo
uni.navigateTo({ url: '/pages/index/index' })

// ✅ 正确:tabBar页面只能用switchTab
uni.switchTab({ url: '/pages/index/index' })

原因:tabBar 是小程序/uniapp的底部导航,机制和普通页面不同,navigateTo 会被框架直接无视。


坑2:URL 参数带中文没 encode ❌

// ❌ 错误:中文参数会乱码
uni.navigateTo({ url: '/pages/profile/profile?name=小明' })

// ✅ 正确: encodeURIComponent 处理中文
uni.navigateTo({ 
url: '/pages/profile/profile?name=' + encodeURIComponent('小明') 
})
// 接收时不用解码,框架自动处理

坑3:onLoad 和 onShow 混淆 ❌

// ❌ 错误:在 onShow 里读取onLoad设置的data(会拿不到)
onShow() {
console.log(this.username) // undefined!因为还没执行onLoad
}

// ✅ 正确:数据初始化放 onLoad
onLoad() {
this.username = '小明'
}
生命周期 什么时候调用 适合做什么
onLoad 页面首次加载 获取参数、读取数据、初始化
onShow 每次页面显示 刷新数据、监听变化
onReady 页面渲染完成 操作DOM(uniapp里一般不用)

坑4:navigateBack 不知道传 delta ❌

// 如果你用 redirectTo 跳转了2次,想一口气返回2页:
// ❌ 错误:navigateBack() 默认只返回1页
uni.navigateBack()

// ✅ 正确:指定 delta 参数
uni.navigateBack({ delta: 2 })

坑5:页面跳转前没等异步存储完成 ❌

// ❌ 错误:跳转和存储同时发生,可能跳转时数据还没存好
uni.setStorage({ key: 'info', data: 'xxx' })
uni.switchTab({ url: '/pages/index/index' })

// ✅ 正确:在 success 回调里跳转
uni.setStorage({
key: 'info',
data: 'xxx',
success: () => {
uni.switchTab({ url: '/pages/index/index' })
}
})

调试技巧:用 console.log 打印页面参数

onLoad(options) {
console.log('页面参数:', options)
console.log('name参数是:', options.name)
// 加上这行,你可以在HBuilderX控制台看到实际传了什么
}

✏️ 练习题 + 作业题(7分钟)

练习1(1分钟):修改跳转目标

  • 输入:在 index.vue 里把 goAbout() 改成跳转到 /pages/profile/profile
  • 预期输出:点击按钮跳转到个人资料页
  • 提示:改 url 路径即可

练习2(2分钟):加一个判断

  • 输入:在项目1的 about.vue 里,加一个判断,如果用户名为空则显示「匿名用户」
  • 预期输出:没传参数时显示「匿名用户」
  • 提示:使用三元运算符 this.username || '匿名用户'

练习3(2分钟):给作品集加个「删除」功能

  • 输入:在 works.vue 的每个作品卡片上加删除按钮
  • 预期输出:点击删除后作品从列表消失
  • 提示:用 splice 方法删除数组元素,然后重新存本地

练习4(2分钟):统计访问次数

  • 输入:在首页用本地存储记录用户访问首页的次数
  • 预期输出:显示「你已经来过 X 次」
  • 提示:用 getStorageSync 读取后 +1,再用 setStorage 存回去

练习5(3分钟):分析报错

  • 输入:运行以下代码,页面停留在A,按钮点击没反应
// A页面
uni.switchTab({ url: '/pages/profile/profile' })

// B页面(profile.json里配置了tabBar)
onLoad(options) {
console.log(options.name) // 想获取name但得不到
}
  • 预期输出:说出哪里错了并修复
  • 提示:switchTab 不支持 URL 参数,需要用什么方式传参?

作业:做一个「个人简历展示器」

需求描述:做一个包含4个页面的个人简历展示工具,页面包括:首页(封面)、教育经历、工作经历、技能特长。

功能点
1. 首页点击「查看简历」跳转到教育经历页
2. 教育经历页点击「下一步」跳转到工作经历(URL参数传学校名)
3. 工作经历页点击「下一步」跳转到技能特长
4. 技能特长页点击「返回首页」用 reLaunch 回到首页

加分项
1. 每个页面能编辑自己的信息,存到本地
2. 首页能展示所有页面填写的数据摘要

验收标准
- 4个页面能正常跳转
- 数据编辑后能保存
- 再次打开能从本地读取之前填写的内容


📚 总结 + 资源(5分钟)

本章核心3点

  1. 页面跳转3兄弟navigateTo(能返回)、redirectTo(替代)、reLaunch(清空)
  2. 传参两种方式:URL拼接适合简单数据,本地存储适合复杂数据
  3. tabBar跳转用switchTab:别用错API,不然页面根本没反应

延伸学习资源

互动钩子

你在写个人主页的时候,最纠结的是哪个页面之间的跳转逻辑?是信息传不过去,还是返回的时候数据丢了?评论区聊聊,老粉优先回复!


下章预告:第三章我们要学一个新技能——怎么从网上"拿"数据(uni.request 封装)。学会了它,你的个人主页就能展示实时天气、新闻资讯,而不只是本地存的那点东西了。从「存数据」到「拿数据」,敬请期待!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。