Vue3 | route路由配置、NProgress配置、路由权限控制

394 阅读8分钟

需求

  • 基础路由配置:首页、登录页面、功能页面、404、403

代码

1. 安装Vue Router库

  • Vue Router:
  • 安装指令
    • npm包管理:
      • 默认安装(4版本):npm install vue-router -s
      • 指定版本:npm install vue-router@4 --savenpm i vue-router@3
    • pnpm包管理:pnpm install vue-router@4
    • yarn包管理:yarn add vue-router@4.0.0-beta.6 -S
  • 对应版本
    • vue3对应4版本路由:默认安装命令安装的路由是4版本的,只可在 vue3 及以上使用
    • vue2对应3版本路由:vue2版本需要安装路由的3版本才可以使用

2. 引入router

  • 在项目的入口文件main.js中引入Vue Router,并将其作为Vue应用的插件进行注册。
//目前只引入router
import { createApp } from "vue";
import App from "./App.vue";      //引入资源
import router from "./router";    //引入组件

createApp(App).use(router).mount("#app");
//等效于
const app = createApp(App);       //创建app
app.use(router).mount("#app");    //注入路由+挂载实例

3. 配置路由信息

3-1. 配置路由信息【基础静态路由】

  • 须知:
    • createRouter函数用于创建一个路由实例
    • createWebHistory(base?process.env. BASE_URL)函数用于创建一个浏览器历史记录模式,即history 路由
    • createWebHashHistory(base?process.env. BASE_URL):hash路由
    • routes数组用于配置具体的路由信息,包括路径path、名称name和对应的组件component
    • Layout
    • 在路由配置文件router.jssrc/router/index.js中配置路由信息
  • 代码:
import { createRouter, createWebHistory } from "vue-router"  //history 模式
//import { createRouter, createWebHashHistory } from "vue-router"  //hash 模式
import Home from "./views/Home.vue";
import Layout from "./views/Layout.vue";

const routes = [
    {
        //静态路由
        path: '/', 
        name: 'Home', 
        component: Home
    }, {
        //基于Layout布局
        path: '/Page',
        name: 'Page',
        component: Layout
    }, {
        //基于./views/About.vue
        path: '/Page',
        name: 'Page',
        component: () => import("@/views/About")
    }, {
      path: "/403",
      component: () => import("@/views/403"),
      hidden: true,
    }, {
      path: "/404",
      component: () => import("@/views/404"),
      hidden: true,
    }
]

const router = createRouter({
    history: createWebHistory(),         // history 模式
    //history: createWebHashHistory(),   // hash模式
    routes
})

export default router;  //该路由router已经导出可供使用

3-2. 配置路由信息【叠加各种路由】

  • 路由模式:history模式是hash模式的升级版,主要区别在浏览器链接的显示的不同
    • hash模式:把前端路由路径用 # 号拼接在真实 URL 后面的模式。当 # 后面的路径发生变化时,浏览器不会重新发起请求,而是触发 hashchange 事件。hash 模式链接样式:http://localhost:8080/#/home
    • history模式:history API 是 HTML5 的新特性,允许开发者直接更改前端路由。history 模式链接样式:http://localhost:8080/home
  • 导入路由:
    • createRouter函数用于创建一个路由实例
    • createWebHistory(base?process.env.BASE_URL)函数:
      • 用于创建一个浏览器历史记录模式,即history 路由
      • base参数(可选):默认值是"/",应用的基路径,更改base: process.env.BASE_URL,用BASE_URL替换"/"
    • createWebHashHistory(base?process.env.BASE_URL):hash路由
    • createMemoryHistory(base?process.env.BASE_URL):带缓存history路由
  • 路由:
    • 静态路由:
    • 动态路由:
      • 用于创建带有参数的路由,在路径中使用占位符:xxx,如path: '/user/:id',id为占位符可以是任意字符串
      • 访问参数:通过$route.params来访问路由参数,如用户访问的路由为/user/111222时,$route.params.id111222
    • 嵌套路由:
      • 用于构建复杂的页面结构
      • 子路由的path不需要加/
      • 由于嵌套的子路由的路径是相对于父路由的,因此子路由的路径需要叠加父路由的路径才能访问,如/父/子
    • 路由元信息meta:
      • 通过meta属性可以在路由地址和导航守卫上都被访问到将任意信息附加到路由上
      • meta可以在路由地址和导航守卫上都被访问到,route.meta
  • 滚动行为scrollBehavior
    • 自定义路由切换时页面如何滚动,如切换路由后页面滚到顶部或者保持原先滚动位置
    • 保持某一固定位置:返回一个ScrollToOptions位置对象,如top: 0
    • 固定某一css选择器或DOM元素的偏移位置:el: "xxx", top: xxx,
    • 固定原先的滚动位置:return savedPosition
    • 固定滚动到锚点的行为:el: to.hash, behavior: "smooth"
  • 其他:
    • 路由配置中base参数:base: process.env.BASE_URL
  • 代码:
import { createRouter, createWebHistory } from "vue-router";
import Home from "./views/Home.vue";

const routes = [
    { 
        //静态路由
        path: '/', 
        name: 'Home', 
        component: Home,
        meta: { title: "HOME" },
    }, {
        //动态路由
        path: '/user/:id',
        name: 'User',
        component: User,
        meta: { title: "USER" },
    }, {
        //嵌套路由
        path: '/dashboard',
        component: Dashboard,
        children: [
        {
            path: '',           //路由为`/dashboard`
            component: Overview,
            meta: { title: "DB" },
        }, {
            path: 'profile',    //路由为`/dashboard/profile`
            component: Profile,
            meta: { requiresAuth: true },
        }, {
            path: 'settings',   //路由为`/dashboard/settings`
            component: Settings,
            meta: { requiresAuth: false },
        } ]
    }
]

const scrollBehavior = function (to, from, savedPosition) {
    // 始终滚动到顶部
    return { top: 0 }
    // 固定某一css选择器或DOM元素的偏移位置
    return {
        el: '#main',  //等效于:el: document.getElementById('main'),
        top: 10,  //元素上方偏移10
    }
    // 保持某一固定位置
    return to.meta.position || {x: 0, y: 0}
}

const router = createRouter({
    history: createWebHistory(),
    scrollBehavior,
    routes,
    base: process.env.BASE_URL,
})

export default router;

4. 路由层面权限控制

4-1. 路由层面权限控制【导航守卫+NProgress】

  • 路由守卫:
    • 用于路由切换时执行一些额外的逻辑
    • beforeEach: 全局前置守卫,在路由切换之前调用。
    • beforeResolve: 全局解析守卫,在路由被确认之前调用。
    • afterEach: 全局后置守卫,在路由切换之后调用。
    • beforeEnter: 路由独享守卫,在路由进入之前调用。
    • beforeRouteUpdate: 路由更新守卫,在当前路由复用组件之前调用。
    • beforeRouteLeave: 路由离开守卫,在当前路由离开之前调用。
    • 参数to: 即将要进入的目标
    • 参数from:当前导航正要离开的路由
    • 可选参数nextvue路由守卫中next方法的理解_vue next()-CSDN博客
  • NProgress:
    • 介绍:
    • 安装:
      • npm包管理:npm install nprogress --save
      • yarn包管理:yarn add nprogress
    • 引入NProgress:import NProgress from "nprogress";
    • 引入NProgress样式:import "NProgress/nprogress.less";
    • 配置:NProgress.configure({ ... })
      • 是否显示右上角螺旋加载提示:showSpinner: false
      • 进度条开始时的百分比(可选):minimum: 0.08,默认0.08
      • 设置父容器(可选):parent: '#box1',默认body
    • 使用:
      • NProgress 一般搭配路由守卫使用,如router.beforeEach中开启进度条,router.afterEach中关闭进度条
      • 开启进度条:NProgress.start()
      • 关闭进度条:NProgress.done()
      • 设置进度条的百分比:NProgress.set(n),其中n是0~1之间的数字
      • 以随机量递增进度条,但永远不会达到 100%:NProgress.inc()
    • 修改颜色:在App.vue中的style中增加#nprogress .bar { background: red !important; }
  • 代码:
import router from "./router";
import NProgress from "nprogress";
import "NProgress/nprogress.less";

//配置
NProgress.configure({ showSpinner: false });

router.beforeEach(async(to, from, next) => {
    NProgress.start();
    next();  //继续路由跳转
});

router.afterEach(() => {
    NProgress.done();
});

4-2. 路由层面权限控制【导航守卫】

  • 参考:
  • 路由权限管理:(简单方案)
    • 方案:只在路由跳转时做权限判断,即对需要权限区分的路由在跳转时候添加前置路由守卫router.beforeEach,不满足权限的跳转到相应页面(自定义401、404页面等)
    • 简单代码:
router.beforeEach((to, from, next) => {
    if (store.getters.roles.length === 0) {  // 判断是否保存有权限信息
      const roles = store.getters.roles;
      if (roles !== 'admin') {               // 不为管理员
        if (to.path === '/faultInfo') {
          next({ path: '/401' });
        } else {
          next();
        }        
      } else {
        next();
      }
    } else {
      next('/login');
    }
});
  • 代码:
import router from "./router";
import NProgress from "nprogress";
import "NProgress/nprogress.less";
import { setToken, getToken } from "@util/auth";          //js-cookie封装和调用
import ElMessage
import getToken
//配置
NProgress.configure({ showSpinner: false });

