likes
comments
collection
share

前端路由导航(vue-router)

作者站长头像
站长
· 阅读数 15

前端路由的产生

前端路由是后来发展到SPA(单页应用)时才出现的概念。

SPA 就是一个 web 项目只有一个 HTML 页面,一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转。

优点:前后端的彻底分离,不刷新页面,用户体验较好,页面持久性较好。

缺点:初次加载耗时多,前进后退路由管理,SEO 难度较大。

前端路由实现起来其实不难,本质是监听 URL 的变化,然后匹配路由规则,在不刷新的情况下显示相应的页面。

前端有两种路由模式,理解这两种路由模式,能更好理解

实例:

// router/index.js
const router = new VueRouter({
    // 指定理由模式
    mode: 'history',
    // 路由配置
    routes: [
        {
            
        },
        {
            path: '*',
            redirect: '/404'
        }],
  });

vueRouter

提供了灵活的路由配置和导航功能

功能包括:

  • 路由映射:可以将 url 映射到 Vue组件,实现不同 url 对应不同的页面内容。
  • 嵌套路由映射:可以在路由下定义子路由,实现更复杂的页面结构和嵌套组件的渲染。
  • 动态路由:通过路由参数传递数据。你可以在路由配置中定义带有参数的路由路径,并通过 $route.params 获取传递的参数。
  • 模块化、基于组件的路由配置:路由配置是基于组件的,每个路由都可以指定一个 Vue 组件作为其页面内容,将路由配置拆分为多个模块,在需要的地方引入。
  • 路由参数、查询、通配符:通过路由参数传递数据,实现页面间的数据传递和动态展示。
  • 导航守卫:Vue Router 提供了全局的导航守卫和路由级别的导航守卫,可以在路由跳转前后执行一些操作,如验证用户权限、加载数据等。
  • 展示由 Vue.js 的过渡系统提供的过渡效果:可以为路由组件添加过渡效果,使页面切换更加平滑和有动感。
  • 细致的导航控制:可以通过编程式导航(通过 JavaScript 控制路由跳转)和声明式导航(通过 ****组件实现跳转)实现页面的跳转。
  • 路由模式设置:Vue Router 支持两种路由模式:HTML5 history 模式或 hash 模式。
  • 可定制的滚动行为:当页面切换时,Vue Router 可以自动处理滚动位置。定制滚动行为,例如滚动到页面顶部或指定的元素位置。
  • URL 的正确编码:Vue Router 会自动对 URL 进行正确的编码。

路由组件

router-view:router-view 将显示与 url 对应的组件。

keep-alive 可以设置以下props属性:

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
  • max - 数字。最多可以缓存多少组件实例

keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。

<!-- 不缓存视图,对于编辑或者需要及时跟新数据的操作,可以使用缓存,增加 activated-->
<router-view v-if="!keepAlive"></router-view> 

<!-- 缓存视图 要增加 activated -->
<keep-alive :max="4" :include="['a', 'b']">
   <router-view />
</keep-alive>

$route

$route: 是当前路由信息对象,获取和当前路由有关的信息。 route 为属性是只读的,里面的属性是 immutable (不可变) 的,不过可以通过 watch 监听路由的变化。

基础属性:

fullPath: ""  // 当前路由完整路径,包含查询参数和 hash 的完整路径
hash: "" // 当前路由的 hash 值 (锚点)
matched: [] // 包含当前路由的所有嵌套路径片段的路由记录 
meta: {} // 路由文件中自赋值的meta信息
name: "" // 路由名称
params: {}  // 一个 key/value 对象,包含了动态片段和全匹配片段就是一个空对象。
path: ""  // 字符串,对应当前路由的路径
query: {}  // 一个 key/value 对象,表示 URL 查询参数。跟随在路径后用'?'带的参数

$router

