整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

vue爬坑记之动态菜单&动态路由

vue爬坑记之动态菜单&动态路由

耘开源,致力于基础平台开发,点击上方“关注”,每天为你分享开发技巧和开源干货

其实vue也用了好几年了,都是从脚手架开始入手的,可能随着年纪,学习的欲望越来越少了,一直想看看vue-router是怎么运作的,但是一直都没有下手.

最近打算自己搞一个基础开发平台,需要自己下手了,这个地方总归是要懂的,花了几天时间,从迷糊到清晰,慢慢的了解了这块的运作方式,把这块原理试着阐释一下:

在我看来本质就是一个前端的拦截器,然后内部跳转,这也是单页应用的核心,不刷新可以完美地搞定所有事情,但是又和以前的ajax有所区别,可以更好的工程化的解决问题,用来满足现在越来越重的前端需求

2.vue的组件,这个算是一个成功的,不过以前我写过flex,可能现在大家不熟悉,那个时候是用actionScript和xml混合协作的是不是很熟悉,没错我感觉现在的这几个vue和angular都参考了那个,而且flash gameover之后那几个大神好像都去了google,没多久angular就面世了,一样的动态绑定,一样的动态刷新,但是组件算是前进了一大步.他脱胎于class但是优于class,让我们可以单个组件封装了一个完整的html和js以及css的功能

3.路由虽然官方的demo是

<router-link to="/foo">Go to Foo</router-link>

但是我感觉开发应该很少这样用,毕竟我们都是代码小能手,这种固定的东西如何我们天天改的需求,所以常用的还是

router.push({ path: 'register', query: { plan: 'private' }})// 我们可以自由的传参和修改地址

但是我们路由总要一个地方管理,这里我们的主角就要出现了VueRouter,我们可以定义好我们的全局路由管理用了吧我们整个项目的路由统一管理

const router=new VueRouter({
  routes: [
    {
      path: '/user/:userId',
      name: 'user',
      component: User
    }
  ]
})

但是我们的我们没办法把路由都写死在前端,当然我们也可以这样做,但是开始简单,后期维护就负责,而且各自权限管理,也把我们搞得昏头暗地的,我们自然而然地想到了,后台系统把路由组装好,然后再给前端,前端根据后台显示的路由加载界面,这样既可以节省性能,又可以完整的控制权限.

动态加载路由,需要使用官方的api,直接调用函数就可以了,一开始没找到,一直在自己添加原始的路由,结果路由一直匹配不到

router.addRoute
addRoute(route: RouteConfig): ()=> void

后台组装路由,包括基础的对象,主要是path和name,meta用来添加备用信息

@Data
public class IViewMenu {

    /**
     * 数据ID
     */
    private String          id;
    /**
     * 前端路径
     */
    private String        path;
    /**
     * 名称
     */
    private String        name;
    private String        redirect;
    private String        component;
    private IViewMenuMeta meta;

    private List<IViewMenu> children;

    @Data
    static private class IViewMenuMeta {
        /**
         * 访问权限
         */
        private String  access;
        /**
         * 图标
         */
        private String  icon;
        /**
         * 分组标题
         */
        private String  title;
        /**
         * 是否缓存
         */
        private boolean notCache=false;
        /**
         * 关闭tab回调函数
         */
        private String  beforeCloseName;

    }
}

4.import,组件的动态加载

我们每一个界面都是一个单独的组件,但是我们现在希望路由规则是后台传过来的,那我们的组件也是根据后台的规则动态加载的,import是我们的常用规则的

// 常用的组件就是这样加载的,其他都是正常属性,component需要我们到前端来加载
{
    path: '/login',
    name: 'login',
    meta: {
      title: 'Login - 登录',
      hideInMenu: true
    },
    component: ()=> import('@/view/base/login/login.vue')
  }
// 因为eslint 是不允许import在函数使用的,我们就要单独把这个建一个别名
export const _import=file=> ()=> import('@/view/' + file + '.vue')
然后在.eslintignore这个文件里面把这个文件给忽略,不然编译会一直gg