router.beforeEach(async(to, from, next) => {
    NProgress.start();
    const token = getToken();                             //获取token
    if (token) {  //用户有token
        const roles = store.getters.role;                 //从store中获取用户身份
        if (JSON.stringify(roles)) {                      //用户身份存在
            next();
            NProgress.done();
        } else {  //用户身份不存在则获取用户角色
            try {
                await store.dispatch("user/getInfo");     //获取用户角色
                next();
                NProgress.done();
            } catch (error) {
                await store.dispatch("user/resetToken");  //重置Token
                next({ path: "/" });
                NProgress.done();
            }
        }
    } else {                                              //没有Token则获取Token
        if (to.path.includes("/getToken")) {              //如果目标路径包含"/getToken"
            const res = await getToken({ code: to.query.code});
            if(res.code === 20000) {
                const userToken = res.data.token.xxx;
                setToken(userToken);
                if(getToken()) {
                    next({                                //跳转到之前的路径
                        path: (localStorage.getItem("path")) || "/",
                        query: (JSON.parse(localStorage.getItem("query")) || {})
                    });
                } else {
                    ElMessage({
                        message: "打开cookie限制或者更换火狐浏览器!",
                        type: "error"
                    });
                }
        } else if (["/403", "/404", "/login"].includes(to.path) || to.path === "/" ) {  //跳转这几个路径
            next();
            NProgress.done();
        } else { //跳转其他路径
            if(process.env.NODE_ENV === "development") {  //开发环境
                next("/login");
            } else {
                localStorage.setItem("path",to.path);     //保存目标路径
                localStorage.setItem("query",to.path);    //保存查询参数
                const redirectUrl = process.env.VUE_APP_REDIRECT_URL;
                const clientId = process.env.VUE_APP_ID;
                window.location.href =                    //跳转到登录见面
                    "https://..." +
                    clientId +
                    "..." +
                    redirectUrl;
            }
            NProgress.done();
    }
});

router.afterEach(() => {
    NProgress.done();
});

5. 加入<router-view>组件

  • 须知:
    • 在项目的根组件App.vue中加入<router-view>组件,用于展示当前路由对应的组件
    • 当用户点击导航链接时,Vue Router会根据路由配置文件中的配置,加载对应的组件,并将其渲染到<router-view>
  • 代码
<template>
    <div id="app">
        <router-view></router-view>
    </div>
</template>

<script>
export default {
    name: 'App'
}
</script>

6. 页面实现各种路由操作

  • 使用<router-link>组件来实现导航操作,<router-link>组件的to属性用于指定链接的目标路径,
<template>
    <div>
        <router-link to="/">Home</router-link>
        <router-link to="/about">About</router-link>
    </div>
</template>

<script>
export default {
    name: 'Navigation'
}
</script>
  • router.push
function handleLogin() {
    this.$router.push('/indexView');
}

7. 访问路由变量

  • Option API中使用和Vue 2.x中使用没有差别:
    • Vue组件中通过$route.params来访问路由参数:如动态路由参数image.png
    • js中通过this.$router.push
<template>
  <div>{{ $route.params }}</div>
</template>


<script>
  export default {
    data() {
      return {}
    },
    mounted() {
      // 路由跳转 && 设置参数
      this.$router.push({
        path: '/list',
        query: {
          id: 123,
        },
      })
      
      // 获取参数
      let { id } = this.$route.query
    },
  }
</script>
  • Composition API中不能再直接问 this.$router 或 this.$route,所以要使用 useRouter 和 useRoute 函数
<template>
  <div></div>
</template>

<script>
  import { ref } from 'vue'
  import { useRouter, useRoute, onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
  import ajax from "./ajax";

  export default {
    setup () {
      const router = useRouter()
      const route = useRoute()

      // 路由跳转 && 设置参数
      router.push({
        path: '/list',
        query: {
          id: 123,
        },
      })

      // 获取参数
      let { id } = route.query

      // 局部路由守卫
      onBeforeRouteLeave((to, from) => {
        const answer = window.confirm(
          '是否要离开本页面?'
        )
        // 取消导航并停留在同一页面上
        if (!answer) return false
      })

      const userData = ref()

      onBeforeRouteUpdate(async (to, from) => {
        //仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
        if (to.params.id !== from.params.id) {
          userData.value = await ajax(to.params.id)
        }
      })

    },
  }
</script>

8. 监听路由变化

  • 代码:
<script>
export default {
    watch:{
        $route: "getPath",
    },
    created: {
        this.getPath();
    },
    methods: {
        getPath() {
            this.currentId = this.$route.query.proId;
            //this.fetchList();
        }
    }
}
</script>