前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浅学前端:Vue篇(三)

浅学前端:Vue篇(三)

原创
作者头像
传说之下的花儿
发布2023-11-13 23:12:22
2330
发布2023-11-13 23:12:22
举报

2) Vue-Router

vue 属于单页面应用

单页面应用:就是你的整个程序就那一个HTML页面。 可能有人会疑问,我们写了这么多的视图组件,难道这些视图组件都会被用在同一个HTML页面中吗? 没错,他们就是会被用在同一个HTML页面中,只不过这个页面的内容,将来会替换成组件1、组件2、或者是组件3的内容,他的内容会变,但是页面只有一个。

而我们今天学习的这个所谓的路由,就是根据浏览器路径不同,用不同的视图组件替换这个页面内容展示。

例子:

访问根路径:主页就是1个视图组件

访问404:

可以看到,中间的视图组件发生了改变,但是最外层的HTML页面没变。

1. 配置路由

新建一个路由 js 文件,例如 src/router/example14.js,内容如下

代码语言:javascript
复制
 import Vue from 'vue'
 import VueRouter from 'vue-router'
 // @ 绝对路径,代表src
 import ContainerView from '@/views/example14/ContainerView.vue'
 import LoginView from '@/views/example14/LoginView.vue'
 import NotFountView from '@/views/example14/NotFountView.vue'
 ​
 Vue.use(VueRouter)
 ​
 const routes = [
   {
     path: "/",
     component: ContainerView,
   },
   {
     path: "/login",
     component: LoginView,
   },
   {
     path: "/404",
     component: NotFountView,
   },
 ]
 ​
 const router = new VueRouter({
   routes
 })
 ​
 export default router
 ​
  • 最重要的就是建立了【路径】与【视图组件】之间的映射关系
  • 本例中映射了 3 个路径与对应的视图组件

在 main.js 中采用我们的路由 js

代码语言:javascript
复制
import Vue from 'vue'
import e14 from './views/Example14View.vue'
import router from './router/example14'  // 修改这里
import store from './store'
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.config.productionTip = false

Vue.use(Element)
new Vue({
  router,
  store,
  render: h => h(e14)
}).$mount('#app')

根组件是 Example14View.vue,内容为:

代码语言:javascript
复制
<template>
    <div class="out">
        最外层
        <!-- 起到占位作用,改变路径后,这个路径对应的视图组件就会占据 `<router-view>` 的位置,替换掉它之前的内容 -->
        <router-view></router-view>
    </div>

</template>
<style scoped>
...
</style>
  • 样式略
  • 其中 <router-view> 起到占位作用,改变路径后,这个路径对应的视图组件就会占据 <router-view> 的位置,替换掉它之前的内容

2. 动态导入

之前都是使用import这个关键字导入了 我们的vue组件,这种叫做静态导入;除此之外还有动态导入,首先说一下为什么要使用静态导入呢?

将来我们vue的应用程序发布的时候,要打个包,打包的时候,他会将所有组件的JavaScript代码打包到一起,JavaScript包会变得越来越大,影响页面加载速度。所以我们最好的一种解决方式就是不要把所有代码打包到一起,让它按需加载,比如我们用到LoginView.vue的代码时候,这时候才把这个组件的JavaScript代码加载出来,这样就可以大大减少你代码的体积,分层不同的代码块,更为高效。

原文; https://router.vuejs.org/zh/guide/advanced/lazy-loading.html 当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把 不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。

代码语言:javascript
复制
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: "/",
    // 箭头函数 ()=>import(路径)
    component: () => import('@/views/example14/ContainerView.vue'),
  },
  {
    path: "/login",
    component: () => import('@/views/example14/LoginView.vue'),
  },
  {
    path: "/404",
    component: () => import('@/views/example14/NotFoundView.vue'),
  },
]

const router = new VueRouter({
  routes
})

export default router
  • 静态导入是将所有组件的 js 代码打包到一起,如果组件非常多,打包后的 js 文件会很大,影响页面加载速度
  • 动态导入是将组件的 js 代码放入独立的文件,用到时才加载

验证动态路由的好处: 静态路由:

并且打开F12可以看到,静态加载将三个的组件的js代码打包到了app.js文件里:

动态路由:

打开F12,可以看到,是生成了一个对应的.js文件,加载访问组件的js代码。

3. 嵌套路由

组件内再要切换内容,就需要用到嵌套路由(子路由),下面的例子是在【ContainerView 组件】内定义了 3 个子路由

