← 返回主页

第11课: Vue Router路由管理

什么是Vue Router?

Vue Router是Vue.js的官方路由管理器,用于构建单页面应用(SPA)。它允许我们在不刷新页面的情况下切换视图。

安装Vue Router

# npm
npm install vue-router@4

# yarn
yarn add vue-router@4

# pnpm
pnpm add vue-router@4

基础配置

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App)
  .use(router)
  .mount('#app')

路由视图和链接

<!-- App.vue -->
<template>
  <div>
    <nav>
      <router-link to="/">首页</router-link>
      <router-link to="/about">关于</router-link>
    </nav>

    <!-- 路由出口 -->
    <router-view />
  </div>
</template>

动态路由

// 路由配置
const routes = [
  {
    path: '/user/:id',
    name: 'User',
    component: User
  },
  {
    path: '/post/:id/:title',
    component: Post
  }
]
<!-- User.vue -->
<template>
  <div>
    <h2>用户ID: {{ $route.params.id }}</h2>
    <h2>用户ID: {{ userId }}</h2>
  </div>
</template>

<script setup>
import { useRoute } from 'vue-router'
import { computed } from 'vue'

const route = useRoute()
const userId = computed(() => route.params.id)
</script>

编程式导航

<script setup>
import { useRouter } from 'vue-router'

const router = useRouter()

// 字符串路径
function goToAbout() {
  router.push('/about')
}

// 对象
function goToUser() {
  router.push({ path: '/user/123' })
}

// 命名路由
function goToUserNamed() {
  router.push({ name: 'User', params: { id: 123 } })
}

// 带查询参数
function goToSearch() {
  router.push({ path: '/search', query: { q: 'vue' } })
  // 结果: /search?q=vue
}

// 替换当前历史记录
function replaceRoute() {
  router.replace('/home')
}

// 前进后退
function goBack() {
  router.go(-1) // 后退一步
}

function goForward() {
  router.go(1) // 前进一步
}
</script>

嵌套路由

const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      {
        path: '',
        component: UserHome
      },
      {
        path: 'profile',
        component: UserProfile
      },
      {
        path: 'posts',
        component: UserPosts
      }
    ]
  }
]
<!-- User.vue -->
<template>
  <div>
    <h2>用户 {{ $route.params.id }}</h2>
    <nav>
      <router-link :to="`/user/${$route.params.id}`">首页</router-link>
      <router-link :to="`/user/${$route.params.id}/profile`">资料</router-link>
      <router-link :to="`/user/${$route.params.id}/posts`">文章</router-link>
    </nav>
    <router-view />
  </div>
</template>

命名视图

const routes = [
  {
    path: '/',
    components: {
      default: Home,
      sidebar: Sidebar,
      footer: Footer
    }
  }
]
<template>
  <div>
    <router-view />
    <router-view name="sidebar" />
    <router-view name="footer" />
  </div>
</template>

路由懒加载

const routes = [
  {
    path: '/',
    component: () => import('../views/Home.vue')
  },
  {
    path: '/about',
    component: () => import('../views/About.vue')
  },
  {
    path: '/user/:id',
    component: () => import('../views/User.vue')
  }
]

导航守卫

全局前置守卫

router.beforeEach((to, from, next) => {
  console.log('导航到:', to.path)
  console.log('来自:', from.path)

  // 检查是否需要登录
  if (to.meta.requiresAuth && !isLoggedIn()) {
    next('/login')
  } else {
    next()
  }
})

全局后置钩子

router.afterEach((to, from) => {
  // 设置页面标题
  document.title = to.meta.title || '默认标题'
})

路由独享守卫

const routes = [
  {
    path: '/admin',
    component: Admin,
    beforeEnter: (to, from, next) => {
      if (isAdmin()) {
        next()
      } else {
        next('/login')
      }
    }
  }
]

组件内守卫

<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

// 离开路由前
onBeforeRouteLeave((to, from) => {
  const answer = window.confirm('确定要离开吗?未保存的更改将丢失。')
  if (!answer) return false
})

// 路由更新时(同一组件,参数变化)
onBeforeRouteUpdate((to, from) => {
  console.log('路由参数更新')
})
</script>

路由元信息

const routes = [
  {
    path: '/admin',
    component: Admin,
    meta: {
      requiresAuth: true,
      title: '管理后台',
      roles: ['admin']
    }
  }
]
router.beforeEach((to, from, next) => {
  // 访问元信息
  if (to.meta.requiresAuth) {
    // 检查认证
  }

  // 设置标题
  document.title = to.meta.title || '默认标题'

  next()
})

路由传参

const routes = [
  {
    path: '/user/:id',
    component: User,
    props: true // 将params作为props传递
  },
  {
    path: '/search',
    component: Search,
    props: route => ({ query: route.query.q }) // 函数模式
  }
]
<!-- User.vue -->
<script setup>
defineProps(['id'])
</script>

<template>
  <div>用户ID: {{ id }}</div>
</template>

实战示例:完整路由配置

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/Home.vue'),
    meta: { title: '首页' }
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('../views/Login.vue'),
    meta: { title: '登录' }
  },
  {
    path: '/dashboard',
    component: () => import('../layouts/Dashboard.vue'),
    meta: { requiresAuth: true },
    children: [
      {
        path: '',
        name: 'DashboardHome',
        component: () => import('../views/dashboard/Home.vue'),
        meta: { title: '控制台' }
      },
      {
        path: 'profile',
        name: 'Profile',
        component: () => import('../views/dashboard/Profile.vue'),
        meta: { title: '个人资料' }
      },
      {
        path: 'settings',
        name: 'Settings',
        component: () => import('../views/dashboard/Settings.vue'),
        meta: { title: '设置' }
      }
    ]
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('../views/NotFound.vue'),
    meta: { title: '404' }
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

// 全局前置守卫
router.beforeEach((to, from, next) => {
  const isLoggedIn = localStorage.getItem('token')

  if (to.meta.requiresAuth && !isLoggedIn) {
    next('/login')
  } else {
    next()
  }
})

// 全局后置钩子
router.afterEach((to) => {
  document.title = to.meta.title || 'Vue App'
})

export default router

滚动行为

const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else if (to.hash) {
      return { el: to.hash }
    } else {
      return { top: 0 }
    }
  }
})

练习

  1. 创建一个多页面应用,包含首页、关于、联系页面
  2. 实现用户详情页,使用动态路由参数
  3. 创建一个需要登录的管理后台,使用导航守卫
  4. 实现嵌套路由,创建一个包含多个子页面的用户中心