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';