代码语言:javascript
复制
const routes = [
  {
    path:'/',
    component: () => import('@/views/example14/ContainerView.vue'),
    redirect: '/c/p1',
    children: [
      { 
        path:'c/p1',
        component: () => import('@/views/example14/container/P1View.vue')
      },
      { 
        path:'c/p2',
        component: () => import('@/views/example14/container/P2View.vue')
      },
      { 
        path:'c/p3',
        component: () => import('@/views/example14/container/P3View.vue')
      }
    ]
  },
  {
    path:'/login',
    component: () => import('@/views/example14/LoginView.vue')
  },
  {
    path:'/404',
    component: () => import('@/views/example14/NotFoundView.vue')
  },
  {
    path:'*', // path 的取值为 * 表示匹配不到其它 path 时,就会匹配它
    redirect: '/404' // redirect 可以用来重定向(跳转)到一个新的地址
  }
]

子路由变化,切换的是【ContainerView 组件】中 <router-view></router-view> 部分的内容

代码语言:javascript
复制
<template>
    <div class="container">
        <router-view></router-view>
    </div>
</template>
  • redirect 可以用来重定向(跳转)到一个新的地址。
  • path 的取值为 * 表示匹配不到其它 path 时,就会匹配它。
4. ElementUI 布局

通常主页要做布局,下面的代码是 ElementUI 提供的【上-【左-右】】布局:

https://element.eleme.cn/#/zh-CN/component/container

代码语言:javascript
复制
<template>
    <div class="container">
        <el-container>
            <el-header></el-header>
            <el-container>
                <el-aside width="200px"></el-aside>
                <el-main>
                    <router-view></router-view>
                </el-main>
            </el-container>
        </el-container>
    </div>
</template>
5. 路由跳转
标签式
代码语言:javascript
复制
<el-aside width="200px">
    <router-link to="/c1/p1">P1</router-link>
    <router-link to="/c1/p2">P2</router-link>
    <router-link to="/c1/p3">P3</router-link>
</el-aside>
编程式
代码语言:javascript
复制
<el-header>
    <el-button type="primary" icon="el-icon-edit" 
               circle size="mini" @click="jump('/c1/p1')"></el-button>
    <el-button type="success" icon="el-icon-check" 
               circle size="mini" @click="jump('/c1/p2')"></el-button>
    <el-button type="warning" icon="el-icon-star-off" 
               circle size="mini" @click="jump('/c1/p3')"></el-button>
</el-header>

jump 方法

代码语言:javascript
复制
<script>
const options = {
    methods : {
        jump(url) {
            this.$router.push(url);
        }
    }
}
export default options;
</script>
  • 其中 this.$router 是拿到路由对象
  • push 方法根据 url 进行跳转
导航菜单
代码语言:javascript
复制
<el-menu router background-color="#545c64" text-color="#fff" active-text-color="#ffd04b">
    <el-submenu index="/c1">
        <span slot="title">
            <!--小图标-->
            <i class="el-icon-platform-eleme"></i>
            菜单1
        </span>
        <el-menu-item index="/c1/p1">子项1</el-menu-item>
        <el-menu-item index="/c1/p2">子项2</el-menu-item>
        <el-menu-item index="/c1/p3">子项3</el-menu-item>
    </el-submenu>
    <el-menu-item index="/c2">
        <span slot="title">
            <i class="el-icon-phone"></i>
            菜单2
        </span>
    </el-menu-item>
    <el-menu-item index="/c3">
        <span slot="title">
            <i class="el-icon-star-on"></i>
            菜单3
        </span>
    </el-menu-item>
</el-menu>
  • 关于小图标:https://element.eleme.cn/#/zh-CN/component/icon
  • 图标和菜单项文字 建议用 <span slot='title'></span> 包裹起来
  • el-menu-item替换成el-submenu就可以添加子项了
  • el-menu 标签上加上 router 属性,表示结合导航菜单与路由对象,此时,就可以利用菜单项的 index 属性来路由跳转(表示你要跳转到哪里去)
6. 动态路由与菜单

https://www.bilibili.com/video/BV1Tt4y1772f

我们实际应用中,不同的用户,根据身份不一样,看到的菜单和跳转的路由可能是不一样的。

将菜单、路由信息(仅主页的)存入数据库中

