4.1 路由配置基础

4.1.1 pages.json配置详解

pages.json 是UniApp的核心配置文件,用于配置页面路由、窗口表现、底部导航等。

1. 基础配置结构

{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首页",
        "navigationBarBackgroundColor": "#007aff",
        "navigationBarTextStyle": "white",
        "backgroundColor": "#f8f8f8",
        "enablePullDownRefresh": true,
        "onReachBottomDistance": 50
      }
    },
    {
      "path": "pages/user/user",
      "style": {
        "navigationBarTitleText": "个人中心",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/detail/detail",
      "style": {
        "navigationBarTitleText": "详情页",
        "app-plus": {
          "bounce": "none",
          "titleNView": {
            "buttons": [
              {
                "text": "分享",
                "fontSize": "16px",
                "color": "#ffffff"
              }
            ]
          }
        }
      }
    }
  ],
  "globalStyle": {
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "UniApp教程",
    "navigationBarBackgroundColor": "#ffffff",
    "backgroundColor": "#f8f8f8",
    "app-plus": {
      "background": "#efeff4"
    }
  },
  "tabBar": {
    "color": "#7A7E83",
    "selectedColor": "#007aff",
    "borderStyle": "black",
    "backgroundColor": "#ffffff",
    "list": [
      {
        "pagePath": "pages/index/index",
        "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": "我的"
      }
    ]
  },
  "condition": {
    "current": 0,
    "list": [
      {
        "name": "详情页",
        "path": "pages/detail/detail",
        "query": "id=123&title=测试商品"
      }
    ]
  },
  "subPackages": [
    {
      "root": "subpkg",
      "pages": [
        {
          "path": "goods/list",
          "style": {
            "navigationBarTitleText": "商品列表"
          }
        }
      ]
    }
  ],
  "preloadRule": {
    "pages/index/index": {
      "network": "all",
      "packages": ["subpkg"]
    }
  }
}

2. 配置项详解

  • pages: 页面路径配置数组,第一个页面为应用入口页面
  • globalStyle: 全局默认窗口表现
  • tabBar: 底部导航配置
  • condition: 启动模式配置(开发工具中使用)
  • subPackages: 分包配置
  • preloadRule: 分包预下载规则

4.1.2 页面样式配置

1. 导航栏配置

{
  "style": {
    "navigationBarTitleText": "页面标题",
    "navigationBarBackgroundColor": "#007aff",
    "navigationBarTextStyle": "white",
    "navigationStyle": "default",
    "app-plus": {
      "titleNView": {
        "buttons": [
          {
            "text": "\ue601",
            "fontSrc": "/static/uni.ttf",
            "fontSize": "22px",
            "color": "#ffffff",
            "width": "40px"
          }
        ],
        "searchInput": {
          "backgroundColor": "rgba(255, 255, 255, 0.8)",
          "borderRadius": "20px",
          "placeholder": "请输入搜索内容",
          "disabled": false
        }
      }
    }
  }
}

2. 下拉刷新配置

{
  "style": {
    "enablePullDownRefresh": true,
    "backgroundTextStyle": "dark",
    "app-plus": {
      "pullToRefresh": {
        "support": true,
        "color": "#007aff",
        "style": "circle",
        "height": "50px",
        "range": "100px",
        "offset": "0px"
      }
    }
  }
}

3. 上拉加载配置

{
  "style": {
    "onReachBottomDistance": 50,
    "backgroundTextStyle": "dark"
  }
}

4.2 页面跳转与导航

4.2.1 编程式导航

1. uni.navigateTo() - 保留当前页面

<template>
  <view class="navigation-demo">
    <text class="title">编程式导航演示</text>
    
    <!-- 基础跳转 -->
    <view class="section">
      <text class="section-title">基础页面跳转</text>
      <button @click="navigateToDetail">跳转到详情页</button>
      <button @click="navigateToUser">跳转到用户页</button>
      <button @click="navigateToList">跳转到列表页</button>
    </view>
    
    <!-- 带参数跳转 -->
    <view class="section">
      <text class="section-title">带参数跳转</text>
      <button @click="navigateWithParams">跳转并传递参数</button>
      <button @click="navigateWithObject">跳转并传递对象</button>
      <button @click="navigateWithQuery">跳转并传递查询参数</button>
    </view>
    
    <!-- 条件跳转 -->
    <view class="section">
      <text class="section-title">条件跳转</text>
      <button @click="conditionalNavigate">登录后跳转</button>
      <button @click="checkAndNavigate">检查权限后跳转</button>
    </view>
    
    <!-- 动画跳转 -->
    <view class="section">
      <text class="section-title">动画跳转</text>
      <button @click="navigateWithAnimation">带动画跳转</button>
      <button @click="navigateSlideUp">上滑动画</button>
    </view>
  </view>
</template>

<script>
export default {
  name: 'NavigationDemo',
  data() {
    return {
      userInfo: {
        id: 123,
        name: '张三',
        avatar: '/static/avatar.jpg'
      }
    }
  },
  methods: {
    // 基础跳转
    navigateToDetail() {
      uni.navigateTo({
        url: '/pages/detail/detail',
        success: (res) => {
          console.log('跳转成功', res)
        },
        fail: (err) => {
          console.error('跳转失败', err)
          uni.showToast({
            title: '跳转失败',
            icon: 'error'
          })
        }
      })
    },
    
    navigateToUser() {
      uni.navigateTo({
        url: '/pages/user/user'
      })
    },
    
    navigateToList() {
      uni.navigateTo({
        url: '/pages/list/list'
      })
    },
    
    // 带参数跳转
    navigateWithParams() {
      const productId = 12345
      const productName = 'iPhone 15 Pro'
      
      uni.navigateTo({
        url: `/pages/detail/detail?id=${productId}&name=${encodeURIComponent(productName)}`
      })
    },
    
    navigateWithObject() {
      // 复杂对象需要序列化
      const productInfo = {
        id: 12345,
        name: 'iPhone 15 Pro',
        price: 8999,
        specs: {
          color: '深空黑色',
          storage: '256GB'
        }
      }
      
      uni.navigateTo({
        url: `/pages/detail/detail?data=${encodeURIComponent(JSON.stringify(productInfo))}`
      })
    },
    
    navigateWithQuery() {
      const params = {
        category: 'electronics',
        brand: 'apple',
        minPrice: 1000,
        maxPrice: 10000
      }
      
      const queryString = Object.keys(params)
        .map(key => `${key}=${encodeURIComponent(params[key])}`)
        .join('&')
      
      uni.navigateTo({
        url: `/pages/list/list?${queryString}`
      })
    },
    
    // 条件跳转
    conditionalNavigate() {
      // 检查登录状态
      const token = uni.getStorageSync('token')
      
      if (!token) {
        uni.showModal({
          title: '提示',
          content: '请先登录',
          confirmText: '去登录',
          success: (res) => {
            if (res.confirm) {
              uni.navigateTo({
                url: '/pages/login/login'
              })
            }
          }
        })
        return
      }
      
      // 已登录,跳转到目标页面
      uni.navigateTo({
        url: '/pages/profile/profile'
      })
    },
    
    checkAndNavigate() {
      // 检查用户权限
      const userRole = uni.getStorageSync('userRole')
      
      if (userRole !== 'admin') {
        uni.showToast({
          title: '权限不足',
          icon: 'error'
        })
        return
      }
      
      uni.navigateTo({
        url: '/pages/admin/admin'
      })
    },
    
    // 动画跳转
    navigateWithAnimation() {
      uni.navigateTo({
        url: '/pages/detail/detail',
        animationType: 'slide-in-right',
        animationDuration: 300
      })
    },
    
    navigateSlideUp() {
      uni.navigateTo({
        url: '/pages/modal/modal',
        animationType: 'slide-in-bottom',
        animationDuration: 250
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.navigation-demo {
  padding: 20rpx;
  
  .title {
    display: block;
    font-size: 32rpx;
    font-weight: bold;
    text-align: center;
    margin-bottom: 30rpx;
    color: #333;
  }
  
  .section {
    margin-bottom: 40rpx;
    padding: 20rpx;
    background-color: #f8f8f8;
    border-radius: 12rpx;
    
    .section-title {
      display: block;
      font-size: 28rpx;
      font-weight: bold;
      margin-bottom: 20rpx;
      color: #333;
    }
    
    button {
      width: 100%;
      margin-bottom: 15rpx;
      padding: 20rpx;
      background-color: #007aff;
      color: white;
      border: none;
      border-radius: 8rpx;
      font-size: 26rpx;
      
      &:last-child {
        margin-bottom: 0;
      }
      
      &:active {
        background-color: #0056cc;
      }
    }
  }
}
</style>

4.6 本章总结

4.6.1 学习要点回顾

1. 路由配置 - pages.json 是 UniApp 路由配置的核心文件 - pages 数组定义应用的页面路径和样式 - globalStyle 设置全局页面样式 - tabBar 配置底部导航栏 - condition 配置启动模式 - subPackages 实现分包加载 - preloadRule 配置分包预下载规则

2. 页面导航 - 编程式导航:使用 uni.navigateTo()uni.redirectTo()uni.reLaunch()uni.switchTab()uni.navigateBack() 等 API - 声明式导航:使用 <navigator> 组件实现页面跳转 - 页面栈管理:理解不同导航方式对页面栈的影响 - 参数传递:URL 参数、事件传参、存储传参等多种方式

3. TabBar 导航 - 原生 TabBar 配置和自定义 TabBar 组件 - TabBar 切换控制和状态管理 - 角标显示和动态更新 - 权限控制和条件跳转

4. 路由守卫 - 路由拦截器的实现和使用 - 前置守卫和后置守卫 - 权限验证系统 - 登录状态管理

4.6.2 实践练习

练习1:创建多页面应用

创建一个包含以下页面的应用: - 首页(TabBar) - 分类页(TabBar) - 购物车(TabBar,需要登录) - 个人中心(TabBar,需要登录) - 商品详情页 - 登录页 - 注册页

要求: 1. 配置 TabBar 导航 2. 实现页面间的跳转和参数传递 3. 添加登录验证 4. 实现购物车角标功能

练习2:实现路由权限控制

基于练习1,添加以下功能: 1. 创建路由拦截器 2. 实现登录状态检查 3. 添加权限验证系统 4. 创建管理员页面(需要管理员权限) 5. 实现动态权限更新

练习3:自定义TabBar组件

创建一个功能丰富的自定义TabBar: 1. 支持角标显示(数字和红点) 2. 支持中间凸起按钮 3. 支持动画效果 4. 适配不同设备的安全区域 5. 支持主题切换

4.6.3 常见问题解答

Q1:页面跳转时参数丢失怎么办?

A1:检查以下几点: - 确保参数正确编码,特殊字符需要使用 encodeURIComponent() - 复杂对象建议使用 JSON.stringify() 序列化后传递 - 大量数据建议使用本地存储或全局状态管理

Q2:TabBar 页面无法使用 navigateTo 跳转?

A2:TabBar 页面必须使用 uni.switchTab() 进行跳转,不能使用 uni.navigateTo()

Q3:如何实现页面返回时刷新数据?

A3:可以使用以下方法: - 在 onShow 生命周期中刷新数据 - 使用事件总线在页面间通信 - 使用全局状态管理(如 Vuex/Pinia)

Q4:自定义TabBar如何与原生TabBar共存?

A4:设置 "custom": true 后,原生TabBar会被隐藏,需要完全使用自定义组件。如果需要部分页面使用原生TabBar,建议使用条件渲染。

Q5:路由拦截器会影响性能吗?

A5:合理使用路由拦截器不会显著影响性能,但要注意: - 避免在拦截器中执行耗时操作 - 异步操作要正确处理 - 避免无限循环重定向

Q6:如何处理深层嵌套的页面返回?

A6:可以使用以下方法: - uni.navigateBack({ delta: n }) 返回多层 - uni.reLaunch() 重新启动到指定页面 - 记录页面栈状态,实现智能返回

4.6.4 最佳实践建议

1. 路由设计 - 保持路由结构清晰,使用语义化的路径名 - 合理规划页面层级,避免过深的嵌套 - 统一路由命名规范,便于维护

2. 参数传递 - 简单参数使用 URL 传递 - 复杂数据使用本地存储或状态管理 - 敏感信息不要通过 URL 传递

3. 权限控制 - 前端权限控制主要用于用户体验优化 - 重要权限验证必须在后端进行 - 实现细粒度的权限控制系统

4. 性能优化 - 使用分包加载减少首屏加载时间 - 合理配置预加载规则 - 避免频繁的页面跳转

5. 用户体验 - 提供清晰的导航指示 - 实现平滑的页面切换动画 - 处理网络异常和错误状态

4.7 下一章预告

在下一章《网络请求与数据处理》中,我们将学习:

5.1 HTTP 请求

  • uni.request() API 详解
  • 请求拦截器和响应拦截器
  • 错误处理和重试机制
  • 文件上传和下载

5.2 数据缓存

  • 本地存储 API(同步和异步)
  • 缓存策略和过期管理
  • 数据加密和安全存储

5.3 状态管理

  • Vuex/Pinia 在 UniApp 中的使用
  • 全局状态设计模式
  • 数据持久化方案

5.4 接口封装

  • 统一的 API 管理
  • 请求和响应的标准化处理
  • Mock 数据和开发调试

5.5 实时通信

  • WebSocket 连接管理
  • 消息推送和处理
  • 断线重连机制

通过下一章的学习,你将掌握 UniApp 中数据处理的核心技能,能够构建功能完整的数据驱动应用。

2. uni.redirectTo() - 关闭当前页面

<template>
  <view class="redirect-demo">
    <text class="title">页面重定向演示</text>
    
    <view class="section">
      <text class="section-title">页面替换</text>
      <button @click="redirectToHome">重定向到首页</button>
      <button @click="redirectToLogin">重定向到登录页</button>
      <button @click="redirectWithParams">带参数重定向</button>
    </view>
    
    <view class="section">
      <text class="section-title">登录流程</text>
      <button @click="loginSuccess">登录成功跳转</button>
      <button @click="logoutRedirect">退出登录</button>
    </view>
  </view>
</template>

<script>
export default {
  name: 'RedirectDemo',
  methods: {
    redirectToHome() {
      uni.redirectTo({
        url: '/pages/index/index'
      })
    },
    
    redirectToLogin() {
      uni.redirectTo({
        url: '/pages/login/login'
      })
    },
    
    redirectWithParams() {
      const returnUrl = '/pages/profile/profile'
      uni.redirectTo({
        url: `/pages/login/login?returnUrl=${encodeURIComponent(returnUrl)}`
      })
    },
    
    loginSuccess() {
      // 模拟登录成功
      uni.setStorageSync('token', 'mock-token-123')
      uni.setStorageSync('userInfo', {
        id: 123,
        name: '张三',
        avatar: '/static/avatar.jpg'
      })
      
      uni.showToast({
        title: '登录成功',
        icon: 'success'
      })
      
      // 登录成功后重定向到首页
      setTimeout(() => {
        uni.redirectTo({
          url: '/pages/index/index'
        })
      }, 1500)
    },
    
    logoutRedirect() {
      uni.showModal({
        title: '确认退出',
        content: '确定要退出登录吗?',
        success: (res) => {
          if (res.confirm) {
            // 清除登录信息
            uni.removeStorageSync('token')
            uni.removeStorageSync('userInfo')
            
            uni.showToast({
              title: '已退出登录',
              icon: 'success'
            })
            
            // 重定向到登录页
            setTimeout(() => {
              uni.redirectTo({
                url: '/pages/login/login'
              })
            }, 1500)
          }
        }
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.redirect-demo {
  padding: 20rpx;
  
  .title {
    display: block;
    font-size: 32rpx;
    font-weight: bold;
    text-align: center;
    margin-bottom: 30rpx;
    color: #333;
  }
  
  .section {
    margin-bottom: 40rpx;
    padding: 20rpx;
    background-color: #f8f8f8;
    border-radius: 12rpx;
    
    .section-title {
      display: block;
      font-size: 28rpx;
      font-weight: bold;
      margin-bottom: 20rpx;
      color: #333;
    }
    
    button {
      width: 100%;
      margin-bottom: 15rpx;
      padding: 20rpx;
      background-color: #ff6b35;
      color: white;
      border: none;
      border-radius: 8rpx;
      font-size: 26rpx;
      
      &:last-child {
        margin-bottom: 0;
      }
      
      &:active {
        background-color: #e55a2b;
      }
    }
  }
}
</style>

3. uni.reLaunch() - 关闭所有页面

<template>
  <view class="relaunch-demo">
    <text class="title">应用重启演示</text>
    
    <view class="section">
      <text class="section-title">应用重启</text>
      <button @click="relaunchToHome">重启到首页</button>
      <button @click="relaunchToWelcome">重启到欢迎页</button>
    </view>
    
    <view class="section">
      <text class="section-title">特殊场景</text>
      <button @click="resetApp">重置应用</button>
      <button @click="switchAccount">切换账号</button>
    </view>
  </view>
</template>

<script>
export default {
  name: 'RelaunchDemo',
  methods: {
    relaunchToHome() {
      uni.reLaunch({
        url: '/pages/index/index'
      })
    },
    
    relaunchToWelcome() {
      uni.reLaunch({
        url: '/pages/welcome/welcome'
      })
    },
    
    resetApp() {
      uni.showModal({
        title: '重置应用',
        content: '这将清除所有数据并重启应用,确定继续吗?',
        success: (res) => {
          if (res.confirm) {
            // 清除所有存储数据
            uni.clearStorageSync()
            
            uni.showToast({
              title: '重置成功',
              icon: 'success'
            })
            
            // 重启应用
            setTimeout(() => {
              uni.reLaunch({
                url: '/pages/welcome/welcome'
              })
            }, 1500)
          }
        }
      })
    },
    
    switchAccount() {
      uni.showModal({
        title: '切换账号',
        content: '切换账号将清除当前登录信息',
        success: (res) => {
          if (res.confirm) {
            // 清除用户相关数据
            uni.removeStorageSync('token')
            uni.removeStorageSync('userInfo')
            uni.removeStorageSync('userPreferences')
            
            // 重启到登录页
            uni.reLaunch({
              url: '/pages/login/login'
            })
          }
        }
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.relaunch-demo {
  padding: 20rpx;
  
  .title {
    display: block;
    font-size: 32rpx;
    font-weight: bold;
    text-align: center;
    margin-bottom: 30rpx;
    color: #333;
  }
  
  .section {
    margin-bottom: 40rpx;
    padding: 20rpx;
    background-color: #f8f8f8;
    border-radius: 12rpx;
    
    .section-title {
      display: block;
      font-size: 28rpx;
      font-weight: bold;
      margin-bottom: 20rpx;
      color: #333;
    }
    
    button {
      width: 100%;
      margin-bottom: 15rpx;
      padding: 20rpx;
      background-color: #ff3b30;
      color: white;
      border: none;
      border-radius: 8rpx;
      font-size: 26rpx;
      
      &:last-child {
        margin-bottom: 0;
      }
      
      &:active {
        background-color: #d32f2f;
      }
    }
  }
}
</style>

4.2.2 声明式导航

1. navigator组件基础用法

<template>
  <view class="navigator-demo">
    <text class="title">声明式导航演示</text>
    
    <!-- 基础导航 -->
    <view class="section">
      <text class="section-title">基础导航</text>
      
      <navigator 
        url="/pages/detail/detail" 
        class="nav-item"
      >
        <text class="nav-text">跳转到详情页</text>
        <text class="nav-arrow">></text>
      </navigator>
      
      <navigator 
        url="/pages/user/user" 
        open-type="navigate"
        class="nav-item"
      >
        <text class="nav-text">跳转到用户页</text>
        <text class="nav-arrow">></text>
      </navigator>
    </view>
    
    <!-- 不同跳转类型 -->
    <view class="section">
      <text class="section-title">不同跳转类型</text>
      
      <navigator 
        url="/pages/index/index" 
        open-type="redirect"
        class="nav-item redirect"
      >
        <text class="nav-text">重定向到首页</text>
        <text class="nav-arrow">></text>
      </navigator>
      
      <navigator 
        url="/pages/welcome/welcome" 
        open-type="reLaunch"
        class="nav-item relaunch"
      >
        <text class="nav-text">重启到欢迎页</text>
        <text class="nav-arrow">></text>
      </navigator>
      
      <navigator 
        open-type="navigateBack" 
        delta="1"
        class="nav-item back"
      >
        <text class="nav-text">返回上一页</text>
        <text class="nav-arrow"><</text>
      </navigator>
    </view>
    
    <!-- 带参数导航 -->
    <view class="section">
      <text class="section-title">带参数导航</text>
      
      <navigator 
        :url="detailUrl" 
        class="nav-item"
      >
        <text class="nav-text">查看商品详情</text>
        <text class="nav-desc">ID: {{ productId }}</text>
        <text class="nav-arrow">></text>
      </navigator>
      
      <navigator 
        :url="listUrl" 
        class="nav-item"
      >
        <text class="nav-text">查看商品列表</text>
        <text class="nav-desc">分类: {{ category }}</text>
        <text class="nav-arrow">></text>
      </navigator>
    </view>
    
    <!-- 条件导航 -->
    <view class="section">
      <text class="section-title">条件导航</text>
      
      <navigator 
        v-if="isLoggedIn" 
        url="/pages/profile/profile" 
        class="nav-item"
      >
        <text class="nav-text">个人资料</text>
        <text class="nav-arrow">></text>
      </navigator>
      
      <navigator 
        v-else 
        url="/pages/login/login" 
        class="nav-item login"
      >
        <text class="nav-text">请先登录</text>
        <text class="nav-arrow">></text>
      </navigator>
      
      <navigator 
        v-if="userRole === 'admin'" 
        url="/pages/admin/admin" 
        class="nav-item admin"
      >
        <text class="nav-text">管理后台</text>
        <text class="nav-arrow">></text>
      </navigator>
    </view>
    
    <!-- 动画导航 -->
    <view class="section">
      <text class="section-title">动画导航</text>
      
      <navigator 
        url="/pages/detail/detail" 
        animation-type="slide-in-right"
        animation-duration="300"
        class="nav-item animated"
      >
        <text class="nav-text">右滑进入</text>
        <text class="nav-arrow">></text>
      </navigator>
      
      <navigator 
        url="/pages/modal/modal" 
        animation-type="slide-in-bottom"
        animation-duration="250"
        class="nav-item animated"
      >
        <text class="nav-text">底部弹出</text>
        <text class="nav-arrow">^</text>
      </navigator>
    </view>
    
    <!-- 自定义样式导航 -->
    <view class="section">
      <text class="section-title">自定义样式</text>
      
      <navigator 
        url="/pages/detail/detail" 
        class="custom-nav-item card-style"
        hover-class="card-hover"
      >
        <view class="card-content">
          <view class="card-icon">📱</view>
          <view class="card-info">
            <text class="card-title">商品详情</text>
            <text class="card-desc">查看商品的详细信息</text>
          </view>
          <text class="card-arrow">></text>
        </view>
      </navigator>
      
      <navigator 
        url="/pages/list/list" 
        class="custom-nav-item button-style"
        hover-class="button-hover"
      >
        <text class="button-text">浏览商品列表</text>
      </navigator>
    </view>
  </view>
</template>

<script>
export default {
  name: 'NavigatorDemo',
  data() {
    return {
      productId: 12345,
      category: 'electronics',
      isLoggedIn: false,
      userRole: 'user'
    }
  },
  computed: {
    detailUrl() {
      return `/pages/detail/detail?id=${this.productId}&name=${encodeURIComponent('iPhone 15 Pro')}`
    },
    
    listUrl() {
      return `/pages/list/list?category=${this.category}&sort=price`
    }
  },
  onLoad() {
    // 检查登录状态
    const token = uni.getStorageSync('token')
    this.isLoggedIn = !!token
    
    // 获取用户角色
    const userInfo = uni.getStorageSync('userInfo')
    if (userInfo && userInfo.role) {
      this.userRole = userInfo.role
    }
  }
}
</script>

<style lang="scss" scoped>
.navigator-demo {
  padding: 20rpx;
  
  .title {
    display: block;
    font-size: 32rpx;
    font-weight: bold;
    text-align: center;
    margin-bottom: 30rpx;
    color: #333;
  }
  
  .section {
    margin-bottom: 40rpx;
    
    .section-title {
      display: block;
      font-size: 28rpx;
      font-weight: bold;
      margin-bottom: 20rpx;
      color: #333;
    }
  }
  
  // 基础导航样式
  .nav-item {
    display: flex;
    align-items: center;
    padding: 25rpx 20rpx;
    margin-bottom: 15rpx;
    background-color: white;
    border-radius: 12rpx;
    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
    
    &:last-child {
      margin-bottom: 0;
    }
    
    &:active {
      background-color: #f5f5f5;
    }
    
    .nav-text {
      flex: 1;
      font-size: 26rpx;
      color: #333;
    }
    
    .nav-desc {
      font-size: 22rpx;
      color: #999;
      margin-right: 10rpx;
    }
    
    .nav-arrow {
      font-size: 24rpx;
      color: #999;
    }
  }
  
  // 不同类型的样式
  .redirect {
    border-left: 4rpx solid #ff6b35;
  }
  
  .relaunch {
    border-left: 4rpx solid #ff3b30;
  }
  
  .back {
    border-left: 4rpx solid #666;
  }
  
  .login {
    border-left: 4rpx solid #007aff;
  }
  
  .admin {
    border-left: 4rpx solid #ff9500;
  }
  
  .animated {
    border-left: 4rpx solid #09bb07;
  }
  
  // 自定义样式
  .custom-nav-item {
    margin-bottom: 20rpx;
    
    &:last-child {
      margin-bottom: 0;
    }
  }
  
  .card-style {
    background-color: white;
    border-radius: 16rpx;
    box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
    overflow: hidden;
    
    .card-content {
      display: flex;
      align-items: center;
      padding: 30rpx 25rpx;
    }
    
    .card-icon {
      font-size: 48rpx;
      margin-right: 20rpx;
    }
    
    .card-info {
      flex: 1;
    }
    
    .card-title {
      display: block;
      font-size: 28rpx;
      font-weight: bold;
      color: #333;
      margin-bottom: 5rpx;
    }
    
    .card-desc {
      display: block;
      font-size: 22rpx;
      color: #999;
    }
    
    .card-arrow {
      font-size: 28rpx;
      color: #007aff;
    }
  }
  
  .card-hover {
    background-color: #f8f8f8;
  }
  
  .button-style {
    background: linear-gradient(45deg, #007aff, #09bb07);
    border-radius: 25rpx;
    padding: 25rpx;
    text-align: center;
    
    .button-text {
      color: white;
      font-size: 28rpx;
      font-weight: bold;
    }
  }
  
  .button-hover {
    opacity: 0.8;
    transform: scale(0.98);
  }
}
</style>

4.2.3 页面栈管理

1. 页面栈概念

页面栈是UniApp管理页面的机制,类似于浏览器的历史记录:

  • uni.navigateTo(): 新页面入栈
  • uni.redirectTo(): 当前页面出栈,新页面入栈
  • uni.navigateBack(): 页面出栈
  • uni.reLaunch(): 清空页面栈,新页面入栈
  • uni.switchTab(): 清空页面栈,切换到tabBar页面

2. 页面栈操作示例

<template>
  <view class="page-stack-demo">
    <text class="title">页面栈管理演示</text>
    
    <!-- 页面栈信息 -->
    <view class="section">
      <text class="section-title">当前页面栈信息</text>
      <view class="stack-info">
        <text class="info-item">页面栈长度: {{ stackLength }}</text>
        <text class="info-item">当前页面: {{ currentPage }}</text>
        <text class="info-item">可返回: {{ canGoBack ? '是' : '否' }}</text>
      </view>
      <button @click="getPageStack">刷新页面栈信息</button>
    </view>
    
    <!-- 页面栈操作 -->
    <view class="section">
      <text class="section-title">页面栈操作</text>
      
      <button @click="pushPage">入栈新页面</button>
      <button @click="replacePage">替换当前页面</button>
      <button @click="goBack">返回上一页</button>
      <button @click="goBackMultiple">返回多级页面</button>
      <button @click="clearStack">清空页面栈</button>
    </view>
    
    <!-- 页面栈历史 -->
    <view class="section">
      <text class="section-title">页面栈历史</text>
      <view class="stack-history">
        <view 
          v-for="(page, index) in pageHistory" 
          :key="index" 
          class="history-item"
        >
          <text class="page-index">{{ index + 1 }}</text>
          <text class="page-path">{{ page.route }}</text>
          <text class="page-time">{{ formatTime(page.timestamp) }}</text>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  name: 'PageStackDemo',
  data() {
    return {
      stackLength: 0,
      currentPage: '',
      canGoBack: false,
      pageHistory: []
    }
  },
  onLoad() {
    this.getPageStack()
    this.recordPageVisit()
  },
  onShow() {
    this.getPageStack()
  },
  methods: {
    // 获取页面栈信息
    getPageStack() {
      const pages = getCurrentPages()
      this.stackLength = pages.length
      this.canGoBack = pages.length > 1
      
      if (pages.length > 0) {
        const currentPage = pages[pages.length - 1]
        this.currentPage = currentPage.route || 'unknown'
      }
    },
    
    // 记录页面访问
    recordPageVisit() {
      const pages = getCurrentPages()
      if (pages.length > 0) {
        const currentPage = pages[pages.length - 1]
        const pageInfo = {
          route: currentPage.route || 'unknown',
          timestamp: Date.now()
        }
        
        // 获取历史记录
        let history = uni.getStorageSync('pageHistory') || []
        history.push(pageInfo)
        
        // 限制历史记录数量
        if (history.length > 20) {
          history = history.slice(-20)
        }
        
        // 保存历史记录
        uni.setStorageSync('pageHistory', history)
        this.pageHistory = history
      }
    },
    
    // 入栈新页面
    pushPage() {
      uni.navigateTo({
        url: '/pages/detail/detail?from=stack-demo',
        success: () => {
          console.log('页面入栈成功')
        }
      })
    },
    
    // 替换当前页面
    replacePage() {
      uni.redirectTo({
        url: '/pages/list/list?from=stack-demo',
        success: () => {
          console.log('页面替换成功')
        }
      })
    },
    
    // 返回上一页
    goBack() {
      if (this.canGoBack) {
        uni.navigateBack({
          delta: 1,
          success: () => {
            console.log('返回成功')
          },
          fail: (err) => {
            console.error('返回失败', err)
            uni.showToast({
              title: '无法返回',
              icon: 'error'
            })
          }
        })
      } else {
        uni.showToast({
          title: '已是首页',
          icon: 'none'
        })
      }
    },
    
    // 返回多级页面
    goBackMultiple() {
      if (this.stackLength > 2) {
        uni.navigateBack({
          delta: 2,
          success: () => {
            console.log('返回两级页面成功')
          }
        })
      } else {
        uni.showToast({
          title: '页面栈不足',
          icon: 'none'
        })
      }
    },
    
    // 清空页面栈
    clearStack() {
      uni.showModal({
        title: '确认操作',
        content: '这将清空所有页面并返回首页',
        success: (res) => {
          if (res.confirm) {
            uni.reLaunch({
              url: '/pages/index/index',
              success: () => {
                console.log('页面栈已清空')
              }
            })
          }
        }
      })
    },
    
    // 格式化时间
    formatTime(timestamp) {
      const date = new Date(timestamp)
      const hours = date.getHours().toString().padStart(2, '0')
      const minutes = date.getMinutes().toString().padStart(2, '0')
      const seconds = date.getSeconds().toString().padStart(2, '0')
      return `${hours}:${minutes}:${seconds}`
    }
  }
}
</script>

<style lang="scss" scoped>
.page-stack-demo {
  padding: 20rpx;
  
  .title {
    display: block;
    font-size: 32rpx;
    font-weight: bold;
    text-align: center;
    margin-bottom: 30rpx;
    color: #333;
  }
  
  .section {
    margin-bottom: 40rpx;
    padding: 20rpx;
    background-color: #f8f8f8;
    border-radius: 12rpx;
    
    .section-title {
      display: block;
      font-size: 28rpx;
      font-weight: bold;
      margin-bottom: 20rpx;
      color: #333;
    }
    
    button {
      width: 100%;
      margin-bottom: 15rpx;
      padding: 20rpx;
      background-color: #007aff;
      color: white;
      border: none;
      border-radius: 8rpx;
      font-size: 26rpx;
      
      &:last-child {
        margin-bottom: 0;
      }
      
      &:active {
        background-color: #0056cc;
      }
    }
  }
  
  .stack-info {
    margin-bottom: 20rpx;
    
    .info-item {
      display: block;
      font-size: 24rpx;
      color: #666;
      margin-bottom: 10rpx;
      padding: 10rpx 15rpx;
      background-color: white;
      border-radius: 8rpx;
    }
  }
  
  .stack-history {
    max-height: 400rpx;
    overflow-y: auto;
    
    .history-item {
      display: flex;
      align-items: center;
      padding: 15rpx;
      margin-bottom: 10rpx;
      background-color: white;
      border-radius: 8rpx;
      
      .page-index {
        width: 60rpx;
        text-align: center;
        font-size: 22rpx;
        color: #999;
        background-color: #f0f0f0;
        border-radius: 50%;
        padding: 5rpx;
        margin-right: 15rpx;
      }
      
      .page-path {
        flex: 1;
        font-size: 24rpx;
        color: #333;
      }
      
      .page-time {
        font-size: 20rpx;
        color: #999;
      }
    }
  }
}
</style>

4.3 路由参数传递

4.3.1 URL参数传递

1. 基础参数传递

<!-- 发送页面 -->
<template>
  <view class="param-sender">
    <text class="title">参数传递演示</text>
    
    <view class="section">
      <text class="section-title">基础参数</text>
      <button @click="sendBasicParams">传递基础参数</button>
      <button @click="sendMultipleParams">传递多个参数</button>
    </view>
    
    <view class="section">
      <text class="section-title">复杂参数</text>
      <button @click="sendObjectParam">传递对象参数</button>
      <button @click="sendArrayParam">传递数组参数</button>
    </view>
    
    <view class="section">
      <text class="section-title">特殊字符</text>
      <button @click="sendSpecialChars">传递特殊字符</button>
      <button @click="sendChineseText">传递中文文本</button>
    </view>
  </view>
</template>

<script>
export default {
  name: 'ParamSender',
  methods: {
    // 传递基础参数
    sendBasicParams() {
      const id = 12345
      const name = 'iPhone 15 Pro'
      const price = 8999
      
      uni.navigateTo({
        url: `/pages/detail/detail?id=${id}&name=${encodeURIComponent(name)}&price=${price}`
      })
    },
    
    // 传递多个参数
    sendMultipleParams() {
      const params = {
        category: 'electronics',
        brand: 'apple',
        model: 'iPhone 15 Pro',
        color: '深空黑色',
        storage: '256GB',
        price: 8999,
        discount: 0.95,
        inStock: true
      }
      
      const queryString = Object.keys(params)
        .map(key => `${key}=${encodeURIComponent(params[key])}`)
        .join('&')
      
      uni.navigateTo({
        url: `/pages/detail/detail?${queryString}`
      })
    },
    
    // 传递对象参数
    sendObjectParam() {
      const productInfo = {
        id: 12345,
        name: 'iPhone 15 Pro',
        price: 8999,
        specs: {
          color: '深空黑色',
          storage: '256GB',
          screen: '6.1英寸',
          camera: '4800万像素'
        },
        features: ['Face ID', '无线充电', '防水'],
        seller: {
          id: 'apple_store',
          name: 'Apple官方旗舰店',
          rating: 4.9
        }
      }
      
      uni.navigateTo({
        url: `/pages/detail/detail?data=${encodeURIComponent(JSON.stringify(productInfo))}`
      })
    },
    
    // 传递数组参数
    sendArrayParam() {
      const categories = ['手机', '电脑', '平板', '耳机']
      const tags = ['热销', '新品', '推荐']
      
      const params = {
        categories: JSON.stringify(categories),
        tags: JSON.stringify(tags),
        type: 'list'
      }
      
      const queryString = Object.keys(params)
        .map(key => `${key}=${encodeURIComponent(params[key])}`)
        .join('&')
      
      uni.navigateTo({
        url: `/pages/list/list?${queryString}`
      })
    },
    
    // 传递特殊字符
    sendSpecialChars() {
      const specialText = 'Hello & World! @#$%^&*()_+-=[]{}|;:",./<>?'
      const url = 'https://www.example.com/api?param=value'
      
      uni.navigateTo({
        url: `/pages/detail/detail?text=${encodeURIComponent(specialText)}&url=${encodeURIComponent(url)}`
      })
    },
    
    // 传递中文文本
    sendChineseText() {
      const title = '苹果iPhone 15 Pro 深空黑色 256GB'
      const description = '全新设计,钛金属材质,A17 Pro芯片,专业级摄像系统'
      
      uni.navigateTo({
        url: `/pages/detail/detail?title=${encodeURIComponent(title)}&desc=${encodeURIComponent(description)}`
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.param-sender {
  padding: 20rpx;
  
  .title {
    display: block;
    font-size: 32rpx;
    font-weight: bold;
    text-align: center;
    margin-bottom: 30rpx;
    color: #333;
  }
  
  .section {
    margin-bottom: 40rpx;
    padding: 20rpx;
    background-color: #f8f8f8;
    border-radius: 12rpx;
    
    .section-title {
      display: block;
      font-size: 28rpx;
      font-weight: bold;
      margin-bottom: 20rpx;
      color: #333;
    }
    
    button {
      width: 100%;
      margin-bottom: 15rpx;
      padding: 20rpx;
      background-color: #007aff;
      color: white;
      border: none;
      border-radius: 8rpx;
      font-size: 26rpx;
      
      &:last-child {
        margin-bottom: 0;
      }
      
      &:active {
        background-color: #0056cc;
      }
    }
  }
}
</style>

2. 接收页面参数

<!-- 接收页面 -->
<template>
  <view class="param-receiver">
    <text class="title">接收到的参数</text>
    
    <!-- 基础参数显示 -->
    <view class="section" v-if="basicParams">
      <text class="section-title">基础参数</text>
      <view class="param-list">
        <view 
          v-for="(value, key) in basicParams" 
          :key="key" 
          class="param-item"
        >
          <text class="param-key">{{ key }}:</text>
          <text class="param-value">{{ value }}</text>
        </view>
      </view>
    </view>
    
    <!-- 对象参数显示 -->
    <view class="section" v-if="objectData">
      <text class="section-title">对象参数</text>
      <view class="object-display">
        <text class="object-json">{{ JSON.stringify(objectData, null, 2) }}</text>
      </view>
    </view>
    
    <!-- 数组参数显示 -->
    <view class="section" v-if="arrayData.length > 0">
      <text class="section-title">数组参数</text>
      <view class="array-display">
        <view 
          v-for="(item, index) in arrayData" 
          :key="index" 
          class="array-item"
        >
          <text class="array-index">{{ index }}:</text>
          <text class="array-value">{{ item }}</text>
        </view>
      </view>
    </view>
    
    <!-- 原始查询字符串 -->
    <view class="section">
      <text class="section-title">原始查询字符串</text>
      <text class="query-string">{{ rawQuery }}</text>
    </view>
    
    <!-- 操作按钮 -->
    <view class="section">
      <text class="section-title">操作</text>
      <button @click="refreshParams">刷新参数</button>
      <button @click="goBack">返回上一页</button>
    </view>
  </view>
</template>

<script>
export default {
  name: 'ParamReceiver',
  data() {
    return {
      basicParams: null,
      objectData: null,
      arrayData: [],
      rawQuery: ''
    }
  },
  onLoad(options) {
    console.log('接收到的参数:', options)
    this.parseParams(options)
  },
  methods: {
    // 解析参数
    parseParams(options) {
      this.rawQuery = JSON.stringify(options, null, 2)
      
      // 处理基础参数
      const basicParams = {}
      const excludeKeys = ['data', 'categories', 'tags']
      
      Object.keys(options).forEach(key => {
        if (!excludeKeys.includes(key)) {
          basicParams[key] = this.decodeParam(options[key])
        }
      })
      
      if (Object.keys(basicParams).length > 0) {
        this.basicParams = basicParams
      }
      
      // 处理对象参数
      if (options.data) {
        try {
          this.objectData = JSON.parse(decodeURIComponent(options.data))
        } catch (error) {
          console.error('解析对象参数失败:', error)
          uni.showToast({
            title: '参数解析失败',
            icon: 'error'
          })
        }
      }
      
      // 处理数组参数
      const arrayData = []
      
      if (options.categories) {
        try {
          const categories = JSON.parse(decodeURIComponent(options.categories))
          arrayData.push(...categories.map(item => `分类: ${item}`))
        } catch (error) {
          console.error('解析分类参数失败:', error)
        }
      }
      
      if (options.tags) {
        try {
          const tags = JSON.parse(decodeURIComponent(options.tags))
          arrayData.push(...tags.map(item => `标签: ${item}`))
        } catch (error) {
          console.error('解析标签参数失败:', error)
        }
      }
      
      this.arrayData = arrayData
    },
    
    // 解码参数
    decodeParam(param) {
      try {
        return decodeURIComponent(param)
      } catch (error) {
        console.error('参数解码失败:', error)
        return param
      }
    },
    
    // 刷新参数
    refreshParams() {
      const pages = getCurrentPages()
      const currentPage = pages[pages.length - 1]
      
      if (currentPage && currentPage.options) {
        this.parseParams(currentPage.options)
        uni.showToast({
          title: '参数已刷新',
          icon: 'success'
        })
      }
    },
    
    // 返回上一页
    goBack() {
      uni.navigateBack()
    }
  }
}
</script>

<style lang="scss" scoped>
.param-receiver {
  padding: 20rpx;
  
  .title {
    display: block;
    font-size: 32rpx;
    font-weight: bold;
    text-align: center;
    margin-bottom: 30rpx;
    color: #333;
  }
  
  .section {
    margin-bottom: 40rpx;
    padding: 20rpx;
    background-color: #f8f8f8;
    border-radius: 12rpx;
    
    .section-title {
      display: block;
      font-size: 28rpx;
      font-weight: bold;
      margin-bottom: 20rpx;
      color: #333;
    }
  }
  
  .param-list {
    .param-item {
      display: flex;
      padding: 15rpx;
      margin-bottom: 10rpx;
      background-color: white;
      border-radius: 8rpx;
      
      .param-key {
        width: 150rpx;
        font-size: 24rpx;
        font-weight: bold;
        color: #666;
      }
      
      .param-value {
        flex: 1;
        font-size: 24rpx;
        color: #333;
        word-break: break-all;
      }
    }
  }
  
  .object-display {
    background-color: white;
    border-radius: 8rpx;
    padding: 20rpx;
    
    .object-json {
      font-family: 'Courier New', monospace;
      font-size: 22rpx;
      color: #333;
      white-space: pre-wrap;
      word-break: break-all;
    }
  }
  
  .array-display {
    .array-item {
      display: flex;
      padding: 15rpx;
      margin-bottom: 10rpx;
      background-color: white;
      border-radius: 8rpx;
      
      .array-index {
        width: 80rpx;
        font-size: 24rpx;
        font-weight: bold;
        color: #666;
      }
      
      .array-value {
        flex: 1;
        font-size: 24rpx;
        color: #333;
      }
    }
  }
  
  .query-string {
    display: block;
    background-color: white;
    border-radius: 8rpx;
    padding: 20rpx;
    font-family: 'Courier New', monospace;
    font-size: 22rpx;
    color: #333;
    white-space: pre-wrap;
    word-break: break-all;
  }
  
  button {
    width: 100%;
    margin-bottom: 15rpx;
    padding: 20rpx;
    background-color: #007aff;
    color: white;
    border: none;
    border-radius: 8rpx;
    font-size: 26rpx;
    
    &:last-child {
      margin-bottom: 0;
    }
    
    &:active {
      background-color: #0056cc;
    }
  }
}
</style>

4.3.2 事件传参

1. 全局事件总线

// utils/eventBus.js
class EventBus {
  constructor() {
    this.events = {}
  }
  
  // 监听事件
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = []
    }
    this.events[event].push(callback)
  }
  
  // 触发事件
  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(callback => {
        callback(data)
      })
    }
  }
  
  // 移除事件监听
  off(event, callback) {
    if (this.events[event]) {
      const index = this.events[event].indexOf(callback)
      if (index > -1) {
        this.events[event].splice(index, 1)
      }
    }
  }
  
  // 只监听一次
  once(event, callback) {
    const onceCallback = (data) => {
      callback(data)
      this.off(event, onceCallback)
    }
    this.on(event, onceCallback)
  }
}

export default new EventBus()

2. 使用事件总线传递数据

<!-- 发送页面 -->
<template>
  <view class="event-sender">
    <text class="title">事件传参演示</text>
    
    <view class="section">
      <text class="section-title">发送数据</text>
      <button @click="sendUserData">发送用户数据</button>
      <button @click="sendProductData">发送商品数据</button>
      <button @click="sendFormData">发送表单数据</button>
    </view>
    
    <view class="section">
      <text class="section-title">跳转并发送</text>
      <button @click="navigateAndSend">跳转并发送数据</button>
    </view>
  </view>
</template>

<script>
import EventBus from '@/utils/eventBus.js'

export default {
  name: 'EventSender',
  data() {
    return {
      userData: {
        id: 123,
        name: '张三',
        email: 'zhangsan@example.com',
        avatar: '/static/avatar.jpg'
      },
      productData: {
        id: 12345,
        name: 'iPhone 15 Pro',
        price: 8999,
        images: ['/static/phone1.jpg', '/static/phone2.jpg']
      }
    }
  },
  methods: {
    sendUserData() {
      EventBus.emit('userDataUpdate', this.userData)
      uni.showToast({
        title: '用户数据已发送',
        icon: 'success'
      })
    },
    
    sendProductData() {
      EventBus.emit('productDataUpdate', this.productData)
      uni.showToast({
        title: '商品数据已发送',
        icon: 'success'
      })
    },
    
    sendFormData() {
      const formData = {
        title: '新建订单',
        items: [
          { id: 1, name: '商品A', quantity: 2 },
          { id: 2, name: '商品B', quantity: 1 }
        ],
        total: 299.99,
        timestamp: Date.now()
      }
      
      EventBus.emit('formDataSubmit', formData)
      uni.showToast({
        title: '表单数据已发送',
        icon: 'success'
      })
    },
    
    navigateAndSend() {
      // 先发送数据
      EventBus.emit('pageDataTransfer', {
        type: 'navigation',
        source: 'event-sender',
        data: this.productData,
        timestamp: Date.now()
      })
      
      // 然后跳转页面
      uni.navigateTo({
        url: '/pages/receiver/receiver'
      })
    }
  }
}
</script>

3. 接收页面监听事件

<!-- 接收页面 -->
<template>
  <view class="event-receiver">
    <text class="title">事件接收演示</text>
    
    <view class="section" v-if="receivedData.length > 0">
      <text class="section-title">接收到的数据</text>
      <view 
        v-for="(item, index) in receivedData" 
        :key="index" 
        class="data-item"
      >
        <text class="data-type">{{ item.type }}</text>
        <text class="data-time">{{ formatTime(item.timestamp) }}</text>
        <text class="data-content">{{ JSON.stringify(item.data, null, 2) }}</text>
      </view>
    </view>
    
    <view class="section">
      <text class="section-title">操作</text>
      <button @click="clearData">清空数据</button>
      <button @click="goBack">返回上一页</button>
    </view>
  </view>
</template>

<script>
import EventBus from '@/utils/eventBus.js'

export default {
  name: 'EventReceiver',
  data() {
    return {
      receivedData: []
    }
  },
  onLoad() {
    this.setupEventListeners()
  },
  onUnload() {
    this.removeEventListeners()
  },
  methods: {
    setupEventListeners() {
      // 监听用户数据更新
      EventBus.on('userDataUpdate', this.handleUserData)
      
      // 监听商品数据更新
      EventBus.on('productDataUpdate', this.handleProductData)
      
      // 监听表单数据提交
      EventBus.on('formDataSubmit', this.handleFormData)
      
      // 监听页面数据传输
      EventBus.on('pageDataTransfer', this.handlePageData)
    },
    
    removeEventListeners() {
      EventBus.off('userDataUpdate', this.handleUserData)
      EventBus.off('productDataUpdate', this.handleProductData)
      EventBus.off('formDataSubmit', this.handleFormData)
      EventBus.off('pageDataTransfer', this.handlePageData)
    },
    
    handleUserData(data) {
      this.addReceivedData('用户数据', data)
    },
    
    handleProductData(data) {
      this.addReceivedData('商品数据', data)
    },
    
    handleFormData(data) {
      this.addReceivedData('表单数据', data)
    },
    
    handlePageData(data) {
      this.addReceivedData('页面数据', data)
    },
    
    addReceivedData(type, data) {
      this.receivedData.unshift({
        type,
        data,
        timestamp: Date.now()
      })
      
      // 限制数据数量
      if (this.receivedData.length > 10) {
        this.receivedData = this.receivedData.slice(0, 10)
      }
    },
    
    formatTime(timestamp) {
      const date = new Date(timestamp)
      return date.toLocaleTimeString()
    },
    
    clearData() {
      this.receivedData = []
      uni.showToast({
        title: '数据已清空',
        icon: 'success'
      })
    },
    
    goBack() {
      uni.navigateBack()
    }
  }
}
</script>

4.3.3 存储传参

1. 本地存储传参

<template>
  <view class="storage-demo">
    <text class="title">存储传参演示</text>
    
    <view class="section">
      <text class="section-title">存储数据</text>
      <button @click="storeUserInfo">存储用户信息</button>
      <button @click="storeShoppingCart">存储购物车</button>
      <button @click="storeFormData">存储表单数据</button>
    </view>
    
    <view class="section">
      <text class="section-title">读取数据</text>
      <button @click="readStoredData">读取存储数据</button>
      <button @click="clearStorage">清空存储</button>
    </view>
    
    <view class="section" v-if="storedData">
      <text class="section-title">存储的数据</text>
      <text class="data-display">{{ JSON.stringify(storedData, null, 2) }}</text>
    </view>
  </view>
</template>

<script>
export default {
  name: 'StorageDemo',
  data() {
    return {
      storedData: null
    }
  },
  onLoad() {
    this.readStoredData()
  },
  methods: {
    // 存储用户信息
    storeUserInfo() {
      const userInfo = {
        id: 123,
        name: '张三',
        email: 'zhangsan@example.com',
        avatar: '/static/avatar.jpg',
        preferences: {
          theme: 'light',
          language: 'zh-CN',
          notifications: true
        },
        lastLogin: Date.now()
      }
      
      try {
        uni.setStorageSync('userInfo', userInfo)
        uni.showToast({
          title: '用户信息已存储',
          icon: 'success'
        })
      } catch (error) {
        console.error('存储失败:', error)
        uni.showToast({
          title: '存储失败',
          icon: 'error'
        })
      }
    },
    
    // 存储购物车
    storeShoppingCart() {
      const cartData = {
        items: [
          {
            id: 1,
            name: 'iPhone 15 Pro',
            price: 8999,
            quantity: 1,
            image: '/static/phone.jpg'
          },
          {
            id: 2,
            name: 'AirPods Pro',
            price: 1999,
            quantity: 2,
            image: '/static/airpods.jpg'
          }
        ],
        total: 12997,
        discount: 500,
        finalTotal: 12497,
        updateTime: Date.now()
      }
      
      uni.setStorage({
        key: 'shoppingCart',
        data: cartData,
        success: () => {
          uni.showToast({
            title: '购物车已存储',
            icon: 'success'
          })
        },
        fail: (error) => {
          console.error('存储失败:', error)
          uni.showToast({
            title: '存储失败',
            icon: 'error'
          })
        }
      })
    },
    
    // 存储表单数据
    storeFormData() {
      const formData = {
        personalInfo: {
          name: '李四',
          phone: '13800138000',
          email: 'lisi@example.com',
          address: '北京市朝阳区'
        },
        orderInfo: {
          orderNo: 'ORD20240101001',
          products: ['iPhone 15', 'MacBook Pro'],
          amount: 25998,
          paymentMethod: 'alipay'
        },
        timestamp: Date.now(),
        status: 'draft'
      }
      
      // 使用加密存储敏感数据
      const encryptedData = this.encryptData(formData)
      
      uni.setStorageSync('formData', encryptedData)
      uni.showToast({
        title: '表单数据已存储',
        icon: 'success'
      })
    },
    
    // 读取存储数据
    readStoredData() {
      try {
        const userInfo = uni.getStorageSync('userInfo')
        const cartData = uni.getStorageSync('shoppingCart')
        const formData = uni.getStorageSync('formData')
        
        this.storedData = {
          userInfo: userInfo || null,
          shoppingCart: cartData || null,
          formData: formData ? this.decryptData(formData) : null
        }
        
        if (this.storedData.userInfo || this.storedData.shoppingCart || this.storedData.formData) {
          uni.showToast({
            title: '数据读取成功',
            icon: 'success'
          })
        } else {
          uni.showToast({
            title: '暂无存储数据',
            icon: 'none'
          })
        }
      } catch (error) {
        console.error('读取数据失败:', error)
        uni.showToast({
          title: '读取失败',
          icon: 'error'
        })
      }
    },
    
    // 清空存储
    clearStorage() {
      uni.showModal({
        title: '确认清空',
        content: '确定要清空所有存储数据吗?',
        success: (res) => {
          if (res.confirm) {
            try {
              uni.removeStorageSync('userInfo')
              uni.removeStorageSync('shoppingCart')
              uni.removeStorageSync('formData')
              
              this.storedData = null
              
              uni.showToast({
                title: '存储已清空',
                icon: 'success'
              })
            } catch (error) {
              console.error('清空失败:', error)
              uni.showToast({
                title: '清空失败',
                icon: 'error'
              })
            }
          }
        }
      })
    },
    
    // 简单加密(实际项目中应使用更安全的加密方法)
    encryptData(data) {
      const jsonString = JSON.stringify(data)
      return btoa(encodeURIComponent(jsonString))
    },
    
    // 简单解密
    decryptData(encryptedData) {
      try {
        const jsonString = decodeURIComponent(atob(encryptedData))
        return JSON.parse(jsonString)
      } catch (error) {
        console.error('解密失败:', error)
        return null
      }
    }
  }
}
</script>

4.4 TabBar导航

4.4.1 TabBar配置

1. 基础TabBar配置

{
  "tabBar": {
    "color": "#7A7E83",
    "selectedColor": "#007aff",
    "borderStyle": "black",
    "backgroundColor": "#ffffff",
    "position": "bottom",
    "fontSize": "10px",
    "iconWidth": "24px",
    "spacing": "3px",
    "height": "50px",
    "midButton": {
      "width": "80px",
      "height": "50px",
      "text": "发布",
      "iconPath": "static/tab-publish.png",
      "iconWidth": "24px",
      "backgroundImage": "static/tab-mid-bg.png"
    },
    "list": [
      {
        "pagePath": "pages/index/index",
        "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/publish/publish",
        "iconPath": "static/tab-publish.png",
        "selectedIconPath": "static/tab-publish-active.png",
        "text": "发布"
      },
      {
        "pagePath": "pages/message/message",
        "iconPath": "static/tab-message.png",
        "selectedIconPath": "static/tab-message-active.png",
        "text": "消息"
      },
      {
        "pagePath": "pages/user/user",
        "iconPath": "static/tab-user.png",
        "selectedIconPath": "static/tab-user-active.png",
        "text": "我的"
      }
    ]
  }
}

2. 自定义TabBar样式

{
  "tabBar": {
    "custom": true,
    "color": "#7A7E83",
    "selectedColor": "#007aff",
    "borderStyle": "white",
    "backgroundColor": "#ffffff",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页"
      },
      {
        "pagePath": "pages/category/category",
        "text": "分类"
      },
      {
        "pagePath": "pages/cart/cart",
        "text": "购物车"
      },
      {
        "pagePath": "pages/user/user",
        "text": "我的"
      }
    ]
  }
}

4.4.2 自定义TabBar组件

1. 创建自定义TabBar组件

<!-- custom-tab-bar/index.vue -->
<template>
  <view class="custom-tab-bar" :style="{ paddingBottom: safeAreaBottom + 'px' }">
    <view class="tab-bar-container">
      <view 
        v-for="(item, index) in tabList" 
        :key="index" 
        class="tab-item"
        :class="{ active: currentIndex === index }"
        @click="switchTab(item, index)"
      >
        <!-- 图标 -->
        <view class="tab-icon">
          <image 
            v-if="item.iconPath" 
            :src="currentIndex === index ? item.selectedIconPath : item.iconPath" 
            class="icon-image"
          />
          <text 
            v-else 
            class="icon-text"
            :class="{ active: currentIndex === index }"
          >
            {{ item.icon }}
          </text>
          
          <!-- 角标 -->
          <view 
            v-if="item.badge" 
            class="badge"
            :class="{ dot: item.badge === 'dot' }"
          >
            <text v-if="item.badge !== 'dot'" class="badge-text">{{ item.badge }}</text>
          </view>
        </view>
        
        <!-- 文字 -->
        <text class="tab-text" :class="{ active: currentIndex === index }">
          {{ item.text }}
        </text>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  name: 'CustomTabBar',
  data() {
    return {
      currentIndex: 0,
      safeAreaBottom: 0,
      tabList: [
        {
          pagePath: '/pages/index/index',
          text: '首页',
          iconPath: '/static/tab-home.png',
          selectedIconPath: '/static/tab-home-active.png',
          badge: null
        },
        {
          pagePath: '/pages/category/category',
          text: '分类',
          iconPath: '/static/tab-category.png',
          selectedIconPath: '/static/tab-category-active.png',
          badge: null
        },
        {
          pagePath: '/pages/cart/cart',
          text: '购物车',
          iconPath: '/static/tab-cart.png',
          selectedIconPath: '/static/tab-cart-active.png',
          badge: 3
        },
        {
          pagePath: '/pages/message/message',
          text: '消息',
          iconPath: '/static/tab-message.png',
          selectedIconPath: '/static/tab-message-active.png',
          badge: 'dot'
        },
        {
          pagePath: '/pages/user/user',
          text: '我的',
          iconPath: '/static/tab-user.png',
          selectedIconPath: '/static/tab-user-active.png',
          badge: null
        }
      ]
    }
  },
  mounted() {
    this.getSafeAreaBottom()
    this.updateCurrentIndex()
  },
  methods: {
    // 获取安全区域底部高度
    getSafeAreaBottom() {
      const systemInfo = uni.getSystemInfoSync()
      this.safeAreaBottom = systemInfo.safeAreaInsets ? systemInfo.safeAreaInsets.bottom : 0
    },
    
    // 更新当前选中索引
    updateCurrentIndex() {
      const pages = getCurrentPages()
      const currentPage = pages[pages.length - 1]
      const currentRoute = '/' + currentPage.route
      
      const index = this.tabList.findIndex(item => item.pagePath === currentRoute)
      if (index !== -1) {
        this.currentIndex = index
      }
    },
    
    // 切换Tab
    switchTab(item, index) {
      if (this.currentIndex === index) {
        return
      }
      
      this.currentIndex = index
      
      uni.switchTab({
        url: item.pagePath,
        fail: (err) => {
          console.error('切换Tab失败:', err)
        }
      })
    },
    
    // 更新角标
    updateBadge(index, badge) {
      if (index >= 0 && index < this.tabList.length) {
        this.tabList[index].badge = badge
      }
    },
    
    // 清除角标
    clearBadge(index) {
      this.updateBadge(index, null)
    }
  }
}
</script>

<style lang="scss" scoped>
.custom-tab-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: #ffffff;
  border-top: 1rpx solid #e5e5e5;
  z-index: 1000;
  
  .tab-bar-container {
    display: flex;
    height: 100rpx;
    
    .tab-item {
      flex: 1;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      position: relative;
      padding: 10rpx 0;
      
      &.active {
        .tab-icon {
          transform: scale(1.1);
        }
      }
      
      .tab-icon {
        position: relative;
        margin-bottom: 8rpx;
        transition: transform 0.2s ease;
        
        .icon-image {
          width: 48rpx;
          height: 48rpx;
        }
        
        .icon-text {
          font-size: 48rpx;
          color: #7A7E83;
          
          &.active {
            color: #007aff;
          }
        }
        
        .badge {
          position: absolute;
          top: -8rpx;
          right: -8rpx;
          min-width: 32rpx;
          height: 32rpx;
          background-color: #ff3b30;
          border-radius: 16rpx;
          display: flex;
          align-items: center;
          justify-content: center;
          
          &.dot {
            width: 16rpx;
            height: 16rpx;
            min-width: 16rpx;
            border-radius: 8rpx;
            top: -4rpx;
            right: -4rpx;
          }
          
          .badge-text {
            color: white;
            font-size: 20rpx;
            font-weight: bold;
            padding: 0 8rpx;
          }
        }
      }
      
      .tab-text {
        font-size: 20rpx;
        color: #7A7E83;
        transition: color 0.2s ease;
        
        &.active {
          color: #007aff;
          font-weight: bold;
        }
      }
    }
  }
}
</style>

2. 在页面中使用自定义TabBar

<!-- pages/index/index.vue -->
<template>
  <view class="page-container">
    <!-- 页面内容 -->
    <view class="content">
      <text class="title">首页内容</text>
      
      <view class="section">
        <text class="section-title">TabBar操作</text>
        <button @click="updateCartBadge">更新购物车角标</button>
        <button @click="showMessageDot">显示消息红点</button>
        <button @click="clearAllBadges">清除所有角标</button>
      </view>
    </view>
    
    <!-- 自定义TabBar -->
    <custom-tab-bar ref="tabBar" />
  </view>
</template>

<script>
import CustomTabBar from '@/custom-tab-bar/index.vue'

export default {
  name: 'IndexPage',
  components: {
    CustomTabBar
  },
  data() {
    return {
      cartCount: 0
    }
  },
  methods: {
    updateCartBadge() {
      this.cartCount += 1
      this.$refs.tabBar.updateBadge(2, this.cartCount)
      
      uni.showToast({
        title: `购物车角标: ${this.cartCount}`,
        icon: 'success'
      })
    },
    
    showMessageDot() {
      this.$refs.tabBar.updateBadge(3, 'dot')
      
      uni.showToast({
        title: '消息红点已显示',
        icon: 'success'
      })
    },
    
    clearAllBadges() {
      this.$refs.tabBar.clearBadge(2)
      this.$refs.tabBar.clearBadge(3)
      this.cartCount = 0
      
      uni.showToast({
        title: '角标已清除',
        icon: 'success'
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.page-container {
  min-height: 100vh;
  padding-bottom: 100rpx; // 为TabBar留出空间
  
  .content {
    padding: 20rpx;
    
    .title {
      display: block;
      font-size: 32rpx;
      font-weight: bold;
      text-align: center;
      margin-bottom: 30rpx;
      color: #333;
    }
    
    .section {
      margin-bottom: 40rpx;
      padding: 20rpx;
      background-color: #f8f8f8;
      border-radius: 12rpx;
      
      .section-title {
        display: block;
        font-size: 28rpx;
        font-weight: bold;
        margin-bottom: 20rpx;
        color: #333;
      }
      
      button {
        width: 100%;
        margin-bottom: 15rpx;
        padding: 20rpx;
        background-color: #007aff;
        color: white;
        border: none;
        border-radius: 8rpx;
        font-size: 26rpx;
        
        &:last-child {
          margin-bottom: 0;
        }
        
        &:active {
          background-color: #0056cc;
        }
      }
    }
  }
}
</style>

4.4.3 TabBar切换控制

1. 编程式TabBar切换

<template>
  <view class="tab-control-demo">
    <text class="title">TabBar切换控制</text>
    
    <view class="section">
      <text class="section-title">基础切换</text>
      <button @click="switchToHome">切换到首页</button>
      <button @click="switchToCategory">切换到分类</button>
      <button @click="switchToCart">切换到购物车</button>
      <button @click="switchToUser">切换到我的</button>
    </view>
    
    <view class="section">
      <text class="section-title">条件切换</text>
      <button @click="switchToCartWithCheck">检查登录后切换</button>
      <button @click="switchToUserWithAuth">验证权限后切换</button>
    </view>
    
    <view class="section">
      <text class="section-title">TabBar信息</text>
      <view class="info-item">
        <text class="info-label">当前Tab:</text>
        <text class="info-value">{{ currentTabText }}</text>
      </view>
      <view class="info-item">
        <text class="info-label">Tab索引:</text>
        <text class="info-value">{{ currentTabIndex }}</text>
      </view>
      <button @click="getCurrentTabInfo">获取当前Tab信息</button>
    </view>
  </view>
</template>

<script>
export default {
  name: 'TabControlDemo',
  data() {
    return {
      currentTabText: '',
      currentTabIndex: -1,
      tabList: [
        { path: '/pages/index/index', text: '首页' },
        { path: '/pages/category/category', text: '分类' },
        { path: '/pages/cart/cart', text: '购物车' },
        { path: '/pages/user/user', text: '我的' }
      ]
    }
  },
  onLoad() {
    this.getCurrentTabInfo()
  },
  onShow() {
    this.getCurrentTabInfo()
  },
  methods: {
    // 切换到首页
    switchToHome() {
      uni.switchTab({
        url: '/pages/index/index',
        success: () => {
          console.log('切换到首页成功')
        },
        fail: (err) => {
          console.error('切换失败:', err)
          uni.showToast({
            title: '切换失败',
            icon: 'error'
          })
        }
      })
    },
    
    // 切换到分类
    switchToCategory() {
      uni.switchTab({
        url: '/pages/category/category'
      })
    },
    
    // 切换到购物车
    switchToCart() {
      uni.switchTab({
        url: '/pages/cart/cart'
      })
    },
    
    // 切换到我的
    switchToUser() {
      uni.switchTab({
        url: '/pages/user/user'
      })
    },
    
    // 检查登录后切换到购物车
    switchToCartWithCheck() {
      const token = uni.getStorageSync('token')
      
      if (!token) {
        uni.showModal({
          title: '提示',
          content: '请先登录后再查看购物车',
          confirmText: '去登录',
          success: (res) => {
            if (res.confirm) {
              uni.navigateTo({
                url: '/pages/login/login'
              })
            }
          }
        })
        return
      }
      
      uni.switchTab({
        url: '/pages/cart/cart'
      })
    },
    
    // 验证权限后切换
    switchToUserWithAuth() {
      const userInfo = uni.getStorageSync('userInfo')
      
      if (!userInfo) {
        uni.showToast({
          title: '请先登录',
          icon: 'none'
        })
        return
      }
      
      if (userInfo.status !== 'active') {
        uni.showModal({
          title: '账号异常',
          content: '您的账号状态异常,请联系客服',
          showCancel: false
        })
        return
      }
      
      uni.switchTab({
        url: '/pages/user/user'
      })
    },
    
    // 获取当前Tab信息
    getCurrentTabInfo() {
      const pages = getCurrentPages()
      const currentPage = pages[pages.length - 1]
      const currentRoute = '/' + currentPage.route
      
      const tabIndex = this.tabList.findIndex(item => item.path === currentRoute)
      
      if (tabIndex !== -1) {
        this.currentTabIndex = tabIndex
        this.currentTabText = this.tabList[tabIndex].text
      } else {
        this.currentTabIndex = -1
        this.currentTabText = '非Tab页面'
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.tab-control-demo {
  padding: 20rpx;
  
  .title {
    display: block;
    font-size: 32rpx;
    font-weight: bold;
    text-align: center;
    margin-bottom: 30rpx;
    color: #333;
  }
  
  .section {
    margin-bottom: 40rpx;
    padding: 20rpx;
    background-color: #f8f8f8;
    border-radius: 12rpx;
    
    .section-title {
      display: block;
      font-size: 28rpx;
      font-weight: bold;
      margin-bottom: 20rpx;
      color: #333;
    }
    
    button {
      width: 100%;
      margin-bottom: 15rpx;
      padding: 20rpx;
      background-color: #007aff;
      color: white;
      border: none;
      border-radius: 8rpx;
      font-size: 26rpx;
      
      &:last-child {
        margin-bottom: 0;
      }
      
      &:active {
        background-color: #0056cc;
      }
    }
    
    .info-item {
      display: flex;
      align-items: center;
      padding: 15rpx;
      margin-bottom: 10rpx;
      background-color: white;
      border-radius: 8rpx;
      
      .info-label {
        width: 150rpx;
        font-size: 24rpx;
        color: #666;
      }
      
      .info-value {
        flex: 1;
        font-size: 24rpx;
        color: #333;
        font-weight: bold;
      }
    }
  }
}
</style>

4.5 路由守卫与权限控制

4.5.1 路由拦截器

1. 创建路由拦截器

// utils/routeGuard.js
class RouteGuard {
  constructor() {
    this.beforeHooks = []
    this.afterHooks = []
    this.init()
  }
  
  // 初始化路由拦截
  init() {
    this.interceptNavigateTo()
    this.interceptRedirectTo()
    this.interceptReLaunch()
    this.interceptSwitchTab()
  }
  
  // 添加前置守卫
  beforeEach(hook) {
    this.beforeHooks.push(hook)
  }
  
  // 添加后置守卫
  afterEach(hook) {
    this.afterHooks.push(hook)
  }
  
  // 执行前置守卫
  async runBeforeHooks(to, from) {
    for (const hook of this.beforeHooks) {
      const result = await hook(to, from)
      if (result === false) {
        return false
      }
      if (typeof result === 'string') {
        return result // 重定向路径
      }
    }
    return true
  }
  
  // 执行后置守卫
  runAfterHooks(to, from) {
    this.afterHooks.forEach(hook => {
      hook(to, from)
    })
  }
  
  // 拦截 navigateTo
  interceptNavigateTo() {
    const originalNavigateTo = uni.navigateTo
    
    uni.navigateTo = async (options) => {
      const to = this.parseRoute(options.url)
      const from = this.getCurrentRoute()
      
      const result = await this.runBeforeHooks(to, from)
      
      if (result === false) {
        return
      }
      
      if (typeof result === 'string') {
        options.url = result
      }
      
      const originalSuccess = options.success
      options.success = (res) => {
        this.runAfterHooks(to, from)
        originalSuccess && originalSuccess(res)
      }
      
      return originalNavigateTo(options)
    }
  }
  
  // 拦截 redirectTo
  interceptRedirectTo() {
    const originalRedirectTo = uni.redirectTo
    
    uni.redirectTo = async (options) => {
      const to = this.parseRoute(options.url)
      const from = this.getCurrentRoute()
      
      const result = await this.runBeforeHooks(to, from)
      
      if (result === false) {
        return
      }
      
      if (typeof result === 'string') {
        options.url = result
      }
      
      const originalSuccess = options.success
      options.success = (res) => {
        this.runAfterHooks(to, from)
        originalSuccess && originalSuccess(res)
      }
      
      return originalRedirectTo(options)
    }
  }
  
  // 拦截 reLaunch
  interceptReLaunch() {
    const originalReLaunch = uni.reLaunch
    
    uni.reLaunch = async (options) => {
      const to = this.parseRoute(options.url)
      const from = this.getCurrentRoute()
      
      const result = await this.runBeforeHooks(to, from)
      
      if (result === false) {
        return
      }
      
      if (typeof result === 'string') {
        options.url = result
      }
      
      const originalSuccess = options.success
      options.success = (res) => {
        this.runAfterHooks(to, from)
        originalSuccess && originalSuccess(res)
      }
      
      return originalReLaunch(options)
    }
  }
  
  // 拦截 switchTab
  interceptSwitchTab() {
    const originalSwitchTab = uni.switchTab
    
    uni.switchTab = async (options) => {
      const to = this.parseRoute(options.url)
      const from = this.getCurrentRoute()
      
      const result = await this.runBeforeHooks(to, from)
      
      if (result === false) {
        return
      }
      
      if (typeof result === 'string') {
        options.url = result
      }
      
      const originalSuccess = options.success
      options.success = (res) => {
        this.runAfterHooks(to, from)
        originalSuccess && originalSuccess(res)
      }
      
      return originalSwitchTab(options)
    }
  }
  
  // 解析路由
  parseRoute(url) {
    const [path, query] = url.split('?')
    const params = {}
    
    if (query) {
      query.split('&').forEach(param => {
        const [key, value] = param.split('=')
        params[key] = decodeURIComponent(value || '')
      })
    }
    
    return {
      path,
      query: params,
      fullPath: url
    }
  }
  
  // 获取当前路由
  getCurrentRoute() {
    const pages = getCurrentPages()
    const currentPage = pages[pages.length - 1]
    
    if (!currentPage) {
      return { path: '', query: {}, fullPath: '' }
    }
    
    const path = '/' + currentPage.route
    const query = currentPage.options || {}
    const queryString = Object.keys(query)
      .map(key => `${key}=${encodeURIComponent(query[key])}`)
      .join('&')
    
    return {
      path,
      query,
      fullPath: queryString ? `${path}?${queryString}` : path
    }
  }
}

export default new RouteGuard()

2. 使用路由拦截器

// main.js
import { createSSRApp } from 'vue'
import App from './App.vue'
import routeGuard from './utils/routeGuard'

// 添加路由守卫
routeGuard.beforeEach(async (to, from) => {
  console.log('路由跳转:', from.path, '->', to.path)
  
  // 检查登录状态
  const token = uni.getStorageSync('token')
  const needAuthPages = [
    '/pages/user/profile',
    '/pages/order/list',
    '/pages/cart/cart',
    '/pages/user/settings'
  ]
  
  if (needAuthPages.includes(to.path) && !token) {
    uni.showToast({
      title: '请先登录',
      icon: 'none'
    })
    return '/pages/login/login'
  }
  
  // 检查用户权限
  const userInfo = uni.getStorageSync('userInfo')
  const adminPages = ['/pages/admin/dashboard', '/pages/admin/users']
  
  if (adminPages.includes(to.path) && (!userInfo || userInfo.role !== 'admin')) {
    uni.showToast({
      title: '权限不足',
      icon: 'none'
    })
    return false
  }
  
  return true
})

routeGuard.afterEach((to, from) => {
  console.log('路由跳转完成:', to.path)
  
  // 统计页面访问
  uni.request({
    url: 'https://api.example.com/analytics/page-view',
    method: 'POST',
    data: {
      page: to.path,
      timestamp: Date.now()
    }
  })
})

export function createApp() {
  const app = createSSRApp(App)
  return {
    app
  }
}

4.5.2 权限验证系统

1. 权限管理工具

// utils/permission.js
class PermissionManager {
  constructor() {
    this.permissions = []
    this.roles = []
    this.init()
  }
  
  // 初始化权限数据
  async init() {
    try {
      const userInfo = uni.getStorageSync('userInfo')
      if (userInfo) {
        this.permissions = userInfo.permissions || []
        this.roles = userInfo.roles || []
      }
    } catch (error) {
      console.error('权限初始化失败:', error)
    }
  }
  
  // 检查是否有指定权限
  hasPermission(permission) {
    if (!permission) return true
    return this.permissions.includes(permission)
  }
  
  // 检查是否有指定角色
  hasRole(role) {
    if (!role) return true
    return this.roles.includes(role)
  }
  
  // 检查是否有任一权限
  hasAnyPermission(permissions) {
    if (!permissions || permissions.length === 0) return true
    return permissions.some(permission => this.hasPermission(permission))
  }
  
  // 检查是否有所有权限
  hasAllPermissions(permissions) {
    if (!permissions || permissions.length === 0) return true
    return permissions.every(permission => this.hasPermission(permission))
  }
  
  // 检查是否有任一角色
  hasAnyRole(roles) {
    if (!roles || roles.length === 0) return true
    return roles.some(role => this.hasRole(role))
  }
  
  // 更新权限数据
  updatePermissions(userInfo) {
    this.permissions = userInfo.permissions || []
    this.roles = userInfo.roles || []
    
    // 缓存到本地
    uni.setStorageSync('userInfo', userInfo)
  }
  
  // 清除权限数据
  clearPermissions() {
    this.permissions = []
    this.roles = []
    uni.removeStorageSync('userInfo')
    uni.removeStorageSync('token')
  }
  
  // 检查页面访问权限
  checkPagePermission(pagePath) {
    const pagePermissions = {
      '/pages/admin/dashboard': ['admin.dashboard'],
      '/pages/admin/users': ['admin.users'],
      '/pages/order/manage': ['order.manage'],
      '/pages/product/edit': ['product.edit'],
      '/pages/finance/report': ['finance.view']
    }
    
    const requiredPermissions = pagePermissions[pagePath]
    if (!requiredPermissions) return true
    
    return this.hasAnyPermission(requiredPermissions)
  }
  
  // 检查功能权限
  checkFeaturePermission(feature) {
    const featurePermissions = {
      'create_order': ['order.create'],
      'edit_product': ['product.edit'],
      'delete_user': ['user.delete'],
      'view_report': ['finance.view', 'admin.dashboard']
    }
    
    const requiredPermissions = featurePermissions[feature]
    if (!requiredPermissions) return true
    
    return this.hasAnyPermission(requiredPermissions)
  }
}

export default new PermissionManager()

2. 权限指令

// utils/directives.js
import permission from './permission'

// v-permission 指令
export const vPermission = {
  mounted(el, binding) {
    const { value } = binding
    
    if (value && !permission.hasAnyPermission(Array.isArray(value) ? value : [value])) {
      el.style.display = 'none'
    }
  },
  
  updated(el, binding) {
    const { value } = binding
    
    if (value && !permission.hasAnyPermission(Array.isArray(value) ? value : [value])) {
      el.style.display = 'none'
    } else {
      el.style.display = ''
    }
  }
}

// v-role 指令
export const vRole = {
  mounted(el, binding) {
    const { value } = binding
    
    if (value && !permission.hasAnyRole(Array.isArray(value) ? value : [value])) {
      el.style.display = 'none'
    }
  },
  
  updated(el, binding) {
    const { value } = binding
    
    if (value && !permission.hasAnyRole(Array.isArray(value) ? value : [value])) {
      el.style.display = 'none'
    } else {
      el.style.display = ''
    }
  }
}

3. 在页面中使用权限控制

<template>
  <view class="permission-demo">
    <text class="title">权限控制示例</text>
    
    <!-- 基础权限控制 -->
    <view class="section">
      <text class="section-title">基础权限</text>
      
      <button 
        v-permission="['order.create']"
        @click="createOrder"
      >
        创建订单
      </button>
      
      <button 
        v-permission="['product.edit']"
        @click="editProduct"
      >
        编辑商品
      </button>
      
      <button 
        v-permission="['user.delete']"
        @click="deleteUser"
      >
        删除用户
      </button>
    </view>
    
    <!-- 角色权限控制 -->
    <view class="section">
      <text class="section-title">角色权限</text>
      
      <view v-role="['admin']" class="admin-panel">
        <text class="panel-title">管理员面板</text>
        <button @click="goToAdminDashboard">管理后台</button>
        <button @click="manageUsers">用户管理</button>
      </view>
      
      <view v-role="['manager', 'admin']" class="manager-panel">
        <text class="panel-title">管理者面板</text>
        <button @click="viewReports">查看报表</button>
        <button @click="manageOrders">订单管理</button>
      </view>
      
      <view v-role="['user']" class="user-panel">
        <text class="panel-title">用户面板</text>
        <button @click="viewProfile">个人资料</button>
        <button @click="viewOrders">我的订单</button>
      </view>
    </view>
    
    <!-- 动态权限检查 -->
    <view class="section">
      <text class="section-title">动态权限检查</text>
      
      <view class="permission-info">
        <text class="info-label">当前权限:</text>
        <text class="info-value">{{ currentPermissions.join(', ') }}</text>
      </view>
      
      <view class="permission-info">
        <text class="info-label">当前角色:</text>
        <text class="info-value">{{ currentRoles.join(', ') }}</text>
      </view>
      
      <button @click="checkPermission('order.create')">检查创建订单权限</button>
      <button @click="checkRole('admin')">检查管理员角色</button>
      <button @click="refreshPermissions">刷新权限</button>
    </view>
  </view>
</template>

<script>
import permission from '@/utils/permission'

export default {
  name: 'PermissionDemo',
  data() {
    return {
      currentPermissions: [],
      currentRoles: []
    }
  },
  onLoad() {
    this.loadPermissions()
  },
  methods: {
    // 加载权限信息
    loadPermissions() {
      this.currentPermissions = permission.permissions
      this.currentRoles = permission.roles
    },
    
    // 创建订单
    createOrder() {
      if (!permission.checkFeaturePermission('create_order')) {
        uni.showToast({
          title: '权限不足',
          icon: 'none'
        })
        return
      }
      
      uni.showToast({
        title: '创建订单成功',
        icon: 'success'
      })
    },
    
    // 编辑商品
    editProduct() {
      if (!permission.checkFeaturePermission('edit_product')) {
        uni.showToast({
          title: '权限不足',
          icon: 'none'
        })
        return
      }
      
      uni.navigateTo({
        url: '/pages/product/edit'
      })
    },
    
    // 删除用户
    deleteUser() {
      if (!permission.checkFeaturePermission('delete_user')) {
        uni.showToast({
          title: '权限不足',
          icon: 'none'
        })
        return
      }
      
      uni.showModal({
        title: '确认删除',
        content: '确定要删除该用户吗?',
        success: (res) => {
          if (res.confirm) {
            uni.showToast({
              title: '删除成功',
              icon: 'success'
            })
          }
        }
      })
    },
    
    // 跳转到管理后台
    goToAdminDashboard() {
      if (!permission.checkPagePermission('/pages/admin/dashboard')) {
        uni.showToast({
          title: '权限不足',
          icon: 'none'
        })
        return
      }
      
      uni.navigateTo({
        url: '/pages/admin/dashboard'
      })
    },
    
    // 用户管理
    manageUsers() {
      uni.navigateTo({
        url: '/pages/admin/users'
      })
    },
    
    // 查看报表
    viewReports() {
      if (!permission.checkFeaturePermission('view_report')) {
        uni.showToast({
          title: '权限不足',
          icon: 'none'
        })
        return
      }
      
      uni.navigateTo({
        url: '/pages/finance/report'
      })
    },
    
    // 订单管理
    manageOrders() {
      uni.navigateTo({
        url: '/pages/order/manage'
      })
    },
    
    // 查看个人资料
    viewProfile() {
      uni.navigateTo({
        url: '/pages/user/profile'
      })
    },
    
    // 查看我的订单
    viewOrders() {
      uni.navigateTo({
        url: '/pages/order/list'
      })
    },
    
    // 检查权限
    checkPermission(perm) {
      const hasPermission = permission.hasPermission(perm)
      uni.showToast({
        title: hasPermission ? '有权限' : '无权限',
        icon: hasPermission ? 'success' : 'none'
      })
    },
    
    // 检查角色
    checkRole(role) {
      const hasRole = permission.hasRole(role)
      uni.showToast({
        title: hasRole ? '有角色' : '无角色',
        icon: hasRole ? 'success' : 'none'
      })
    },
    
    // 刷新权限
    async refreshPermissions() {
      try {
        // 模拟从服务器获取最新权限
        const response = await uni.request({
          url: 'https://api.example.com/user/permissions',
          header: {
            'Authorization': 'Bearer ' + uni.getStorageSync('token')
          }
        })
        
        if (response.data.code === 200) {
          permission.updatePermissions(response.data.data)
          this.loadPermissions()
          
          uni.showToast({
            title: '权限刷新成功',
            icon: 'success'
          })
        }
      } catch (error) {
        console.error('刷新权限失败:', error)
        uni.showToast({
          title: '刷新失败',
          icon: 'error'
        })
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.permission-demo {
  padding: 20rpx;
  
  .title {
    display: block;
    font-size: 32rpx;
    font-weight: bold;
    text-align: center;
    margin-bottom: 30rpx;
    color: #333;
  }
  
  .section {
    margin-bottom: 40rpx;
    padding: 20rpx;
    background-color: #f8f8f8;
    border-radius: 12rpx;
    
    .section-title {
      display: block;
      font-size: 28rpx;
      font-weight: bold;
      margin-bottom: 20rpx;
      color: #333;
    }
    
    button {
      width: 100%;
      margin-bottom: 15rpx;
      padding: 20rpx;
      background-color: #007aff;
      color: white;
      border: none;
      border-radius: 8rpx;
      font-size: 26rpx;
      
      &:last-child {
        margin-bottom: 0;
      }
      
      &:active {
        background-color: #0056cc;
      }
    }
    
    .admin-panel,
    .manager-panel,
    .user-panel {
      padding: 20rpx;
      margin-bottom: 20rpx;
      border-radius: 8rpx;
      
      .panel-title {
        display: block;
        font-size: 24rpx;
        font-weight: bold;
        margin-bottom: 15rpx;
        color: #333;
      }
    }
    
    .admin-panel {
      background-color: #ffe6e6;
      border: 2rpx solid #ff4d4f;
    }
    
    .manager-panel {
      background-color: #fff7e6;
      border: 2rpx solid #fa8c16;
    }
    
    .user-panel {
      background-color: #e6f7ff;
      border: 2rpx solid #1890ff;
    }
    
    .permission-info {
      display: flex;
      align-items: center;
      padding: 15rpx;
      margin-bottom: 10rpx;
      background-color: white;
      border-radius: 8rpx;
      
      .info-label {
        width: 150rpx;
        font-size: 24rpx;
        color: #666;
      }
      
      .info-value {
        flex: 1;
        font-size: 24rpx;
        color: #333;
        font-weight: bold;
      }
    }
  }
}
</style>

4.5.3 登录状态管理

1. 登录状态工具

// utils/auth.js
class AuthManager {
  constructor() {
    this.token = ''
    this.userInfo = null
    this.loginTime = 0
    this.tokenExpireTime = 0
    this.init()
  }
  
  // 初始化认证信息
  init() {
    try {
      this.token = uni.getStorageSync('token') || ''
      this.userInfo = uni.getStorageSync('userInfo') || null
      this.loginTime = uni.getStorageSync('loginTime') || 0
      this.tokenExpireTime = uni.getStorageSync('tokenExpireTime') || 0
    } catch (error) {
      console.error('认证信息初始化失败:', error)
    }
  }
  
  // 检查是否已登录
  isLoggedIn() {
    return !!this.token && !!this.userInfo && !this.isTokenExpired()
  }
  
  // 检查Token是否过期
  isTokenExpired() {
    if (!this.tokenExpireTime) return false
    return Date.now() > this.tokenExpireTime
  }
  
  // 登录
  async login(credentials) {
    try {
      const response = await uni.request({
        url: 'https://api.example.com/auth/login',
        method: 'POST',
        data: credentials
      })
      
      if (response.data.code === 200) {
        const { token, userInfo, expiresIn } = response.data.data
        
        this.token = token
        this.userInfo = userInfo
        this.loginTime = Date.now()
        this.tokenExpireTime = Date.now() + (expiresIn * 1000)
        
        // 保存到本地存储
        uni.setStorageSync('token', this.token)
        uni.setStorageSync('userInfo', this.userInfo)
        uni.setStorageSync('loginTime', this.loginTime)
        uni.setStorageSync('tokenExpireTime', this.tokenExpireTime)
        
        return { success: true, data: userInfo }
      } else {
        return { success: false, message: response.data.message }
      }
    } catch (error) {
      console.error('登录失败:', error)
      return { success: false, message: '网络错误' }
    }
  }
  
  // 登出
  async logout() {
    try {
      // 调用服务器登出接口
      if (this.token) {
        await uni.request({
          url: 'https://api.example.com/auth/logout',
          method: 'POST',
          header: {
            'Authorization': 'Bearer ' + this.token
          }
        })
      }
    } catch (error) {
      console.error('服务器登出失败:', error)
    } finally {
      // 清除本地数据
      this.clearAuthData()
    }
  }
  
  // 清除认证数据
  clearAuthData() {
    this.token = ''
    this.userInfo = null
    this.loginTime = 0
    this.tokenExpireTime = 0
    
    uni.removeStorageSync('token')
    uni.removeStorageSync('userInfo')
    uni.removeStorageSync('loginTime')
    uni.removeStorageSync('tokenExpireTime')
  }
  
  // 刷新Token
  async refreshToken() {
    try {
      const response = await uni.request({
        url: 'https://api.example.com/auth/refresh',
        method: 'POST',
        header: {
          'Authorization': 'Bearer ' + this.token
        }
      })
      
      if (response.data.code === 200) {
        const { token, expiresIn } = response.data.data
        
        this.token = token
        this.tokenExpireTime = Date.now() + (expiresIn * 1000)
        
        uni.setStorageSync('token', this.token)
        uni.setStorageSync('tokenExpireTime', this.tokenExpireTime)
        
        return true
      }
    } catch (error) {
      console.error('刷新Token失败:', error)
    }
    
    return false
  }
  
  // 获取用户信息
  getUserInfo() {
    return this.userInfo
  }
  
  // 获取Token
  getToken() {
    return this.token
  }
  
  // 更新用户信息
  updateUserInfo(userInfo) {
    this.userInfo = { ...this.userInfo, ...userInfo }
    uni.setStorageSync('userInfo', this.userInfo)
  }
  
  // 检查登录状态并自动刷新
  async checkAndRefreshAuth() {
    if (!this.token) {
      return false
    }
    
    // Token即将过期,尝试刷新
    if (this.tokenExpireTime - Date.now() < 5 * 60 * 1000) { // 5分钟内过期
      const refreshed = await this.refreshToken()
      if (!refreshed) {
        this.clearAuthData()
        return false
      }
    }
    
    return true
  }
}

export default new AuthManager()

2. 登录页面示例

<template>
  <view class="login-page">
    <view class="login-container">
      <text class="title">用户登录</text>
      
      <view class="form">
        <view class="input-group">
          <text class="label">用户名</text>
          <input 
            v-model="form.username"
            class="input"
            placeholder="请输入用户名"
            :disabled="loading"
          />
        </view>
        
        <view class="input-group">
          <text class="label">密码</text>
          <input 
            v-model="form.password"
            class="input"
            type="password"
            placeholder="请输入密码"
            :disabled="loading"
          />
        </view>
        
        <view class="checkbox-group">
          <checkbox 
            v-model="form.rememberMe"
            class="checkbox"
          />
          <text class="checkbox-label">记住我</text>
        </view>
        
        <button 
          class="login-btn"
          :class="{ loading: loading }"
          :disabled="loading || !canSubmit"
          @click="handleLogin"
        >
          {{ loading ? '登录中...' : '登录' }}
        </button>
        
        <view class="links">
          <text class="link" @click="goToRegister">注册账号</text>
          <text class="link" @click="goToForgotPassword">忘记密码</text>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
import auth from '@/utils/auth'
import permission from '@/utils/permission'

export default {
  name: 'LoginPage',
  data() {
    return {
      form: {
        username: '',
        password: '',
        rememberMe: false
      },
      loading: false
    }
  },
  computed: {
    canSubmit() {
      return this.form.username.trim() && this.form.password.trim()
    }
  },
  onLoad(options) {
    // 检查是否已登录
    if (auth.isLoggedIn()) {
      this.redirectAfterLogin(options.redirect)
      return
    }
    
    // 自动填充记住的用户名
    const rememberedUsername = uni.getStorageSync('rememberedUsername')
    if (rememberedUsername) {
      this.form.username = rememberedUsername
      this.form.rememberMe = true
    }
  },
  methods: {
    // 处理登录
    async handleLogin() {
      if (!this.canSubmit) {
        uni.showToast({
          title: '请填写完整信息',
          icon: 'none'
        })
        return
      }
      
      this.loading = true
      
      try {
        const result = await auth.login({
          username: this.form.username,
          password: this.form.password
        })
        
        if (result.success) {
          // 记住用户名
          if (this.form.rememberMe) {
            uni.setStorageSync('rememberedUsername', this.form.username)
          } else {
            uni.removeStorageSync('rememberedUsername')
          }
          
          // 更新权限信息
          permission.updatePermissions(result.data)
          
          uni.showToast({
            title: '登录成功',
            icon: 'success'
          })
          
          // 延迟跳转,让用户看到成功提示
          setTimeout(() => {
            this.redirectAfterLogin()
          }, 1500)
        } else {
          uni.showToast({
            title: result.message || '登录失败',
            icon: 'none'
          })
        }
      } catch (error) {
        console.error('登录错误:', error)
        uni.showToast({
          title: '登录失败',
          icon: 'error'
        })
      } finally {
        this.loading = false
      }
    },
    
    // 登录后重定向
    redirectAfterLogin(redirect) {
      const pages = getCurrentPages()
      const currentPage = pages[pages.length - 1]
      const redirectUrl = redirect || currentPage.options?.redirect || '/pages/index/index'
      
      if (redirectUrl.startsWith('/pages/') && redirectUrl.includes('tabBar')) {
        uni.switchTab({ url: redirectUrl })
      } else {
        uni.reLaunch({ url: redirectUrl })
      }
    },
    
    // 跳转到注册页面
    goToRegister() {
      uni.navigateTo({
        url: '/pages/register/register'
      })
    },
    
    // 跳转到忘记密码页面
    goToForgotPassword() {
      uni.navigateTo({
        url: '/pages/forgot-password/forgot-password'
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.login-page {
  min-height: 100vh;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40rpx;
  
  .login-container {
    width: 100%;
    max-width: 600rpx;
    background-color: white;
    border-radius: 20rpx;
    padding: 60rpx 40rpx;
    box-shadow: 0 20rpx 40rpx rgba(0, 0, 0, 0.1);
    
    .title {
      display: block;
      font-size: 48rpx;
      font-weight: bold;
      text-align: center;
      margin-bottom: 60rpx;
      color: #333;
    }
    
    .form {
      .input-group {
        margin-bottom: 40rpx;
        
        .label {
          display: block;
          font-size: 28rpx;
          color: #666;
          margin-bottom: 15rpx;
        }
        
        .input {
          width: 100%;
          height: 80rpx;
          padding: 0 20rpx;
          border: 2rpx solid #e5e5e5;
          border-radius: 12rpx;
          font-size: 28rpx;
          background-color: #f8f8f8;
          
          &:focus {
            border-color: #007aff;
            background-color: white;
          }
          
          &:disabled {
            opacity: 0.6;
          }
        }
      }
      
      .checkbox-group {
        display: flex;
        align-items: center;
        margin-bottom: 40rpx;
        
        .checkbox {
          margin-right: 15rpx;
        }
        
        .checkbox-label {
          font-size: 26rpx;
          color: #666;
        }
      }
      
      .login-btn {
        width: 100%;
        height: 80rpx;
        background-color: #007aff;
        color: white;
        border: none;
        border-radius: 12rpx;
        font-size: 32rpx;
        font-weight: bold;
        margin-bottom: 40rpx;
        
        &:active {
          background-color: #0056cc;
        }
        
        &:disabled {
          background-color: #ccc;
          opacity: 0.6;
        }
        
        &.loading {
          background-color: #ccc;
        }
      }
      
      .links {
        display: flex;
        justify-content: space-between;
        
        .link {
          font-size: 26rpx;
          color: #007aff;
          
          &:active {
            opacity: 0.7;
          }
        }
      }
    }
  }
}
</style>