Skip to content

router路由

安装router

bash
npm install vue-router@4

配置路由

在router.js中的配置路由(包括一些示例)

ts
// 导入路由依赖
import { createRouter, createWebHistory } from 'vue-router';
// 导入进度条进度(可用可不用)
import nprogress from 'nprogress';

// 导入用户路由(可动态添加)
import { userRouterInfo } from './userRouter';

// 配置路由规则
const routes = [
    // 域名重定向
    {
        path: '/',
        redirect: (to: any) => {
            // 方法接收目标路由作为参数
            // return 重定向的字符串路径/路径对象
            return { name: '定向的路由名,如:FileList', query: {} }
        },
    },
    {
        path: '/login',
        name: 'login',
        component: () => import("@/page/login/index.vue"),
        meta: {
            // 页面标题
            title: "立即授权",
            // 是否不需要校验当前账号是否过期 (true表示不需要验证,false表示需要验证)
            isCheckToken: true,
        }
    },
    {
        
        // path: '/:catchAll(.*)*', // 通配符路由匹配所有未匹配到的路径[两个都一样]
        path: '/:pathMatch(.*)*', // 通配符路由匹配所有未匹配到的路径[两个都一样]
        name: '404',
        component: () => import("@/page/404/index.vue"),
        meta: {
            // 页面标题
            title: "页面错误404",
        }
    }
];

// 创建路由
const router = createRouter({
 // 路由的模式 哈希值
    history: createWebHistory(),
    // 路由规则
    routes,
    //滚动行为
    scrollBehavior(to, from, savedPosition) {
        // 如果路径相同,则不需要生效
        if(to.name === from.name) return;

        // 优先处理浏览器前进/后退:恢复历史滚动位置 【为啥要延迟300毫秒呢,因为我路由离开动画设置的是300ms】
        // if (savedPosition) return new Promise((resolve) => setTimeout(() => resolve(savedPosition), 300));

        // 新页面导航:延迟300ms返回顶部(支持锚点定位)
        return new Promise((resolve) => {
            setTimeout(() => {
                // 处理锚点定位(如 #/detail#section-1)
                if (to.hash) {
                    resolve({
                        el: to.hash,
                        behavior: 'smooth',
                        top: 76 // 避开固定导航栏(根据实际高度调整)
                    });
                } else {
                    resolve({ left: 0, top: 0 });
                }
            }, 300); // 【为啥要延迟300毫秒呢,因为我路由离开动画设置的是300ms】
        });
    }
});

// 添加用户路由(动态添加)
userRouterInfo.forEach((item: any) => router.addRoute(item));

// 前置路由守卫
router.beforeEach((to, from, next) => {
    // 开启进度条进度
    nprogress.start();

    // 获取当前账号的token
    let token = localStorage.getItem("token");

    // 判断登录成功后又进入登录页
    if (token && to.name === 'login') return next({ path: "/" });

    // 是否需要校验当前账号是否过期 (true表示不需要验证,false表示需要验证)
    if (!to.meta.isCheckToken) {
        // 验证逻辑...
    }
    // 允许放行
    next();
});

// 后置路由守卫
router.afterEach((to, form) => {
    // 关闭进度条进度
    nprogress.done();

    // 修改页面标题
    document.title = to.meta.title as string;

    // 当前主题是哪一个
    let theme = localStorage.getItem("themeType");
    // 判断当前主题是否存在
    theme = ['light', 'dark'].some((item: string)=> item === theme) ? theme : 'light';

    // 修改主题样式
    document.documentElement.dataset.theme = theme!;

    // 保存当前主题
    localStorage.setItem("themeType", theme!);
});

// 暴露router路由
export const onRouter = (app: { use: Function }) => app.use(router);
// 暴露默认路由
export default router;

动态路由表'./userRouter'的路由规则作为示例展示

ts
// 创建用户路由规则信息
export const userRouterInfo = [
    {
        path: "/FileList",
        name: "FileList",
        component: () => import('@/view/FileList/index.vue'),
        meta: {
            title: "文件",
            // 加入导航栏的配置项
            NavigationOptions:{
                // 标题
                title:'文件列表',
                // 图标
                icon:"icon-sucai",
            },
        },
    },
    {
        path: "/",
        component: () => import('@/page/station/index.vue'),
        children:[
            {
                path: "/tools",
                name: "tools",
                component: () => import(`@/view/tools/index.vue`),
                meta: {
                    title: "Tools-Web-一个轻量的工具",
                    // 加入导航栏的配置项
                    NavigationOptions:{
                        // 标题
                        title:'Tools-web 工具',
                        // 图标
                        icon:"icon-guanligongju",
                    },
                }
            },
            {
                path: "Unit",
                name: "Unit",
                component: () => import(`@/view/tools/tool-page/Unit.vue`),
                meta: {
                    title: "单位转换",
                }
            },
            ......
        ]
    },
    {
        path: "/highQualityWebsite",
        name: "highQualityWebsite",
        component: () => import(`@/view/highQualityWebsite/index.vue`),
        meta: {
            // 标题
            title: '高质量网站分享',
            // 加入导航栏的配置项
            NavigationOptions:{
                // 标题
                title:'站外-优质网站',
                // 图标
                icon:"icon-profileapp",
            },
        }
    },
    {
        path: "/version",
        name: "version",
        component: () => import(`@/view/version/index.vue`),
        meta: {
            // 标题
            title: '系统日志',
            // 加入导航栏的配置项
            NavigationOptions:{
                // 标题
                title:'更新日志',
                // 图标
                icon:"icon-wms-icon-",
            },
        }
    },
    ......
];

