Skip to content

router.js

文件信息

  • 📄 原文件:01_router.js
  • 🔤 语言:javascript

Vue Router 路由配置 Vue Router 是 Vue.js 的官方路由管理器。 用于构建单页面应用(SPA)的页面导航。 安装:npm install vue-router@4

完整代码

javascript
/**
 * ============================================================
 *                    Vue Router 路由配置
 * ============================================================
 * Vue Router 是 Vue.js 的官方路由管理器。
 * 用于构建单页面应用(SPA)的页面导航。
 *
 * 安装:npm install vue-router@4
 * ============================================================
 */

import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';

// ============================================================
//                    1. 基本路由配置
// ============================================================

/**
 * 【路由配置】
 *
 * 每个路由对象包含:
 * - path: URL 路径
 * - name: 路由名称(可选,用于编程式导航)
 * - component: 对应的组件
 * - meta: 元信息(可选)
 * - children: 嵌套路由(可选)
 */

// 导入页面组件
// 方式1:静态导入(适合首屏需要的页面)
import Home from '@/views/Home.vue';
import About from '@/views/About.vue';

// 方式2:动态导入(路由懒加载,按需加载)
const User = () => import('@/views/User.vue');
const UserProfile = () => import('@/views/UserProfile.vue');
const UserSettings = () => import('@/views/UserSettings.vue');
const NotFound = () => import('@/views/NotFound.vue');

// 路由配置数组
const routes = [
    // ============================================================
    //                    基本路由
    // ============================================================

    {
        path: '/',                    // URL 路径
        name: 'Home',                 // 路由名称
        component: Home,              // 对应组件
        meta: {                       // 路由元信息
            title: '首页',
            requiresAuth: false,
        },
    },

    {
        path: '/about',
        name: 'About',
        component: About,
        meta: { title: '关于我们' },
    },

    // ============================================================
    //                    动态路由
    // ============================================================

    /**
     * 【动态路由参数】
     *
     * 使用 :参数名 定义动态段
     * - 在组件中通过 $route.params.参数名 访问
     * - 或使用 useRoute().params.参数名
     */

    {
        path: '/user/:id',            // :id 是动态参数
        name: 'User',
        component: User,
        meta: { title: '用户详情' },

        // 将路由参数作为 props 传递给组件
        props: true,  // 组件会收到 props: { id: '...' }
    },

    {
        // 多个动态参数
        path: '/post/:category/:postId',
        name: 'Post',
        component: () => import('@/views/Post.vue'),
        props: true,  // 组件收到 { category, postId }
    },

    {
        // 可选参数(在参数后加 ?)
        path: '/search/:query?',
        name: 'Search',
        component: () => import('@/views/Search.vue'),
    },

    {
        // 正则约束参数
        path: '/article/:id(\\d+)',   // 只匹配数字
        name: 'Article',
        component: () => import('@/views/Article.vue'),
    },

    {
        // 重复参数(匹配多段)
        path: '/files/:path+',        // 匹配 /files/a/b/c
        name: 'Files',
        component: () => import('@/views/Files.vue'),
    },

    // ============================================================
    //                    嵌套路由
    // ============================================================

    /**
     * 【嵌套路由】
     *
     * 使用 children 定义子路由
     * 父组件需要有 <router-view> 来渲染子路由
     */

    {
        path: '/dashboard',
        name: 'Dashboard',
        component: () => import('@/views/Dashboard.vue'),
        meta: {
            title: '控制台',
            requiresAuth: true,
        },
        // 子路由
        children: [
            {
                path: '',              // 默认子路由 /dashboard
                name: 'DashboardHome',
                component: () => import('@/views/DashboardHome.vue'),
            },
            {
                path: 'analytics',     // /dashboard/analytics
                name: 'Analytics',
                component: () => import('@/views/Analytics.vue'),
            },
            {
                path: 'settings',      // /dashboard/settings
                name: 'Settings',
                component: () => import('@/views/Settings.vue'),
            },
        ],
    },

    // 用户相关嵌套路由
    {
        path: '/users/:id',
        component: () => import('@/views/UserLayout.vue'),
        props: true,
        children: [
            {
                path: '',
                name: 'UserOverview',
                component: () => import('@/views/UserOverview.vue'),
            },
            {
                path: 'profile',
                name: 'UserProfile',
                component: UserProfile,
            },
            {
                path: 'settings',
                name: 'UserSettings',
                component: UserSettings,
            },
        ],
    },

    // ============================================================
    //                    命名视图
    // ============================================================

    /**
     * 【命名视图】
     *
     * 同一个路由可以有多个视图
     * 使用 components (注意是复数) 而不是 component
     */

    {
        path: '/layout',
        components: {
            default: () => import('@/views/MainContent.vue'),
            sidebar: () => import('@/views/Sidebar.vue'),
            header: () => import('@/views/Header.vue'),
        },
    },

    // ============================================================
    //                    重定向和别名
    // ============================================================

    /**
     * 【重定向】
     * 访问某路径时自动跳转到另一个路径
     */

    {
        path: '/home',
        redirect: '/',  // 重定向到首页
    },

    {
        path: '/old-user/:id',
        redirect: to => {
            // 动态重定向
            return { name: 'User', params: { id: to.params.id } };
        },
    },

    /**
     * 【别名】
     * 访问别名路径时显示原路径的内容,但 URL 不变
     */

    {
        path: '/settings',
        alias: ['/preferences', '/config'],  // 三个路径都显示同一个组件
        component: () => import('@/views/Settings.vue'),
    },

    // ============================================================
    //                    404 和通配路由
    // ============================================================

    {
        // 匹配所有未匹配的路由
        path: '/:pathMatch(.*)*',
        name: 'NotFound',
        component: NotFound,
        meta: { title: '页面不存在' },
    },
];


// ============================================================
//                    2. 创建路由实例
// ============================================================

const router = createRouter({
    /**
     * 【历史模式】
     *
     * createWebHistory(): HTML5 History 模式
     * - URL 更美观:/user/123
     * - 需要服务器配置支持
     *
     * createWebHashHistory(): Hash 模式
     * - URL 带 #:/#/user/123
     * - 不需要服务器配置
     */
    history: createWebHistory(import.meta.env.BASE_URL),

    // 路由配置
    routes,

    /**
     * 【滚动行为】
     * 控制路由切换时的滚动位置
     */
    scrollBehavior(to, from, savedPosition) {
        // 如果有保存的位置(浏览器前进/后退),恢复位置
        if (savedPosition) {
            return savedPosition;
        }

        // 如果有锚点,滚动到锚点
        if (to.hash) {
            return {
                el: to.hash,
                behavior: 'smooth',
            };
        }

        // 默认滚动到顶部
        return { top: 0, behavior: 'smooth' };
    },

    /**
     * 【严格模式】
     * 是否严格匹配路径末尾的斜杠
     */
    strict: false,

    /**
     * 【链接激活类名】
     */
    linkActiveClass: 'router-link-active',
    linkExactActiveClass: 'router-link-exact-active',
});


// ============================================================
//                    3. 导航守卫
// ============================================================

/**
 * 【全局前置守卫】
 * 在路由跳转前执行
 * 用于权限验证、登录检查等
 */
router.beforeEach((to, from, next) => {
    console.log('导航从', from.path, '到', to.path);

    // 设置页面标题
    document.title = to.meta.title || 'Vue App';

    // 检查是否需要认证
    if (to.meta.requiresAuth) {
        // 检查登录状态(这里简化处理)
        const isAuthenticated = localStorage.getItem('token');

        if (!isAuthenticated) {
            // 未登录,重定向到登录页
            next({
                path: '/login',
                query: { redirect: to.fullPath },  // 保存目标路径
            });
            return;
        }
    }

    // 继续导航
    next();
});

/**
 * 【全局解析守卫】
 * 在所有组件内守卫和异步路由组件被解析之后调用
 */
router.beforeResolve(async (to) => {
    // 可以在这里进行数据预取
    if (to.meta.requiresData) {
        try {
            // await fetchData();
        } catch (error) {
            // 处理错误,可以取消导航
            return false;
        }
    }
});

/**
 * 【全局后置钩子】
 * 在导航完成后执行
 * 不接收 next 函数,不能改变导航
 */
router.afterEach((to, from, failure) => {
    if (failure) {
        console.error('导航失败:', failure);
        return;
    }

    // 发送页面访问统计
    // analytics.trackPageView(to.fullPath);

    // 隐藏加载状态
    // hideLoading();
});

/**
 * 【错误处理】
 */
router.onError((error) => {
    console.error('路由错误:', error);
});


// ============================================================
//                    4. 路由元信息用法
// ============================================================

/**
 * 【路由元信息 meta】
 *
 * 可以在 meta 中存储任意信息:
 * - requiresAuth: 是否需要登录
 * - roles: 允许的角色
 * - title: 页面标题
 * - breadcrumb: 面包屑
 */

// 在守卫中使用 meta
router.beforeEach((to, from, next) => {
    // 检查路由及其所有父路由的 meta
    const requiresAuth = to.matched.some(record => record.meta.requiresAuth);

    // 检查角色权限
    const requiredRoles = to.meta.roles;
    if (requiredRoles) {
        const userRole = 'admin';  // 假设从 store 获取
        if (!requiredRoles.includes(userRole)) {
            next({ name: 'Forbidden' });
            return;
        }
    }

    next();
});


// ============================================================
//                    5. 导出路由实例
// ============================================================

export default router;


// ============================================================
//                    6. 组件中使用路由(示例代码)
// ============================================================

/**
 * 在组件中使用路由
 *
 * ```vue
 * <template>
 *   <!-- 声明式导航 -->
 *   <router-link to="/">首页</router-link>
 *   <router-link :to="{ name: 'User', params: { id: 123 }}">用户</router-link>
 *   <router-link to="/about" active-class="active">关于</router-link>
 *
 *   <!-- 路由出口 -->
 *   <router-view></router-view>
 *
 *   <!-- 带过渡动画的路由 -->
 *   <router-view v-slot="{ Component }">
 *     <transition name="fade" mode="out-in">
 *       <component :is="Component" />
 *     </transition>
 *   </router-view>
 *
 *   <!-- 缓存路由组件 -->
 *   <router-view v-slot="{ Component }">
 *     <keep-alive>
 *       <component :is="Component" />
 *     </keep-alive>
 *   </router-view>
 * </template>
 *
 * <script setup>
 * import { useRouter, useRoute } from 'vue-router';
 *
 * // 路由器实例(用于导航)
 * const router = useRouter();
 *
 * // 当前路由信息
 * const route = useRoute();
 *
 * // 获取路由参数
 * console.log('当前路径:', route.path);
 * console.log('路由参数:', route.params);
 * console.log('查询参数:', route.query);
 * console.log('路由名称:', route.name);
 * console.log('路由元信息:', route.meta);
 *
 * // 编程式导航
 * function goToUser(id) {
 *   // 方式1:字符串路径
 *   router.push(`/user/${id}`);
 *
 *   // 方式2:对象
 *   router.push({ path: `/user/${id}` });
 *
 *   // 方式3:命名路由 + 参数
 *   router.push({ name: 'User', params: { id } });
 *
 *   // 方式4:带查询参数
 *   router.push({ path: '/search', query: { q: 'vue' }});
 * }
 *
 * // 替换当前路由(不会留下历史记录)
 * function replaceRoute() {
 *   router.replace({ name: 'Home' });
 * }
 *
 * // 前进后退
 * function goBack() {
 *   router.back();     // 等同于 router.go(-1)
 * }
 *
 * function goForward() {
 *   router.forward();  // 等同于 router.go(1)
 * }
 * </script>
 * ```
 */

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布