router:是vueRouter实例对象,是一个全局路由对象,通过this.router: 是 vueRouter 实例对象,是一个全局路由对象,通过 this.router:vueRouter实例对象,是一个全局路由对象,通过this.router 访问路由器, 可以获取整个路由文件或使用路由提供的方法。

// 导航守卫
router.beforeEach((to, from, next) => {
  /* 必须调用 `next` */
})
router.beforeResolve((to, from, next) => {
  /* 必须调用 `next` */
})
router.afterEach((to, from) => {})

动态导航到新路由
router.push // 用于跳转到指定的路由
router.replace // 用于替换当前的路由
router.go(n) // 在路由历史记录中前进或后退n个步骤。参数`n`可以是一个正数或负数,正数表示前进,负数表示后退。
router.back() // 后退一个步骤,等同于`router.go(-1)`
router.forward() // 前进一个步骤,等同于`router.go(1)`


需求实例:

需求描述:在tab栏切换时,不同tab页面中,表单的列表示不同信息

实现

1、点击tab栏切换,访问指定路由

<template>
    <el-tabs 
      v-model.trim="activeTab"
      @tab-click="changeActiveTab">
      <el-tab-pane
        v-for="item in tabList"
        :key="item.name"
        :label="item.label"
        :name="item.name">
      </el-tab-pane>
    </el-tabs>
</template>
<script>
  methods:{
    activatedOrCreated() {
      const tabs = [
        {label: '语音', name: 'audio', sessionType: 2, type: 'audio'},
        {label: '文本', name: 'text', sessionType: 1, type: 'text'},
        {label: '富文本', name: 'workorder', sessionType: 3, type: 'workorder'}
      ];
      // 将对象中的 `type` 属性值,过滤出来
      const { activeTab, tabList } = this.$helper.filterType(tabs);
      // 暂存在data中,如果是第一次进入,从路由中获取,进入后操作获取操作中`type`值
      this.activeTab = this.$route.query.type || activeTab;
      this.tabList = tabList;
    },
    changeActiveTab() {
      if (this.activeTab === 'text') {
        // 用于跳转到指定的路由
        this.$router.push({path: '/smart-check?type=text&active=taskTab'});
      } else if (this.activeTab === 'audio') {
        this.$router.push({path: '/smart-check?type=audio&active=taskTab'});
      } else {
        this.$router.push({path: '/smart-check?type=workorder&active=taskTab'});
      }
    }
  }
};
</script>

2、表格中

当一个路由被匹配时,它的 内容传递参数 将在每个组件中以 this.$route.query 的形式暴露出来

</template>
    <el-table>
     // 当tab栏切换到 语音 时,此列才显示 
         <el-table-column 
                v-if="orderScoreLoding === 'audio' "
                min-width="100"
                label="订单得分">
                <template slot-scope="scope">
                  {{ scope.row.orderScore }}
                </template>
         </el-table-column>
    <el-table>
</template>

export default {
    data(){
        return {orderScoreLoding: ''}
    },
    mounted() {
        // 获取到当前路由查询参数中的'type'值
        this.orderScoreLoding = this.$route.query.type;
    }
}

routes 懒加载(动态加载路由)

把不同路由对应的组件分割成不同的代码块,当路由被访问时才去加载对应的组件 即为路由的懒加载,可以加快项目的加载速度,提高效率

const router = new VueRouter({
  routes: [
    {
      path: '/home',
      name: 'Home'
      component:() = import('../views/home')
		}
  ]
})

动态路由

  • 动态路由的创建,主要是使用 path 属性过程中,使用动态路径参数,路径参数 用冒号 : 表示。
const routes = [
  {
    path: '/user/:id'
    name: 'User'
    components: User
	}
]

vue-router 通过配置  params传参 和  query传参 来实现动态路由

params 传参

  • 必须使用 命名路由 name 传值
  • 参数不会显示在 url 上
  • 浏览器强制刷新时传参会被清空
// 传递参数
this.$router.push({
  name: Home
  params: {
    number: 1 ,
    code: '999'
  }
})
// 接收参数
const p = this.$route.params