路由切换动画

右进左出动画-基本结构

vue
<template>
  <router-view v-slot="{ Component, route }">
    <transition name="animation" mode="out-in" @after-leave="onAfterLeave">
      <!-- 优化处理 -->
      <div class="contain-container" :key="route.path">
        <component :is="Component" />
      </div>
    </transition>
  </router-view>
</template>

<script setup lang="ts">
// 导入动画结束返回顶部方法
import { useRouteScroll } from '@/hook/useRouteScroll';
// 结构方法
const { onAfterLeave } = useRouteScroll();
</script>

<style lang="scss" scoped>
/* 过度动画配置代码 */
// 进入【开始】+离开【结束】
.animation-enter-from,
.animation-leave-to {
	transform: translate3d(25px, 0, 0);
	opacity: 0;
}
// 进入【结束】+离开【开始】
.animation-enter-to,
.animation-leave-from {
  transform: translate3d(0, 0, 0);
	opacity: 1;
}

// 性能优化
.animation-enter-active,
.animation-leave-active {
  /* 提示浏览器该元素将变化,提前优化 */
  will-change: transform, opacity;
  contain: paint;
  
  /* iOS Safari 和 Firefox 的合成器对 will-change 处理异常,降级移除 */
  @supports ((-webkit-touch-callout: none) or (-moz-appearance: none)) {
    will-change: initial;
  }
}

/* 进入效果 */
.animation-enter-active {
  $cubic-enter: cubic-bezier(0.25, 0, 0.25, 1);
  $enter-time: 0.5s;
	transition: transform $enter-time $cubic-enter, opacity $enter-time $cubic-enter;
}

/* 离开效果 */
.animation-leave-active {
  $cubic-leave: cubic-bezier(0.4 ,0 ,0.8 ,1);
  $leave-time: 0.3s;
  // cubic-bezier(1, 0.6, 0.6, 1) // 老动画,起步很慢-中间突然冲出去-结尾又拖沓
  // cubic-bezier(0.25, 0, 0.75, 1) // 快  ───→   稳定   ───→  减速
  // cubic-bezier(0.4 ,0 ,0.8 ,1) // 慢一点  →   加速   →     减速停下
	transition: transform $leave-time $cubic-leave, opacity $leave-time $cubic-leave;
}
</style>

注意事项

左右移动会出现x轴的滚动条,hidden可能会影响其它元素,如:position: sticky;会失效;所以clip不会引发任何问题,完美解决;

scss
body, html {
  // 隐藏x轴滚动条
  overflow-x: clip; //clip: 超出部分直接裁剪;不会创建滚动容器
}

返回顶部优化

ts
// 路由返回顶部【动画结束】
import { nextTick } from 'vue';
import { useRouter } from 'vue-router';

// 全局禁用原生滚动恢复
if ('scrollRestoration' in history) history.scrollRestoration = 'manual';

/** 配置项接口 */
export interface UseRouteScrollOptions {
  /** 固定导航栏高度(px),用于锚点偏移补偿,默认 76 */
  navHeight?: number;
  /** 滚动行为,默认 'smooth' */
  behavior?: ScrollBehavior;
}

/**
 * 路由切换动画结束后的精准滚动
 * @param options - 滚动配置项
 */
export function useRouteScroll(options: UseRouteScrollOptions = {}) {
  const { navHeight = 76, behavior = 'smooth' } = options;
  const router = useRouter();

  /**
   * 供 <transition @after-leave> 绑定的回调
   * 必须在动画完全结束后才会触发,彻底替代 setTimeout
   */
  const onAfterLeave = async (): Promise<void> => {
    const { hash } = router.currentRoute.value;

    if (hash) {
      // 等待新页面 DOM 渲染完毕再定位
      await nextTick();
      const targetEl = document.querySelector(hash);

      if (targetEl) {
        targetEl.scrollIntoView({ behavior, block: 'start' });
        // 补偿固定导航栏遮挡的高度
        window.scrollBy(0, -navHeight);
      }
    } else {
      // 无锚点时默认回到顶部
      window.scrollTo({ top: 0 });
    }
  };

  return { onAfterLeave };
}

为什么要这样做?

因为动画执行期间,前进或后退,浏览器先一步进行到达顶部,这种现象并不友好。

而@after-leave事件可以友好的解决这个问题。

可以禁用原生滚动恢复

ts
// 全局禁用原生滚动恢复
if ('scrollRestoration' in history) history.scrollRestoration = 'manual';

Copyright © 2026 Luke