整体流程是就

登录--->加载菜单-->加载组件-->显示主页 核心代码如下:

  • 核心功能是beforeEach,全局的拦截器,可以帮助我们获取到用户跳转之前的所有请求
  • 用户to过来大部分都是自动匹配过来的,匹配可以规矩path也可以根据name匹配
  • 匹配可以包括多个,上下级都有,整体框架的加载
import Vue from 'vue'
import Router from 'vue-router'
import {routers as routes, _import} from './routers'
import store from '@/store'
import iView from 'view-design'
import { getToken, canTurnTo } from '@/libs/util'
import { isURL } from '@/libs/validate'
import { getRouters } from '@/api/user'
import config from '@/config'
import Main from '@/components/main'
const { homeName }=config

Vue.use(Router)
const router=new Router({
  routes,
  isAddRouters: false,
  mode: 'history'
})
const LOGIN_PAGE_NAME='login'

const turnTo=(to, access, next)=> {
  if (canTurnTo(to.name, access, routes)) next() // 有权限,可访问
  else next({ replace: true, name: 'error_401' }) // 无权限,重定向到401页面
}

const addRouters=(data)=> {
  if (!router.isAddRouters) {
    data.forEach(d=> {
      loadComponent([d])
      routes.push(d)
      router.addRoutes([d]) // 动态添加路由
      router.isAddRouters=true
    })
  }
}

const loadComponent=(data)=> {
  data.forEach(route=> {
    if (route.menu==='3') {
      route['component']=Main
    } else if (isURL(route.path)) {
      route.meta.iframeUrl=route.href
      route['meta']['type']='iframe'
    } else if (route.target==='iframe') {
      route['meta']['iframeUrl']=`${process.env.VUE_APP_SERVER_URL}${route.href}`
    } else {
      try {
        route['component']=_import(route.path) || null
      } catch (e) {
        console.log(e)
      }
    }
    if (route.children && route.children.length > 0) {
      loadComponent(route.children)
    }
  })
}

const gotoAssess=(to, store, next)=> {
  if (store.state.user.hasGetInfo) {
    turnTo(to, store.state.user.access, next)
  } else {
    store.dispatch('getUserInfo', store.state.user.userId).then(user=> {
      // 拉取用户信息,通过用户权限和跳转的页面的name来判断是否有权
      //限访问;access必须是一个数组,如:['super_admin'] ['super_admin', 'admin']
      turnTo(to, user.access, next)
    }).catch(()=> {
      next({
        name: LOGIN_PAGE_NAME
      })
    })
  }
}
/**
* 核心功能是beforeEach,全局的拦截器,可以帮助我们获取到用户跳转之前的所有请求
* 用户to过来大部分都是自动匹配过来的,匹配可以规矩path也可以根据name匹配
*匹配可以包括多个,上下级都有
*/
router.beforeEach((to, from, next)=> {
  iView.LoadingBar.start()
  const token=getToken()
  if (!token && to.name !==LOGIN_PAGE_NAME) {
    // 未登录且要跳转的页面不是登录页
    next({
      name: LOGIN_PAGE_NAME // 跳转到登录页
    })
  } else if (!token && to.name===LOGIN_PAGE_NAME) {
    // 未登陆且要跳转的页面是登录页
    next() // 跳转
  } else if (token && to.name===LOGIN_PAGE_NAME) {
    // 已登录且要跳转的页面是登录页
    next({
      name: homeName // 跳转到homeName页
    })
  } else {
    if (router.isAddRouters) {
      gotoAssess(to, store, next)
    } else {
      // 没获取用户信息,说明没有获取路由菜单
      getRouters(store.state.user.userId).then(routerArr=> {
        addRouters(routerArr)
        gotoAssess(to, store, next)
      })
    }
  }
})

router.afterEach(to=> {
  iView.LoadingBar.finish()
  window.scrollTo(0, 0)
})