query 传参

  • 可以用 name 也可以使用 path 传参
  • 传递的参数会显示在 url 上
  • 页面刷新是传参不会丢失
// 方式一:路由拼接
this.$router.push('/home?username=xixi&age=18')

// 方式二:name + query 传参
this.$router.push({
  name: Home
  query: {
    username: 'xixi',
    age: 18
	}
})

// 方式三:path + name 传参
this.$router.push({
  path: '/home'
  query: {
    username: 'xixi',
    age: 18
	}
})

// 接收参数
const q = this.$route.query

路由守卫

全局路由守卫

路由守卫主要用来通过跳转或取消跳转的方式守卫导航

全局前置路由守卫(beforeEach(to,from, next))

router.beforeEach(async (to, from, next) => {
  // 清除面包屑导航数据
  store.commit('common/SET_BREAD_NAV', [])
  // 是否白名单
  if (isWhiteList(to)) {
    next()
  } else {
    // 未登录,先登录
    try {
      if (!store.state.user.userInfo) {
        // 通过 vuex 中的方法获取用户信息
        await store.dispatch('user/getUserInfo')
        // 登录后判断,是否有角色, 无角色 到平台默认页
        if (!store.state.user.userInfo.permissions || !store.state.user.userInfo.permissions.length) {
          next({ path: '/noPermission' })
        }
      }

      // 登录后判断,是否有访问页面的权限
      if (!hasVisitPermission(to, store.state.user.userInfo)) {
        next({ path: '/404' })
      } else {
        next()
      }
    } catch (err) {
      $error(err)
    }
  }
})

在全局前置路由导航中清除面包屑导航数据的目的:

为了确保每次路由切换时都能重新生成正确的面包屑导航路径。面包屑导航通常是根据用户的导航行为和路由路径动态生成的。当用户从一个页面导航到另一个页面时,路由路径会发生变化,面包屑导航也需要相应地更新。如果不清除面包屑导航数据,那么在每次路由切换时,旧的面包屑导航数据可能会被保留下来,导致显示的面包屑导航路径不正确。

全局解析路由守卫(beforeResolve(to,from, next))

当发生路由切换时,会触发beforeResolve导航守卫

触发时机:在所有组件内守卫和异步路由组件被解析之后,导航确定之前,解析守卫就被正确调用。

即在 beforeEach 和 组件内 beforeRouteEnter 之后,afterEach 之前调用

router.beforeResolve 是获取数据或执行任何其他操作的理想位置

router.beforeResolve(async to => {
  // 如果目标路由配置的`meta`字段包含`requiresCamera`属性,表示该路由需要使用相机权限
  if (to.meta.requiresCamera) {
    try {
      // 调用`askForCameraPermission()`方法来请求相机权限
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误,然后取消导航
        return false
      } else {
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})

全局后置路由守卫(afterEach(to,from))

在路由跳转完成后触发

router.afterEach((to, from) => {
	// 在路由完成跳转后执行,实现分析、更改页面标题、声明页面等辅助功能
	sendToAnalytics(to.fullPath)
})

独享路由守卫(beforeEnter(to,from, next))

独享路由守卫可以直接在路由配置上定义,但是它只在进入路由时触发,不会在 params、query 或 hash 改变时触发

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    // 在路由配置中定义守卫
    beforeEnter: (to, from,next) => {
      next()
    },
  },
]

路由守卫的重用

function removeQueryParams(to) {
  if (Object.keys(to.query).length)
    return { path: to.path, query: {}, hash: to.hash }
}

function removeHash(to) {
  if (to.hash) return { path: to.path, query: to.query, hash: '' }
}

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: [removeQueryParams, removeHash],
  },
  {
    path: '/about',
    component: UserDetails,
    beforeEnter: [removeQueryParams],
  },
]

组件路由守卫