代码语言:javascript
复制
 CREATE TABLE `menu`(
     id INT,
     name VARCHAR(10),
     icon VARCHAR(30),
     path VARCHAR(30),
     pid INT,
     component VARCHAR(20),
     PRIMARY KEY (id)
 ) COMMENT '菜单表';
 ​
 INSERT INTO menu(id, name, pid, path, component, icon) VALUES
    (101, '菜单1', 0,   '/m1',    null,         'el-icon-platform-eleme'),
        (105, '子项1', 101, '/m1/c1', 'C1View.vue', 'el-icon-s-goods'),
        (106, '子项2', 101, '/m1/c2', 'C2View.vue', 'el-icon-menu'),
    (102, '菜单2', 0,   '/m2',    null,         'el-icon-delete-solid'),
        (107, '子项3', 102, '/m2/c3', 'C3View.vue', 'el-icon-s-marketing'),
        (108, '子项4', 102, '/m2/c4', 'C4View.vue', 'el-icon-s-platform'),
        (109, '子项5', 102, '/m2/c5', 'C5  View.vue', 'el-icon-picture'),
    (103, '菜单3', 0,   '/m3',    null,         'el-icon-s-tools'),
        (110, '子项6', 103, '/m3/c6', 'C6View.vue', 'el-icon-upload'),
        (111, '子项7', 103, '/m3/c7', 'C7View.vue', 'el-icon-s-promotion'),
    (104, '菜单4', 0,   '/m4',    'M4View.vue', 'el-icon-user-solid');

不同的用户查询的的菜单、路由信息是不一样的

例如:访问 /api/menu/admin 返回所有的数据,访问 /api/menu/zhang ,是个普通用户,返回部分数据 ,类似的路由跳转也一样,如果你是普通用户,你可以跳转的路由也是有限的,我们想要实现这样的功能,就需要将主页的路由和菜单用后台的数据库给他管理起来。前端根据他们身份不同,动态添加路由和显示菜单。

后端代码自己实现: /api/menu/admin返回所有菜单,/api/menu/zhang:返回菜单2及其子项,/api/menu/wang:返回菜单3及其子项 前端:

1. 动态路由

src/router/example15.js

代码语言:javascript
复制
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
    {
        path: "/",
        // 箭头函数 ()=>import(路径)
        component: () => import('@/views/example15/ContainerView.vue'),
    },
    {
        path: "/login",
        component: () => import('@/views/example15/LoginView.vue'),
    },
    {
        path: "/404",
        component: () => import('@/views/example15/NotFoundView.vue'),
    },
    {
        path: '*', // path 的取值为 * 表示匹配不到其它 path 时,就会匹配它
        redirect: '/404' // redirect 可以用来重定向(跳转)到一个新的地址
    },
]

const router = new VueRouter({
    routes
})

export default router
  • js 这边只保留几个固定路由,如主页、404 和 login,其他路由由后端获得,然后动态加入前端路由里去

src/views/example15/LoginView.vue

代码语言:javascript
复制
<!-- 登录 -->
<template>
    <div class="login">
        <el-input v-model="username" placeholder="请输入用户名"></el-input>
        <el-button type="primary" v-on:click="login()">登录</el-button>
    </div>
</template>
<script>
import axios from 'axios';

export default {
    data: function () {
        return {
            username: "admin",
        }
    },
    methods: {
        async login() {
            const resp = await axios.get(`/api/menu/${this.username}`)
            const array = resp.data.data
            // 打印变化前的路由表的所有路由
            console.log(this.$router.getRoutes());
            for (const { Id, Path, Component } of array) {
                // 如果视图组件不为空
                if (Component !== "") {
                    // 动态添加路由
                    // this.$router 路由对象
                    // addRouter(参数1,参数2)
                    // 参数1:父路由名字
                    // 参数2:要添加的路由信息对象
                    this.$router.addRoute('c', {
                        path: Path,
                        name: Id,
                        component: () => import(`@/views/example15/container/${Component}`),
                    })
                }
            }
            // 打印变化后的路由表的所有路由
            console.log(this.$router.getRoutes());
        },
    },
}
</script>
<style>
.login {
    height: 100%;
    background-color: #e4efbe;
}
</style>
  • 以上方法执行时,将服务器返回的路由信息加入到名为 c 的父路由中去
  • 这里要注意组件路径,前面 @/views 是必须在 js 这边完成拼接的,否则 import 函数会失效

此时如果你直接访问/m1/c1,由于还没有添加到路由中,会直接跳转到404页面。

先登录,打开F12可以看到路由表里路由的变化:

此时我们再访问/m1/c1,可以看到已经被注册到路由表里了:

2. 重置路由

但是现在还有一个问题,我们登录zhang之后,再登录wang,会发现,他是直接在上一个路由的基础上新增了2个,这是不对的,我们需要在登录wang之前,将路由重置到初始状态:

现在使用的vue2,配合使用的Vue Router的版本是3.x版本,以后使用vue3的时候会使用vue4.x版本,但是v3.x的版本里,他的API里只有新增路由的方法,没有删除路由的方法,所以本例中用一些”外门邪道“来实现:

/src/router/example15.js里加入:

代码语言:javascript
复制
// 重置路由
export function resetRouter(){
    // router.matcher的matcher属性 包含了这些路由信息,
    // 用最初的路由的matcher替换当前的路由的match达到重置路由的目的
    router.matcher = new VueRouter({routes}).matcher
}

然后再/src/views/example15/LoginView.vue里加入:

代码语言:javascript
复制
import { resetRouter } from '@/router/example15';
...
//在登录的时候重置路由(正常是在注销时调用)
async login() {
            resetRouter();// 重置路由

此时再登录zhang和wang可以看到路由正常了:

3. 页面刷新

我们上面说的动态路由会遭遇页面刷新的问题(vue属于单页面程序,一刷新页面就意味着页面所有内容都重置了),页面刷新后,会导致动态添加的路由失效。

代码调整: src/router/example15.js新增: // addServerRouters 添加服务器返回的路由信息 // 这一部分重用次数多,封装成方法 export function addServerRouters(array) { // 打印变化前的路由表的所有路由 console.log(router.getRoutes()); for (const { Id, Path, Component } of array) { // 如果视图组件不为空 if (Component !== "") { // 动态添加路由 // this.$router 路由对象 // addRouter(参数1,参数2) // 参数1:父路由名字 // 参数2:要添加的路由信息对象 router.addRoute('c', { path: Path, name: Id, component: () => import(`@/views/example15/container/${Component}`), }) } } // 打印变化后的路由表的所有路由 console.log(router.getRoutes()); } src/views/example15/LoginView.vue: import { resetRouter, addServerRouters } from '@/router/example15'; ... async login() { resetRouter();// 重置路由 const resp = await axios.get(`/api/menu/${this.username}`) const array = resp.data.data // 将原本请求服务器数据封装成了方法 addServerRouters(array) ...

思路就是将后端服务器返回的路由数据先存入浏览器,页面刷新后可以将上次存入浏览器的路由数据再取出来重新调用addServerRouter(),将路由信息进行恢复。

那么我们往浏览器里存,存哪呢?

浏览器提供了2个对象,二者的区别是范围不一样:

  • localStorage:即使你浏览器关了,存储数据仍然还在;
  • sessionStorage:以标签页为单位,标签不关,数据就在,但是关闭标签页时,数据会被清除。

我们的路由信息并不需要永久保存,所以这里使用sessionStorage比较合适,解决方法是将路由数据存入 sessionStorage

代码语言:javascript
复制
<script>
import axios from '@/util/myaxios'
import {resetRouter, addServerRoutes} from '@/router/example15'
const options = {
    data() {
        return {
            username: 'admin'
        }
    },
    methods: {
        async login() {       
            resetRouter(); // 重置路由     
            const resp = await axios.get(`/api/menu/${this.username}`)
            const array = resp.data.data;
            
            // localStorage     即使浏览器关闭,存储的数据仍在
            // sessionStorage   以标签页为单位,关闭标签页时,数据被清除 
            sessionStorage.setItem('serverRouters', JSON.stringify(array));
            addServerRoutes(array); // 动态添加路由
            this.$router.push('/');
        }
    }
}
export default options;
</script>
  • sessionStorage.setItem(key,value): 参数1:key string 参数2:value string 可以看到并不可以直接存储数组,所以这里需要JSON.stringify将数组转成json字符串存储。
  • 然后[F12]->[应用程序]->[会话存储] 查看存储的信息:

现在我们把后端给的路由数据存储到浏览器了,那么我们在哪里读取呢?

页面刷新,重新创建路由对象时,从 sessionStorage 里恢复路由数据:

代码语言:javascript
复制
const router = new VueRouter({
    routes
})

// 从 sessionStorage 中恢复路由数据
const serverRoutes = sessionStorage.getItem('serverRouters');
if (serverRoutes) {
    const array = JSON.parse(serverRoutes);
    addServerRouters(array) // 动态添加路由
}
4. 动态菜单

我们现在实现一个功能,在登录之后跳转到首页,主页里我们再看如何制作动态菜单:

之前学习过的路由跳转方式:

  1. 通过<router-link to=''>来跳转
  2. 通过编程,写代码来跳转
  3. 通过ElementUI的导航栏来跳转
代码语言:javascript
复制
 methods: {
     async login() {
         ...
         this.$router.push("/"); // 跳转到主页
     },
 },

思路:从sessionStorage中获取路由数据,通过array的两次遍历将一位的map变成有父子关系的map,再去赋值给data返回对象里的的top,然后使用v-for循环这个top,如果有子元素就使用el-submenu,如果没有更深层子元素了就使用el-menu-item

代码语言:javascript
复制
 <!-- 主页 -->
 <template>
     <div class="container">
         <!-- 容器 -->
         <el-container>
             <!-- 头部 -->
             <el-header>
             </el-header>
             <!-- 主体容器 -->
             <el-container>
                 <el-aside width="200px">
                     <!-- 侧边导航栏 -->
                     <!-- v-bind:unique-opened="true" 同时只能打开一个导航-->
                     <el-menu router background-color="#545c64" text-color="#fff" active-text-color="#ffd04b"
                         v-bind:unique-opened="true">
                         <!-- 在v-for循环里,生成的标签上必须绑定一个key,否则会报错 v-bind:key="遍历对象的唯一标识" -->
                         <template v-for="m of top">
                             <!-- 一级菜单 -->
                             <!-- submenu的index推荐也加上,虽然没有真正跳转,但是不加后台会打印错误信息 -->
                             <el-submenu v-if="m.children" v-bind:key="m.Id" v-bind:index="m.Path">
                                 <span slot="title">
                                     <i :class="m.Icon"></i>{{m.Name}}
                                 </span>
 ​
                                 <!-- 二级菜单 -->
                                 <!-- 这里默认只有二级菜单 -->
                                 <el-menu-item v-for="mchild of m.children" v-bind:key="mchild.Id"
                                     v-bind:index="mchild.Path">
                                     <span slot="title">
                                         <i :class="mchild.Icon"></i>{{mchild.Name}}
                                     </span>
                                 </el-menu-item>
                             </el-submenu>
                             <!-- 
                                 这里可能还会报错:v-if/else branches must use unique keys.
                                 不用管,这是vsCode的问题,运行不会报错
                              -->
                             <el-menu-item v-else v-bind:key="m.Id" v-bind:index="m.Path">
                                 <span slot="title">
                                     <i :class="m.Icon"></i>{{m.Name}}
                                 </span>
                             </el-menu-item>
                         </template>
                     </el-menu>
                 </el-aside>
 ​
                 <!-- 主要内容 -->
                 <el-main>
                     <!-- 占位,组件加载替换 -->
                     <router-view></router-view>
                 </el-main>
             </el-container>
         </el-container>
     </div>
 </template>
 <script>
 export default {
     data: function () {
         return {
             top: [],
 ​
         }
     },
     methods: {},
     mounted: function () {
         // 页面加载的时候从sessionStorage里取出后端给的路由数据
         const routerStr = sessionStorage.getItem("serverRouters");
         const array = JSON.parse(routerStr);
         // 一维数组转导航栏的格式
         const map = new Map();
         for (const obj of array) {
             map.set(obj.Id, obj);
         }
         // 寻找顶层元素
         const top = [];
         for (const obj of array) {
             // 如果这个parent存在,就创建对应父元素的子元素数组,并将当前元素作为子数组加入其中,
             // 否则就是顶层元素
             const parent = map.get(obj.Pid);
             if (parent) {
                 // ?? 如果左侧不存在,右侧赋值给左侧
                 parent.children ??= [];
                 parent.children.push(obj);
             } else {
                 top.push(obj);
             }
         }
         // console.log(top)
         this.top = top;
 ​
     },
 }
 </script>
 <style>
 .container {
     width: 1000px;
     height: 100%;
     background-color: #7bbfec;
     padding: 20px;
     box-sizing: border-box;
 }
 </style>
  • 没有考虑递归菜单问题,认为菜单只有两级
  • 重点掌握v-forv-if这些指令。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2) Vue-Router
    • 1. 配置路由
      • 2. 动态导入
        • 3. 嵌套路由
          • 4. ElementUI 布局
            • 5. 路由跳转
              • 标签式
              • 编程式
              • 导航菜单
            • 6. 动态路由与菜单
              • 1. 动态路由
              • 2. 重置路由
              • 3. 页面刷新
              • 4. 动态菜单
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档