export default router

描述的可能不是很全,自己遇到的几个问题都再重点突出以下

  • router定义的时候routes是固定的
  • beforeEach 匹配path和name都可以匹配
  • mate元信息可以自己随便搞
  • import 不能随便用

期待你的关注,这里是悟耘开源,开源项目包括EasyPoi(快速完成Office操作工具包,开源中国GVP项目),风铃(基于springboot的快速开发平台),代码生成器等开源项目,悟耘开源致力于基础平台开发,希望帮助大家提高开发速度,提升开发质量,让大家远离996。关注我,获取分享开源路上的感悟和开发相关技术分享。

页面应用程序

  • SPA : Single Page Application 单页面应用程序
  • MPA : Multiple Page Application 多页面应用程序
  • 单页 web 应用就是只有一个 web 页面的应用,
    是加载单个 HTML 页面,
    并在用户与应用程序交互时, 动态更新该页面的 web 应用程序
  • 区别对于传统的多页面应用程序来说, 每次请求服务器返回的都是一个完整的页面对于单页应用程序来说,只有第一次会加载页面,
    以后的每次请求,
    仅仅是获取必要的数据.然后,
    由页面中 js 解析获取的数据,
    展示在页面中
  • 单页面优势 :减少了请求体积,加快页面响应速度,降低了对服务器的压力更好的用户体验,让用户在 web app 感受 native app 的流畅, (局部刷新)
  • 单页面劣势 :开发成本高 (需要学习路由)不利于 SEO
  • 演示 : https://music.163.com/