在组件内使用的钩子函数,类似于组件的生命周期

  • beforeRouteEnter(to,from, next) -- 进入前
  • beforeRouteUpdate(to,from, next) -- 路由变化时
  • beforeRouteLeave(to,from, next) -- 离开后
export default{
  data(){
    ...
  },
  
  // 在渲染该组件的对应路由被验证前调用
  beforeRouteEnter (to, from, next) {
    // 此时 不能获取组件实例 this
    // 因为当守卫执行前,组件实例还没被创建
    next((vm)=>{
      // next 回调 在 组件 beforeMount 之后执行 此时组件实例已创建,
      // 可以通过 vm 访问组件实例
      console.log('组件中的路由守卫==>> beforeRouteEnter 中next 回调 vm', vm)
    )
  },

  // 可用于检测路由的变化
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用  此时组件已挂载完可以访问组件实例 `this`
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    console.log('组件中的路由守卫==>> beforeRouteUpdate')
    next()
  },

  // 在导航离开渲染该组件的对应路由时调用
  beforeRouteLeave (to, from, next) {
    // 可以访问组件实例 `this`
    console.log('组件中的路由守卫==>> beforeRouteLeave')
    next()
  }
}

总结性案例:菜单导航

import smartCheckRouteFactory from './smart-check';
import appealRouteFactory from './appeal';
import settingRouteFactory from './settings';
import exceptionRouteFactory from './exception';

// router/index.js
const router = new VueRouter({
    mode: 'history',
    routes: [
      {
        path: '/',
        beforeEnter: async (to, from, next) => {
          const homepageMap = { // 如果,同时出现拥有两个首页权限的情况,将根据使用优先级高的
            [Power.SMART.SMART_CHECK]: { url: '/smart-check', priority: 1 },
            [Power.REVIEW_CHECK.REVIEW_PLAN]: { url: '/review/plan', priority: 2 },
            [Power.CASE_MANAGE]: { url: '/case-manage', priority: 3 },
            [Power.APPEL.ISSUE_LIST]: { url: '/issue-list', priority: 4 },
            [Power.APPEL.APPEL_LIST]: { url: '/appeal-list', priority: 5 }
          };
          const homepages = store.state.userPowers
            .filter((power) => {
              return homepageMap[power.code]; // 找出,有权限的首页
            })
            .map((power) => { return homepageMap[power.code]; })
            .sort((a, b) => { return a.priority - b.priority; });// 排序
          // 此用户权限最高的页面
          const firstHomepage = homepages[0] || {};
          // 是否有权限,没有就跳转到默认页面(这个默认页面为权限最高的页面)
          const firstPower = store.state.userPowers[0] || { url: '/smart-check/text' };
          const nextPath = firstHomepage.url || firstPower.url || '403';
          next(nextPath); // 跳转到业务路径
        },
        meta: {}
      },
      ...smartCheckRouteFactory(Power),
      ...appealRouteFactory(Power),
      ...settingRouteFactory(Power),
      // 默认菜单页面,不需要访问权限
      ...exceptionRouteFactory(),
      {
        path: '*',
        redirect: '/404'
      }
    ]
  });
// 具体页面
const SmartCheck = () => import('../page/smart-check/index');
const SmartCheckList = () => import('../page/smart-check/smart-check-list');

// router/smart-check.js
export default (Power) => {
  const checkRouteGroup = [
    {
      name: 'smart-check',
      path: '/smart-check',
      component: SmartCheck,
      meta: {
        // 页面权限
        power: Power.SMART.SMART_CHECK,
        // 页面标题
        title: '智能检测',
      }
    },
    {
      name: 'smart-check-list',
      path: '/smart-check/smart-check-list',
      component: SmartCheckList,
      meta: {
        power: Power.SMART.SMART_CHECK,
        title: '检测数据',
        // 面包屑导航
        breadcrumb: [
          { name: 'smart-check' }
        ]
      }
    },
  ]
}
转载自:https://juejin.cn/post/7251857574526140474
评论
请登录