路由管理(Vue Router)

yarn add vue-router@5

plaintext
复制代码

**2. 创建路由配置文件**  
在项目 `src` 目录下新建 `router/index.ts` 文件,定义路由规则与路由实例。使用 `RouteRecordRaw` 类型约束路由配置,确保 TypeScript 类型检查生效:
```typescript
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
// 懒加载组件,减少初始加载时间
const Home = () => import('@/views/Home.vue');
const ProductDetail = () => import('@/views/ProductDetail.vue');
const Login = () => import('@/views/Login.vue');

// 路由规则数组,使用 RouteRecordRaw 类型约束
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    meta: { title: '首页 - 电商平台' } // 元信息,可用于设置页面标题等
  },
  {
    path: '/product/:id', // 动态路由参数,用于商品详情页
    name: 'ProductDetail',
    component: ProductDetail,
    meta: { title: '商品详情 - 电商平台' }
  },
  {
    path: '/login',
    name: 'Login',
    component: Login,
    meta: { title: '登录 - 电商平台', requiresAuth: false } // 无需登录即可访问
  }
];

// 创建路由实例,使用 HTML5 history 模式(无 # 号)
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL), // 基础路径从环境变量获取
  routes
});

export default router;

3. 挂载路由实例
在应用入口文件 main.ts 中,通过 app.use(router) 挂载路由实例,使路由功能在整个应用中可用:

typescript
复制代码
import { createApp } from 'vue';
import App from './App.vue';
import router from './router'; // 导入路由实例

const app = createApp(App);
app.use(router); // 挂载路由
app.mount('#app');

配置要点

  • 推荐使用 createWebHistory 模式(需后端配合处理 404 页面),相比 createWebHashHistory(带 # 号)更符合 SEO 需求。
  • 组件懒加载通过 () => import('路径') 实现,Vue Router 5 对此优化后可减少初始加载时间 40%,显著提升首屏渲染速度[10]。
  • RouteRecordRaw 类型确保路由配置的类型安全,避免路径、组件等关键属性拼写错误。

路由跳转与参数传递

路由跳转是用户导航的核心操作,Vue Router 提供声明式和编程式两种跳转方式,并支持灵活的参数传递机制,满足电商场景中商品列表跳转详情页、搜索结果页等需求。

1. 声明式跳转:<router-link> 组件
通过 <router-link> 组件实现页面跳转,无需手动绑定点击事件,自动渲染为 <a> 标签,且支持激活状态样式(通过 router-link-active 类名)。
电商案例:商品列表页跳转到详情页

plaintext
复制代码
<!-- ProductList.vue -->
<template>
  <div class="product-list">
    <!-- 循环渲染商品列表 -->
    <div v-for="product in products" :key="product.id" class="product-item">
      <!-- 声明式跳转,传递 params 参数 -->
      <router-link :to="`/product/${product.id}`" class="product-link">
        <h3>{{ product.name }}</h3>
        <p>价格:{{ product.price }} 元</p>
      </router-link>
    </div>
  </div>
</template>

2. 编程式跳转:router.push() 方法
通过路由实例的 push 方法实现跳转,适用于需逻辑判断后跳转的场景(如表单提交后)。
电商案例:搜索按钮触发跳转

plaintext
复制代码
<!-- SearchBar.vue -->
<template>
  <div class="search-bar">
    <input v-model="keyword" placeholder="输入商品关键词..." />
    <button @click="handleSearch">搜索</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';

const keyword = ref('');
const router = useRouter(); // 获取路由实例

const handleSearch = () => {
  if (keyword.value.trim()) {
    // 编程式跳转,传递 query 参数(URL 显示为 ?keyword=xxx)
    router.push({
      path: '/search',
      query: { keyword: keyword.value } // query 参数会拼接到 URL 查询字符串
    });
  }
};
</script>

3. 参数传递对比:query vs params

类型 特点 URL 格式 路由配置 获取方式 适用场景
query 类似 GET 请求参数,可保留 /search?keyword=手机 无需特殊配置 useRoute().query.keyword 搜索、筛选等非核心参数
params 动态路由参数,需预定义 /product/123 path: '/product/:id' useRoute().params.id 商品 ID、用户 ID 等核心标识

参数接收示例(商品详情页)

plaintext
复制代码
<!-- ProductDetail.vue -->
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { onMounted, ref } from 'vue';

const route = useRoute(); // 获取当前路由信息
const product = ref(null);

onMounted(() => {
  // 从 params 中获取商品 ID(路由配置中定义的 :id)
  const productId = route.params.id; 
  // 调用 API 获取商品详情
  fetchProductDetail(productId).then(data => {
    product.value = data;
  });
});
</script>

嵌套路由

嵌套路由用于实现页面结构的层级关系,如电商网站的“分类页-子分类页”:点击“手机分类”后,页面左侧显示分类导航(父组件),右侧显示手机列表(子组件)。其核心是通过 children 配置子路由,并在父组件中使用 <router-view> 渲染子组件。

1. 路由配置
router/index.ts 中定义父路由与子路由的层级关系:

typescript
复制代码
const routes: RouteRecordRaw[] = [
  {
    path: '/category',
    name: 'Category',
    component: () => import('@/views/CategoryLayout.vue'), // 父组件(布局组件)
    children: [
      {
        path: 'phone', // 子路由路径(完整 URL 为 /category/phone)
        name: 'PhoneList',
        component: () => import('@/views/PhoneList.vue') // 手机列表子组件
      },
      {
        path: 'laptop', // 完整 URL 为 /category/laptop
        name: 'LaptopList',
        component: () => import('@/views/LaptopList.vue') // 笔记本列表子组件
      },
      {
        path: '', // 默认子路由(当 URL 为 /category 时显示)
        redirect: 'phone' // 重定向到手机列表
      }
    ]
  }
];

2. 父组件模板(布局组件)
父组件 CategoryLayout.vue 需包含 <router-view>,用于渲染子路由对应的组件:

plaintext
复制代码
<!-- CategoryLayout.vue -->
<template>
  <div class="category-page">
    <!-- 左侧分类导航(父组件固定内容) -->
    <div class="category-sidebar">
      <h2>商品分类</h2>
      <ul>
        <!-- 跳转到子路由,使用相对路径(无需 / 开头) -->
        <li><router-link to="phone">手机</router-link></li>
        <li><router-link to="laptop">笔记本</router-link></li>
      </ul>
    </div>
    
    <!-- 右侧内容区,子路由组件将渲染到此处 -->
    <div class="category-content">
      <router-view /> <!-- 子路由出口 -->
    </div>
  </div>
</template>

<style scoped>
/* 布局样式:左侧导航固定宽度,右侧内容自适应 */
.category-page {
  display: flex;
  gap: 20px;
  padding: 20px;
}
.category-sidebar {
  width: 200px;
  border-right: 1px solid #eee;
}
.category-content {
  flex: 1;
}
</style>

嵌套路由规则

  • 子路由 path 无需以 / 开头,会自动拼接父路由路径(如父路由 /category + 子路由 phone/category/phone)。
  • 父组件必须包含 <router-view>,否则子组件无法渲染。
  • 可通过 redirect 设置默认子路由,避免父路由单独访问时内容为空。

路由守卫

路由守卫用于控制导航权限,如登录验证、角色权限检查、数据预加载等,是实现安全访问和优化用户体验的关键机制。Vue Router 提供全局守卫、路由独享守卫和组件内守卫三种类型,覆盖不同层级的导航控制需求。

1. 全局守卫:beforeEach
全局守卫作用于所有路由跳转,常用于验证用户登录状态。电商案例:未登录用户访问购物车时跳转至登录页

typescript
复制代码
// router/index.ts
import { useUserStore } from '@/stores/user'; // 假设使用 Pinia 存储用户状态

// 全局前置守卫,每次路由跳转前触发
router.beforeEach((to, from) => {
  const userStore = useUserStore(); // 获取用户状态
  
  // 判断目标路由是否需要登录(通过 meta.requiresAuth 标记,默认需要)
  const requiresAuth = to.meta.requiresAuth ?? true;
  
  // 未登录且需要登录的路由,重定向到登录页,并记录目标路径(登录后返回)
  if (!userStore.isLogin && requiresAuth) {
    return { 
      path: '/login',
      query: { redirect: to.fullPath } // 存储目标路径,登录后可跳转回来
    };
  }
  
  // 设置页面标题(从路由 meta.title 获取)
  document.title = to.meta.title as string || '电商平台';
});

2. 路由独享守卫:beforeEnter
路由独享守卫仅作用于特定路由,适用于细化权限控制(如管理员路由限制)。电商案例:仅管理员可访问订单管理页

typescript
复制代码
// router/index.ts 中的路由配置
{
  path: '/admin/orders',
  name: 'AdminOrders',
  component: () => import('@/views/admin/Orders.vue'),
  meta: { title: '订单管理 - 管理员后台' },
  // 路由独享守卫,仅管理员可访问
  beforeEnter: (to, from) => {
    const userStore = useUserStore();
    // 非管理员用户禁止访问,返回 false 取消导航
    if (!userStore.isAdmin) {
      alert('无权限访问管理员页面');
      return false; // 阻止跳转
    }
  }
}

3. 组件内守卫:beforeRouteEnter
组件内守卫作用于当前组件,常用于路由进入前预加载数据,避免页面空白。电商案例:商品详情页进入前预加载数据

plaintext
复制代码
<!-- ProductDetail.vue -->
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { ref } from 'vue';

const product = ref(null);
const route = useRoute();

// 组件内守卫:路由进入前触发(此时组件实例未创建,无法访问 this)
beforeRouteEnter(async (to) => {
  const productId = to.params.id;
  // 预加载商品数据,返回结果会传递给 next 回调(Vue 4 可直接 return 数据)
  const productData = await fetchProductDetail(productId);
  // 返回数据会作为参数传递给组件内的 setup 或 created 钩子
  return { productData };
}, (to, from, next) => {
  // Vue 3 兼容写法(Vue 4 可省略 next,直接 return 数据)
  next((vm) => {
    vm.product = vm.productData; // 将预加载数据赋值给组件实例
  });
});
</script>

守卫核心要点

  • Vue 4+ 中,守卫函数可直接 return 路径(重定向)、false(取消导航)或 Promise(异步处理),无需显式调用 next()
  • 全局守卫需注意“守卫复用”问题,避免重复创建状态实例(如 Pinia store 应在守卫内部获取,而非外部定义)。
  • 组件内 beforeRouteEnter 无法直接访问组件实例(thissetup 变量),需通过返回数据或 next(vm => {}) 间接操作。

通过路由守卫的组合使用,可构建多层级的权限控制体系,既保障了系统安全,又通过数据预加载提升了页面响应速度,为电商平台等复杂应用提供可靠的导航管理能力。