介绍路由

  • 路由 : 是浏览器 URL 中的哈希值( # hash) 与 展示视图内容 之间的对应规则简单来说,路由就是一套映射规则(一对一的对应规则), 由开发人员制定规则.-当 URL 中的哈希值( # hash) 发生改变后,路由会根据制定好的规则, 展示对应的视图内容
  • 为什么要学习路由?在 web App 中, 经常会出现通过一个页面来展示和管理整个应用的功能.SPA 往往是功能复杂的应用,为了有效管理所有视图内容,前端路由 应运而生.
  • vue 中的路由 : 是 hashcomponent 的对应关系, 一个哈希值对应一个组件

一 : 路由的基本使用

准备工作 (3个)

  • 安装 : npm i vue-router
  • 引入 :
<script src="./vue.js"></script>
// 千万注意 :引入路由一定要在引入vue之后,因为vue-router是基于vue工作的
<script src="./node_modules/vue-router/dist/vue-router.js"></script>
  • 实例路由对象 + 挂载到vue上实例路由对象 : const router=new VueRouter()挂载到vue上 : new Vue({ router,data,methods })验证路由是否挂载成功, 就看打开页面,最后面有没有个 #/

具体步骤 (4个)

  • 1.入口
  • 2.路由规则
  • 3.组件
  • 4.出口
# 1. 入口
     // 方式1 : url地址为入口   调试开发用
     输入url地址 改变哈希值 `01-路由的基本使用.html#/one`    
     // 方式2 : 声明式导航 : router-link+to (见下面介绍)
# 2. 路由规则
// path : 路由路径
// component : 将来要展示的路由组件
routes: [
    { path: '/one', component: One }, 
    { path: '/two', component: Two }
]
# 3. 组件
// 使用返回值的这个组件名称
const One=Vue.component('one', {
  template: ` <div> 子组件 one </div> `
})
# 4. 出口
<!--  出口 组件要展示的地方-->
<router-view></router-view>

# 总结
拿到入口哈希路径, 根据路由匹配规则,找到对应的组件,显示到对应的出口位置 

二 : 由使用注意事项

  • 入口最常用的入口 是 声明式导航 router-link
<!-- 
router-link 组件最终渲染为 a标签, to属性转化为 a标签的href属性
to 属性的值 , 实际上就是哈希值,将来要参与路由规则中进行与组件匹配
  -->
<router-link to="/one">首页</router-link>
  • 组件
const One={
  template: `<div> 子组件 one </div> `
}
  • 演示 : 多个组件匹配
<div id="app">
  <!-- 1 路由入口:链接导航 -->
  <router-link to="/one">One</router-link>
  <router-link to="/two">Two</router-link>

  <!-- 4 路由出口:用来展示匹配路由视图内容 -->
  <router-view></router-view>
</div>

<!--  导入 vue.js -->
<script src="./vue.js"></script>
<!--  导入 路由文件 -->
<script src="./node_modules/vue-router/dist/vue-router.js"></script>
<script>
  // 3 创建两个组件
  const One={
    template: '<h1>这是 one 组件</h1>'
  }
  const Two={
    template: '<h1>这是 two 组件</h1>'
  }

  // 0 创建路由对象
  const router=new VueRouter({
    // 2. 路由规则
    routes: [
      { path: '/one', component: One },
      { path: '/two', component: Two }
    ]
  })

  const vm=new Vue({
    el: '#app',
    //0. 不要忘记,将路由与vue实例关联到一起!
    router
  })
</script>

三 : 入口导航菜单高亮处理

  • 点击导航=> 元素里添加了两个类
<a href="#/one" class="router-link-exact-active router-link-active">One</a>
<a href="#/two" class="">Two</a>
  • 修改方式1 : 直接修改类的样式
.router-link-exact-active,
.router-link-active {
  color: red;
  font-size: 50px;
}
  • 修改方式2 : 使用存在过的类样式=> 修改默认高亮类名
const router=new VueRouter({
  routes: [],
  // 修改默认高亮的a标签的类名
  // red 是已经存在过的
  linkActiveClass: 'red'
})

四: 路由配置

4.1 动态路由=> 详情列表

导入 : 列表三个手机都要点击进去详情页, 只需要一个组件,显示不同的数据即可

# 入口
<router-link to="/detail/1">手机1</router-link>
<router-link to="/detail/2">手机2</router-link>
<router-link to="/detail/3">手机3</router-link>

<router-link to="/detail">手机4</router-link>  没有参数如何????

# 规则
routes: [
  // 2 . 路由规则
  { path: '/detail/:id?', component: Detail }
]

# 获取参数的三种方式
const Detail={
    template: `
        // 方式1 : 组件中直接读取
        <div> 显示详情页内容....{{ $route.params.id  }} </div>
    `,
    created() {
        // 方式2 : js直接读取
        // 打印只会打印一次,因为组件是复用的,每次进来钩子函数只会执行一次
        console.log(this.$route.params.id)
    },
    // 方式3 : 监听路由的参数,为什么不需要深度监听,因为一个路径变化,就会对应一个对新的路由对象(地址变)
    watch: {
        $route(to, from) {
            console.log(to.params.id)
        }
    }
}

4.2 路由对象 - $route

  • 一个路由对象 (route object) 表示当前激活的路由的状态信息,包含了当前 URL 解析得到的信息
  • 一个哈希值路径==> 一个路由对象
  • $route.path类型: string字符串,对应当前路由的路径,总是解析为绝对路径,如 "/foo/bar"。# 后面?前面的内容
  • $route.params类型: Object一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。
  • $route.query类型: Object参数对象一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 $route.query.user==1,如果没有查询参数,则是个空对象。
  • $route.hash类型: string当前路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。
  • $route.fullPath类型: string全路径完成解析后的 URL,包含查询参数和 hash 的完整路径。
# 演示 : 
<router-link to="/detail/4?age=21#one">detail</router-link>
{ path: '/detail/:id?', component: detail } 
在组件内 created打印 this.$route
> fullPath: "/detail/4?id=001#one"
> hash : "#one"
> params : {id:'4'}
> query : {age : 21}
> path : '/detail/4'

4.3 嵌套路由=> children

导入 : url测试 parent 和child, 想让child 在 parent 中显示

  • parent 的内部 添加 : <router-view> </router-view>
  • 规则里添加 children
  • /child 和 child 的区别如果是/child=> 那么访问就可以直接访问#/child就可以访问 子组件如果是child=> 那么访问就应该访问#/parent/child才可以访问子组件
const parent={
    template: `<p>parent  <router-view> </router-view> </p>`
}
const child={
    template: `<p>child</p>`
}

const router=new VueRouter({
    routes: [
        {
            path: '/parent',
            component: parent,
            children: [
                { path: '/child', component: child }
            ]
        }
    ]
})

4.4 命名路由

  • 有时候,通过一个名称来标识一个路由显得更方便一些,
  • 特别是在链接一个路由,或者是执行一些跳转的时候。===> 场景
  • 你可以在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称。==> 如何命名
# 命名
routes: [
    {
        path: '/parent',
        name: 'parent',
        component: parent
    }
]

# 入口链接 + 跳转  (使用 path 和 name 的转换)
<!-- 方式1 : url手动写 -->

<!-- 方式2 : 入口链接 声明式导航 -->
<router-link to="/parent">点击</router-link>
<router-link :to="{ name : 'parent' }">点击</router-link>  # 忘了 带 : 原始对象类型

<!-- 方式3 : 编程式导航 -->
 fn() {
     // this.$router.push('/parent')
     this.$router.push({
         name: 'parent'
     })
 }

4.5 命名视图

导入 : 有时候想同时 (同级) 展示多个视图,

需求 : 访问 / 根目录 同时展示以下三个组件

  • 三个组件
const header={
    template: `<p>header  </p>`
}
const main={
    template: `<p>main  </p>`
}
const footer={
    template: `<p>footer  </p>`
}
  • 规则
# 以前的那个方式只能显示三个 header
# 演示之前的效果 

routes: [
    {
        path: '/',
        components: {
            default: header,
            m: main,
            f: footer
        }
    }
]
  • 出口
<router-view> </router-view>
<router-view name="m"> </router-view>
<router-view name="f"> </router-view>

4.6 重定向

redirect: '/header'
redirect: { name: 'header' }
redirect: to=> {
      // console.log(to)
    return {
        name: 'about'
    }
}

4.7 组件传参

  • 原始方式使用 $route获取
# 入口
    <router-link to="/header/3">123</router-link>
# 规则
routes: [
    {
        path: '/header/:id',
        component: header,
    }
]
# 获取参数
const header={
    template: `<p>header  {{ $route.params.id }}  </p>`
}
  • 布尔模式
# 入口
    <router-link to="/header/3">123</router-link>

# 规则
routes: [
    {
        path: '/header/:id',
        component: header,
        // 如果 props 被设置为 true,route.params 将会被设置为组件属性
        props: true
    }
]

# 获取参数
const header={
    // 参数 id 当成参数
    props: ['id'],
    template: `<p>header   {{ id }} </p>`
}
  • 对象模式
# 入口
 <router-link to="/header">123</router-link>

# 规则
 routes: [
     {
         path: '/header',
         component: header,
         props: { foo: '0000' }
     }
 ]
# 组件
 const header={
        props: ['foo'],
        template: `<p>header   {{ foo }} </p>`
 }
  • 函数模式
# 同对象模式一样
# 区别是props值不一样
 props: to=> {
     return { foo: '0000' }
 }
  • 注意 : 对象模式和函数模式参数 在props里,所以声明式导航那里就不要传参了

4.8嵌套路由

实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件。

借助 vue-router,使用嵌套路由配置,就可以很简单地表达这种关系。

  • 单层路由
<div id="app">
  <router-view></router-view>
</div>
const User={
  template: '<div>User {{ $route.params.id }}</div>'
}

const router=new VueRouter({
  routes: [
    { path: '/user/:id', component: User }
  ]
})

这里的 <router-view> 是最顶层的出口,渲染最高级路由匹配到的组件。同样地,一个被渲染组件同样可以包含自己的嵌套 <router-view>。例如,在 User 组件的模板添加一个 <router-view>

  • 嵌套路由模板配置const User={ template: ` <div class="user"> <h2>User {{ $route.params.id }}</h2> <router-view></router-view> </div> ` }
  • 路由配置:要在嵌套的出口中渲染组件,需要在 VueRouter 的参数中使用 children 配置
const router=new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [
        {
          // 当 /user/:id/profile 匹配成功,
          // UserProfile 会被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          // 当 /user/:id/posts 匹配成功
          // UserPosts 会被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

注意,以 / 开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径。

五 : 路由进阶

5.1 元信息

  • 规则声明
 routes: [
     {
         path: '/header',
         component: header,
         meta: {
            title: 'XXXX'
         }
     }
 ]
  • 获取
 created() {
    document.title=this.$route.meta.title
 }
  • 作用 :
  • 在路由导航的时候,可以用作判断

5.2 编程式导航

const one={
    template: ` 
<div> <button @click="handleClick('back')">返回 上一页</button>
<button @click="handleClick('push')">跳转 two页</button>
<button @click="handleClick('replace')">替换 two页</button> 
</div>`,
    methods: {
        handleClick(type) {
            if (type=='back') {
                // 返回
                this.$router.back()
            } else if (type=='push') {
                // 跳转 有历史记录
                this.$router.push('/two')
            } else {
                // 替换 没有历史记录
                this.$router.replace('/two')
            }
        }
    }
}
const two={
    template: `<p>two </p>`
}

5.3 导航守卫

Vue 2 中配置路由是一个重要的步骤,它可以让你的单页应用 (SPA) 在不同的页面之间进行跳转而无需重新加载整个页面。

以下是 Vue 2 中配置路由的基本步骤:

安装 Vue Router

首先,你需要安装 Vue Router 包。可以使用以下命令通过 npm 或 yarn 进行安装:

Bash

npm install vue-router

或者

Bash

yarn add vue-router

创建路由配置

创建一个路由配置对象,该对象包含要定义的路由规则。路由规则是将 URL 与要显示的组件进行映射的规则。

JavaScript

import Vue from 'vue';

import VueRouter from 'vue-router';

Vue.use(VueRouter);

const routes=[

{ path: '/', component: Home },

{ path: '/about', component: About },

{ path: '/contact', component: Contact },

];

const router=new VueRouter({

routes,

});

export default router;

在这个例子中,我们定义了三个路由规则:

/:将映射到 Home 组件

/about:将映射到 About 组件

/contact:将映射到 Contact 组件

在根实例中注册路由

在你的 Vue 根实例中,你需要将路由实例注册到 Vue 实例上。这将使 Vue 能够感知路由并相应地更新视图。

JavaScript

import Vue from 'vue';

import App from './App.vue';

import router from './router';

new Vue({

router,

render: function (createElement) {

return createElement(App);

},

}).$mount('#app');

使用路由链接

要从一个路由导航到另一个路由,可以使用 router-link 组件。router-link 组件就像一个普通的 HTML 链接,但它会触发路由导航而不是重新加载页面。

HTML

Home

About

Contact

使用路由参数

可以使用路由参数来传递数据给路由组件。路由参数在 URL 中定义,可以使用 :param 语法。

JavaScript

const routes=[

{ path: '/user/:id', component: User },

];

在 User 组件中,可以使用 $route.params 对象访问路由参数。

JavaScript

export default {

template: `

User ID: {{ $route.params.id }}

`,

};

使用路由守卫

路由守卫可以用来在路由导航发生之前或之后执行代码。这可以用于诸如身份验证、授权和数据预取等任务。

JavaScript

const router=new VueRouter({

routes,

beforeResolve: (to, from, next)=> {

// 在导航发生之前执行代码

},

});

这些只是 Vue 2 中配置路由的基础知识。有关更高级的主题,请参阅 Vue Router 文档: