路由(vue-router)

目标:
能够在项目中安装和配置路由:createRouterapp.use(router)
能够使用路由实现单页面应用程序的开发
能够使用嵌套路由:通过children属性进行路由嵌套
能够实现动态路由匹配:使用冒号声明参数项、this.$route.paramsprops: true
能够使用编程式导航:this.$router.pushthis.$router.go
能够使用导航守卫控制路由的访问权限:
路由实例.beforeEach((to, from, next) => { /* 必须调 next 函数*/ })

axios的使用

  1. 首先安装axios包
    npm i axios ,结果报错ERR! ERESOLVE could not resolve ERESOLVE could not resolve
    解决办法是在后面加 --legacy-peer-deps 即可
    npm install axios -save --legacy-peer-deps
    报错的原因是由于npm不同版本库之间命令不兼容。

  2. 在left组件发送get请求:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <button @click="getInfo">发起Get请求</button>

    import axios from 'axios'
    export default {
    methods: {
    async getInfo() {
    const { data: res } = await axios.get('http://www.liulongbin.top:3006/api/get')
    console.log(res)
    }
    }
    }
  3. 在right组件发送post请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <button @click="postInfo">发起 Post 请求</button>

    import axios from 'axios'
    export default {
    methods: {
    async postInfo() {
    const { data: res } = await axios.post('http://www.liulongbin.top:3006/api/post', { name: 'zs', age: 25 })
    console.log(res)
    }
    }
    }

考虑问题 – 优化

  • 问题

    1. 每个组件中,都可能会有发请求的需求,都需要先导入axios,然后调用这些方法,每次用都很麻烦
    2. 每次请求地址都需要写完整地址,但是我们的请求根路径都完全一样。如果将来后端把根路径改了,我们就需要修改每一个组件,不利于后期维护。
  • 思考
    每一个.vue组件都相当于一个vue实例,我们可以理解为:每一个.vue组件都是new Vue()这个构造函数得到的

  • 优化1 – 在Vue的原型链上挂载一个axios

    1
    2
    3
    4
    5
    // main.js
    import axios from 'axios'
    // 一般我们不叫axios,而是$http
    // 因为vue内置的一些成员都是以$开头的(模仿内置成员)
    Vue.prototype.$http = axios

    这样我们在每一个组件中,就不需要再导入axios,直接通过this/原型访问

  • 优化2 – 全局配置 axios 的请求根路径
    在往原型上挂之前,通过axios做一下局域的配置:
    axios.defaults.baseURL = '请求根路径' // axios自带的属性
    组件使用:

    1
    2
    3
    4
    async getInfo() {
    const { data: res } = await this.$http.get('/api/get')
    console.log(res)
    }
  • 缺点
    无法实现api接口的复用:如果这个api在多个页面都需要被用到,每一次都需要重新调这个api接口

axios最优使用 – 实现api接口的复用

使用axios的最优解 —— 封装!(也就是模块化)

将axios接口作为可导入的工具包进行封装

推荐:将其放在utils文件夹下(工具类的意思)

1
2
3
4
5
6
7
8
9
// utils/request.js
import axios from 'axios'

const request = axios.create({
// 指定请求根路径
baseURL: 'https://applet-base-api-t.itheima.net'
})

export default request

需要几个服务器的接口,就封装几个,用哪个就导入哪个。如:request1封装TB的接口,2封装JD的接口

进一步的封装/模块化

如果有多个组件,都要调用request的articles接口,那么每个组件都要这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import request from '@/utils/request'

export default {
data(){
return{
page: 1, // 页码值
limit: 10 // 每页显示多少条数据
}
},
created () {
this.initArticleList()
},
methods: {
// 封装获取文章列表数据的方法
async initArticleList () {
// 发起get请求,获取文章的列表数据
const { data: res } = await request.get('/articles', {
// 请求参数,一般会把参数值定义在data结点,在这里传进去
// 具体是否需要传参,是什么参数取决于后端API
params: { _page: page, _limit: limit }
})
console.log(res)
}
}
}

await后面返回的是一个Promise类型的值,所以我们可以把这一块单独拿出去
可以将其进一步的封装
所有的API调用都可以放进 API 文件夹
封装的API文件都以API的命名结尾,可以一眼看出这是个API

1
2
3
4
5
6
7
8
9
10
// API/articleAPI.js
// 文章相关的API接口,都封装到这个模块中
import request from '@/utils/request'

// 这里不是默认导出,是按需导出
export const getArticleListAPI = function (_page, _limit) {
return request.get('/articles', {
params: { _page, _limit } // 请求参数
})
}

然后每个组件都可以这样调用接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { getArticleListAPI } from '@/api/articleAPI'

export default {
data(){
return{
page: 1, // 页码值
limit: 10 // 每页显示多少条数据
}
},
created () {
this.initArticleList()
},
methods: {
// 封装获取文章列表数据的方法
async initArticleList () {
// 发起get请求,获取文章的列表数据
const { data: res } = await getArticleListAPI(this.page, this.limit)
console.log(res)
}
}
}

这样才达到了axios真正的用法

路由的基本配置与使用

  1. 什么是路由
    路由(router) 就是 对应关系

  2. SPA(单页面应用程序) 与 前端路由
    SPA指的是一个web网站只有唯一的一个HTML页面,所有组件的展示与切换都在这唯一的一个页面内完成。此时,不同组件之间的切换需要通过前端路由来实现。

  3. 什么是前端路由
    通俗易懂的概念:Hash地址(就是#锚链接) 与 组件 之间的 对应关系
    不同的哈希,会展示出不同的组件页面

  4. 前端路由的工作方式

    1. 用户点击了页面上的路由链接
    2. 导致了URL地址栏中的Hash值发生了变化
    3. 前端路由监听了到Hash地址的变化
    4. 前端路由把当前Hash地址对应的组件渲染都浏览器中

    结论:前端路由,指的是 Hash地址 与 组件之间 的 对应关系
    image

vue-router的基本使用

前端路由的底层原理是监听了浏览器的onhashchange事件,自己去封装前端路由是非常麻烦的一件事,我们可以用现成的包去实现前端路由的处理 – vue-router(vue项目中的一个路由的包,只能在vue项目中用)

  1. 什么是vue-router
    vue-router是vue.js官方给出的路由解决方案。它只能结合vue项目进行使用,能够轻松的管理SPA项目中组件的切换。
    vue-router 的官方文档地址: https://router.vuejs.org/zh/

  2. vue-router 安装和配置的步骤

    1. 安装vue-router包:npm i vue-router@3.5.2 -S
      如果因为ESLint安装报错,可以加上后缀:npm i vue-router@3.5.2 -S --legacy-peer-deps
      vue-router最新版本Vue Router 4 是为 Vue 3 设计的,不兼容Vue2。
      所以想在 Vue 2 中使用 Vue Router,安装包的时候需要指定 Vue Router 3 的版本

    2. 创建路由模块
      在src源代码目录下,新建router/index.js路由模块,并初始化如下代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // src/router/index.js 就是当前项目的路由模块
      // 1. 导入 Vue 和 VueRouter 的包
      import Vue from 'vue'
      import VueRouter from 'vue-router'

      // 2. 调用 Vue.use() 函数,把 VueRouter 安装为 Vue 的插件
      Vue.use(VueRouter)

      // 3. 创建路由的实例对象
      const router = new VueRouter()

      // 4. 向外共享路由的实例对象
      export default router
    3. 导入并挂载路由模块

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 导入路由模块,目的:拿到路由的实例对象
      import router from '@/router/index.js'

      new Vue({
      render: (h) => h(App),
      // 在 Vue 项目中,要想把路由用起来,必须把路由实例对象,通过下面的方式进行挂载
      // router:路由的实例对象
      router // 这里是简写
      }).$mount('#app')

      导入路由的时候,index.js可以省略 – import router from '@/router'
      在模块化导入的时候,如果只给了一层路径,默认会导入和加载路径下面名字叫index.js的文件

    4. 声明路由链接和占位符/声明路由的对应关系(路由规则)
      声明占位符:
      App.vue甚至不需要导入组件,只需要声明一个占位符即可,通过a链接切换对应的组件

      1
      2
      3
      4
      5
      <a href="#/left">left </a>
      <a href="#/right"> right</a>
      <!-- 只要在项目中安装和配置了 vue-router,则就可以使用 router-view 这个组件 -->
      <!-- 作用:占位符 -->
      <router-view></router-view>

      在路由模块声明路由的对应关系 – 路由规则

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // 导入需要的组件
      import Left from '@/components/Left-item.vue'
      import Right from '@/components/Right-item.vue'

      // 3. 创建路由的实例对象
      const router = new VueRouter({
      // routes 是一个数组,作用:定义 “hash 地址” 与 “组件”之间的对应关系
      routes: [
      // 路由规则
      { path: '/left', component: Left },
      { path: '/right', component: Right }
      ]
      })
    5. 使用router-link代替a链接
      router-link本质上也会被渲染成a链接

      1
      2
      3
      4
      <!-- 当安装和配置了 vue-router 后,就可以使用 router-link来替代普通的a链接 -->
      <!-- 优势:不用写前面的#,它会在点这个router-link的时候自动在前面加上# -->
      <router-link to="/left">left</router-link>
      <router-link to="/right">right</router-link>

路由重定向 – redirect

路由重定向指的是:用户在访问地址A的时候,强制用户跳转到地址C,从而展示特定的组件页面。通过路由规则的redirect属性,指定一个新的路由地址,可以很方便地设置路由的重定向:

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
// 在routers数组中,声明路由的匹配规则
routes: [
// 当用户访问 / 的时候,通过 redirect 属性跳转到 /left 对应的路由规则
{ path: '/', redirect: '/left'},
{ path: '/left', component: Left },
{ path: '/right', component: Right }
]
})

嵌套路由

通过路由实现组件的嵌套展示,叫做嵌套路由。

如果某个组件本身就是通过路由的方式呈现出来的,在这个组件里又放一些路由规则,这样就形成了路由嵌套。
image

  • 声明子级路由标签和占位符

    1
    2
    3
    4
    5
    6
    7
    // Right.vue
    <!-- 子级路由链接 -->
    <router-link to="/right/tab1">tab1</router-link>
    <router-link to="/right/tab2">tab2</router-link>
    <hr>
    <!-- 子级路由占位符 -->
    <router-view></router-view>
  • 声明嵌套路由的规则 – 路由对应关系
    通过Children属性声明子路由规则
    在src/router/index.js路由模块中,导入需要的组件,并通过children属性声明子路由规则:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import Tab1 from '@/components/tab1.vue'
    import Tab2 from '@/components/tab2.vue'

    // 3. 创建路由的实例对象
    const router = new VueRouter({
    // routes 是一个数组,作用:定义 “hash 地址” 与 “组件”之间的对应关系
    routes: [
    // 当用户访问 / 的时候,通过 redirect 属性跳转到 /left 对应的路由规则
    { path: '/', redirect: '/left' },
    { path: '/left', component: Left },
    { // right页面的路由规则(父级路由规则)
    path: '/right',
    component: Right,
    children: [ // 通过children属性,嵌套声明子级路由规则
    { path:''}
    { path: 'tab1', component: Tab1 },
    { path: 'tab2', component: Tab2 }
    ]
    }
    ]
    })

    注意:子路由规则,不要以斜线开头!

  • 嵌套路由的路由重定向
    在right的路由规则中加上redirect即可,这样路径/right就直接跳转到了/right/tab2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    routes: [
    // 当用户访问 / 的时候,通过 redirect 属性跳转到 /left 对应的路由规则
    { path: '/', redirect: '/left' },
    { path: '/left', component: Left },
    { // right页面的路由规则(父级路由规则)
    path: '/right',
    component: Right,
    redirect: '/right/tab2',
    children: [ // 通过children属性,嵌套声明子级路由规则
    { path: 'tab1', component: Tab1 },
    { path: 'tab2', component: Tab2 }
    ]
    }
    ]
  • 默认子路由
    除了使用redirect路由重定向,还可以使用默认子路由
    默认子路由:如果children数组中,某个路由规则的path值为空字符串,则这条路由规则,叫做“默认子路由”。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    // right页面的路由规则(父级路由规则)
    path: '/right',
    component: Right,
    children: [
    { path: '', component: Tab1 },
    { path: 'tab1', component: Tab1 },//也可以不写这条规则,而是把tab1的路由标签改为/right
    { path: 'tab2', component: Tab2 }
    ]
    }

    默认子路由和重定向都可以展示出children里面的某一个子组件。

动态路由

思考:有如下3个路由链接:

1
2
<router-link to="/movie/1">电影1</router-link>
<router-link to="/movie/2">电影2</router-link>

定义如下3个路由规则:

1
2
3
{path: '/movie/1', component: Movie }
{path: '/movie/2', component: Movie }
{path: '/movie/3', component: Movie }

缺点:路由规则的复用性差

  1. 动态路由的概念
    动态路由指的是:把 Hash地址 中可变的部分定义为参数项,从而提高路由规则的复用性
    在vue-router中使用英文的冒号( : )来定义路由的参数项。示例代码如下:

    1
    2
    // 路由中的动态参数以︰进行声明,冒号后面的是动态参数的名称
    { path: '/movie/:id', component: Movie }

    将多个路由规则,合并成了一个,提高了路由规则的复用性
    注意:路由链接的跳转地址是不能出现冒号:的!<router-link to="/movie/1">电影1</router-link>

  2. 在组件中拿到动态参数项的值
    this.$route是路由的“参数对象”
    this.$router是路由的“导航对象”
    要想拿到上面movie的id值应该通过:this.$route.params.id

为路由规则开启prop传参

简化拿参数的方式。这种方式和this.$route.params.id是彼此独立的,想用哪个就用哪个

1
2
3
4
5
6
// src/router/index.js
// 为当前这条路由规则开启props传参
{ path: '/movie/:id', component: Movie, props: true }

// movie.vue
props:['id'] // 这样就拿到了

拓展 query 和 fullPath

  1. 在 hash 地址中,/ 后面的参数项,叫做 “路径参数”
    在路由“参数对象”中,需要使用 this.$route.params 来访问路径参数

    1
    2
    <router-link to="/movie/1">电影1</router-link>
    <router-link to="/movie/2">电影2</router-link>
  2. 在 hash 地址中,?后面的参数项,叫做 “查询参数”
    在路由“参数对象”中,需要使用 this.$route.query 来访问查询参数

    1
    2
    <router-link to="/movie/1">电影1</router-link>
    <router-link to="/movie/2?name=zs age=20">电影2</router-link>
  3. this.$route中,path只是路径部分; fullPath是完整的地址
    /movie/2 是 path 的值
    /movie/2?name=zs age=20 是 fullPath 的值

路由导航

导航就是跳转的意思,组件之间的切换,它专业的术语叫导航。

声明式导航 & 编程式导航

  • 在浏览器中,点击链接实现导航的方式,叫做声明式导航
    例如:
    普通网页中点击a链接、vue 项目中点击router-link都属于声明式导航

  • 在浏览器中,调用API方法实现导航的方式,叫做编程式导航
    例如
    普通网页中调用location.href跳转到新页面的方式,属于编程式导航

vue-router 中的编程式导航API

vue-router(导航对象)提供了许多编程式导航的API,其中最常用的导航API分别是:

  1. this.$router.push('hash地址')
    跳转到指定 hash 地址,并增加一条历史记录

  2. this.$router.replace('hash地址')
    跳转到指定的hash地址,并替换掉当前的历史记录

  3. this.$router.go(数值n)
    n可以是正数也可以是负数,在浏览历史里面进行前进和后退

  4. $router.go的简化用法
    在实际开发中,一般只会前进和后退一层页面。因此 vue-router 提供了如下两个便捷方法:
    (1) $router.back():在历史记录中,后退到上一个页面
    (2) $router.forward():在历史记录中,前进到下一个页面

注意:在行内使用编程式导航跳转的时候,this必须要省略,否则会报错!

路由导航守卫 – 控制路由的访问权限

导航守卫可以控制路由的访问权限

假设规定,当前项目中,main这个后台主页只有登陆以后才允许被访问(前提)
怎么证明你有没有登陆————token
可以读你的localStorage,看里面有没有token值,如果有就认为你已经是登录了,没有就认为你没有登陆。

image

  1. 全局前置守卫
    前置:比如从a跳到b,还没跳过去,就会触发守卫。
    每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行访问权限的控制:

    1
    2
    3
    4
    5
    //创建路由实例对象
    const router = new VueRouter({ ... })
    //调用路由实例对象的beforeEach方法,即可声明“全局前置守卫”
    //每次发生路由导航跳转的时候,都会自动触发 fn 这个“回调函数”
    router.beforeEach(fn)
  2. 守卫方法的3个形参
    全局前置守卫的回调函数中接收3个形参,格式为:

    1
    2
    3
    4
    5
    6
    7
    8
    const router = new VueRouter({ ... })

    // 全局前置守卫
    router.beforeEach((to,from,next)=>{
    // ro 是将要访问的路由的信息对象
    // from 是将要离开的路由的信息对象
    // next 是一个函数,调用next() 表示放行,允许这次路由导航
    })
  3. next函数的3种调用方式
    具体使用哪一种,看自己的需求
    image

  • 全局前置守卫控制访问权限简单示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 为router实例对象,声明全局前置导航守卫
    router.beforeEach(function(to, from, next) {
    // 分析:
    // 1. 要拿到用户将要访问的hash地址
    // 2. 判断hash地址是否等于/main
    if (to.path === '/main') { // 等于 /main ,证明需要登陆之后才能访问成功,
    // 需要读取 localStorage 中的 token值
    const token = localStorage.getItem('token')
    if (token) {
    next() // 有token值,则已登录 放行
    } else {
    next('/login') // 无 token值 则强制跳转到login登录页面
    }
    } else {
    next() // 不等于则访问的不是后台主页,不需要登陆 直接放行 next()
    }
    })
    vue的导航守卫中除了全局前置守卫之外,还有全局解析守卫、全局后置钩子、路由独享的守卫、组件内的守卫,共5种
    其中,全局前置守卫的用法是最常见的,后面的几种后续会进行补充

案例练习

  • 路由应用案例

    后台管理案例

  • 黑马头条

    SPA单页面应用程序 移动端 简单案例
    实现下拉刷新、上拉加载更多功能、Vant组件库的使用

    配置:Babel、Router、CSS Pre-processors、Linter / Formatter、Vue 2.x
    选择路由模式 – Use history mode for router? – N

    history mode for router 需要和后端配合使用,兼容性比较差
    #开头的hash值所表示的路由,通用性更强,低级还是高级的浏览器都支持

    css预处理器 – less
    ESLint – Standard config 标准配置
    Lint on save 在保存的时候进行格式的检查

    项目结构:
    views文件夹

    如果某个组件,是通过路由进行动态展示和切换的,这种组件要放到views里面
    如果某个组件不是通过路由切换的,是一个可复用的组件,那么这个组件要放到components里面

Vant组件库

轻量、可靠的移动端组件库
Vant2官方文档(适用于 Vue2 开发) Vant4官方文档(适用于 Vue3 开发)

  1. 安装

    1
    2
    3
    4
    5
    # Vue 3 项目,安装最新版 Vant:
    npm i vant -S

    # Vue 2 项目,安装 Vant 2:
    npm i vant@latest-v2 -S
  2. 导入组件
    我们选择最便捷的方式 – 导入所有组件

    虽然官方推荐的做法是:自动按需引入组件。但是配起来太复杂了,成本太高了
    引入所有组件会增加代码包体积,但是开发的时候我们怎么快怎么来,不用考虑体积的问题
    在发布的时候,我们可以进行项目体积的优化,可以直接把Vant从项目里面抽出去

    1
    2
    3
    4
    5
    6
    // main.js
    import Vue from 'vue';
    import Vant from 'vant';
    import 'vant/lib/index.css';

    Vue.use(Vant);
  3. 使用
    查阅官方文档即可
    Vant2官方文档(适用于 Vue2 开发)
    Vant4官方文档(适用于 Vue3 开发)
    注意:在使用Vant组件的时候,如果某个组件的某个属性默认值是false,希望把它改成true,直接把这个属性写上就可以了
    如:<van-nav-bar title="标题" fixed placeholder />
    相当于<van-nav-bar title="标题" :fixed='ture' :placeholder='true' />
    这里给属性值类型是布尔值,所以要用v-bind绑定,否则是字符串

  4. 覆盖Vant组件的样式/deep/ 样式穿透

    还可以通过Vant组件库提供的定制主题,来直接改变组件的样式,这样在每个组件使用该组件都不需要再去修改。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 在main.js中 引入全部样式
    import 'vant/lib/index.less';

    // vue.config.js
    // 这个文件是 vue-cli 创建出来的项目的配置文件
    // 在vue.config.js 这个配置文件中,可以对整个项目的打包、构建进行全局性的配置
    const { defineConfig } = require('@vue/cli-service')
    module.exports = defineConfig({
    transpileDependencies: true,
    css: {
    loaderOptions: {
    less: {
    // 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
    lessOptions: {
    modifyVars: {
    // 直接覆盖变量
    // 这里是覆盖了 NavBar 导航栏 的背景色
    'nav-bar-background-color': 'red' // #007bff
    // 或者可以通过 less 文件覆盖(文件路径为绝对路径)
    // hack: `true; @import "your-less-file-path.less";`
    }
    }
    }
    }
    }
    })

    缺点:需要修改配置文件,必须重启打包服务器,太过麻烦,所以了解即可,实际开发不会这么使用
    而是通过 less 文件覆盖进行主题的定制:

    1
    hack: `true; @import "your-less-file-path.less";`

    好处:不用重启打包服务器,直接改就能生效

通过less文件覆盖进行主题定制

如:在src目录下新建一个theme.less

1
2
3
4
5
// 在theme.less文件中,覆盖Vant官方的less变量值
@blue: #007bff;

// 覆盖 Navbar 的 less 样式
@nav-bar-background-color: @blue;

然后配置vue.config.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 这个文件是 vue-cli 创建出来的项目的配置文件
// 在vue.config.js 这个配置文件中,可以对整个项目的打包、构建进行全局性的配置
const { defineConfig } = require('@vue/cli-service')

// webpack在进行打包的时候,底层用到了node .js
// 因此,在vue.config.js 配置文件中,可以导入并使用node.js中的核心模块
const path = require('path')
const themePath = path.join(__dirname, '/src/theme.less')

module.exports = defineConfig({
transpileDependencies: true,
css: {
loaderOptions: {
less: {
// 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
lessOptions: {
modifyVars: {
// 通过 less 文件覆盖(文件路径为绝对路径)
hack: `true; @import "${themePath}";`
}
}
}
}
}
})

vue.config.js 配置文件

这个文件是 vue-cli 创建出来的项目的配置文件
在这个配置文件中,可以对整个项目的打包、构建进行全局性的配置

项目开发完成之后,可以npm run build进行打包
最终项目里面会有个dist文件夹,里面有个index.html首页

但是有个问题:我们直接进入dist目录,双击这个首页打开,是看不到效果的。
因为:默认情况下,我们运行npm run build打包生成的dist,里面的这些文件,只能发布到服务器上,通过http协议才可以正常被打开。而双击打开的是file协议,所以看不到效果

如果想要看到效果,需要做一下打包发布的配置:vue.config.js-publicPath
在vue.config.js配置文件中设置publicPath为’./‘,再次打包文件,即可在文件夹中双击打开

1
2
3
4
5
6
// ...
module.exports = defineConfig({
transpileDependencies: true,
publicPath: './',
css: {...}
})
  • publicPath
    部署应用包时的基本 URL。
    默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上,例如 https://www.my-app.com/
    如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.my-app.com/my-app/,则设置 publicPath 为 /my-app/ 。

    这个值也可以被设置为空字符串 (‘’) 或是相对路径 (‘./‘),这样所有的资源都会被链接为相对路径,这样打出来的包可以被部署在任意路径,也可以用在类似 Cordova hybrid 应用的文件系统中。

    相对路径的 publicPath 有一些使用上的限制。在以下情况下,应当避免使用相对 publicPath:

    • 当使用基于 HTML5 history.pushState 的路由时;
    • 当使用 pages 选项构建多页面应用时。

拓展

hash地址注意点

hash地址里面字母都要是小写,url地址里面不要出现大写字符或字母
除非是一些 搜索关键字 可以出现大写

有权限的hash地址

在导航守卫 – 全局前置守卫中
这个if是判断我们访问的是否是一个有权限的hash地址:if (to.path === '/main')

1
2
3
4
5
6
7
8
9
10
11
12
router.beforeEach(function(to, from, next) {
if (to.path === '/main') {
const token = localStorage.getItem('token')
if (token) {
next()
} else {
next('/login')
}
} else {
next()
}
})

如果我们项目中有多个页面,都需要访问权限,这一个判断肯定满足不了我们的需求
我们可以在后面加 或 |

1
if (to.path === '/main' || to.path === '/home' || to.path === '/home/users'){}

但是条件会变的越来越长

我们还可以通过数组:

1
2
const pathArr = ['home','/home/users','/home/rights']
if (pathArr.indexOf(to.path) !== -1){...}

后面有任何的地址,都可以放到数组里面去
如果觉得多了以后,数组也会变的特别长,可以把这个数组单独处理为js文件,需要的时候再导入过来。如:

1
2
3
4
5
6
7
8
9
10
// src/router/pathArr.js
export default ['home','/home/users','/home/rights']

// src/router/index.js
import pathArr from '@/router/pathArr.js'
...
//不需再声明
router.beforeEach(function(to,from,next){
if(pathArr.indexOf(to.path)!=-1){...}
})

使用meta属性进行权限处理

可以配置meta属性,使用前置守卫进行判断是否需要进行权限处理,会方便很多

  1. 配置 meta 属性
    在路由配置中,可以在每个路由的 meta 属性中指定需要进行权限处理的信息。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const router = new VueRouter({
    routes: [
    {
    path: '/admin',
    component: Admin,
    meta: {
    requiresAuth: true,
    roles: ['admin'] // 用于指定可以访问特定路由的用户的角色
    }
    },
    // 其他路由...
    ]
    })

    在这个例子中,/admin 路由需要进行权限处理,只有具有 admin 角色的用户才能访问。

  2. 使用前置守卫
    在 Vue Router 中,使用 beforeEach 前置守卫来拦截导航并执行权限检查:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    router.beforeEach((to, from, next) => {
    if (to.meta.requiresAuth) {// 检查是否需要进行权限处理
    if (isAuthenticated()) {// 检查用户是否已登录
    if (hasRequiredRoles(to.meta.roles)){ // 检查用户是否具有所需的权限
    next()// 允许导航
    }else {
    next('/no-access') // 无权限,重定向到无权限页面
    }
    } else {
    next('/login')// 未登录,重定向到登录页面
    }
    } else {
    next() // 不需要权限处理,允许导航
    }
    })

    其中,isAuthenticated() 和 hasRequiredRoles() 是自定义函数,用于检查用户是否已登录以及是否具有所需的权限。

  • isAuthenticated()函数 :用于检查用户是否已登录
    通常用于判断用户是否携带有效的 token
    实际应用中,可以根据自己的后端 API 设计来实现 isAuthenticated() 函数:

    1. 从本地存储(如 localStorage 或 sessionStorage)中获取用户的 token。
    2. 向后端 API 发送一个请求,携带用户的 token。
    3. 后端 API 验证 token 的有效性。
    4. 如果 token 有效,后端 API 返回成功响应。
    5. isAuthenticated() 函数返回 true,表示用户已登录。
    6. 否则,isAuthenticated() 函数返回 false,表示用户未登录或 token 无效。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      function isAuthenticated() {
      const token = localStorage.getItem('token')// 从本地存储中获取 token
      if (token) {// 如果 token 存在,向后端 API 发送请求
      return fetch('/api/auth/verify-token', {
      method: 'POST',
      headers: {
      'Authorization': `Bearer ${token}`
      }
      })
      .then(res => res.json())
      .then(data => { // 'data' 是一个 JavaScript 对象,包含从服务器接收到的 JSON 数据
      if (data.success) {// 如果 token 有效,返回 true
      return true
      } else {// 如果 token 无效,返回 false
      return false
      }
      })
      .catch(err => {// 如果请求失败,返回 false
      return false
      })
      } else {// 如果 token 不存在,返回 false
      return false
      }
      }
      .then(res => res.json()) 是 JavaScript Promise 链中的一个回调函数。
      作用: 将服务器返回的 HTTP 响应对象 (res)————服务器响应的主体部分(JSON 字符串) 转换为一个纯 JavaScript 对象,并将其传递给下一个 .then() 回调函数。
      注意:
      只有当服务器响应的 Content-Type 头部设置为 application/json 时,res.json() 方法才能正常工作。
      如果服务器响应的主体部分不是有效的 JSON 字符串,res.json() 方法会抛出一个错误。
  • hasRequiredRoles() 函数用于检查用户是否具有访问特定路由所需的权限。
    它的实现方式取决于你的具体应用程序和后端 API 设计。
    一种常见的方法:

    1. 从本地存储(如 localStorage 或 sessionStorage)中获取用户的角色。
    2. 将用户的角色与路由配置中指定的所需角色进行比较。
    3. 如果用户具有所有必需的角色,则返回 true,表示用户具有访问权限。
    4. 否则,返回 false,表示用户没有访问权限。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      function hasRequiredRoles(requiredRoles) {
      const userRoles = localStorage.getItem('roles')// 从本地存储中获取用户的角色
      // 将用户的角色与所需的权限进行比较
      for (let i = 0; i < requiredRoles.length; i++) {
      if (!userRoles.includes(requiredRoles[i])) {
      return false
      }
      }
      return true // 如果用户具有所有必需的角色,返回 true
      }

    另一种方法:
    向后端 API 发送一个请求,携带用户的 token 和路由的所需角色。后端 API 检查用户的权限并返回一个响应,指示用户是否具有访问权限。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    function hasRequiredRoles(requiredRoles) {
    const token = localStorage.getItem('token')// 从本地存储中获取用户的 token
    // 向后端 API 发送请求
    return fetch('/api/auth/check-permissions', {
    method: 'POST',
    headers: {
    'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify({
    requiredRoles: requiredRoles
    })
    })
    .then(res => res.json())
    .then(data => {
    if (data.success) {// 如果用户具有访问权限,返回 true
    return true
    } else { // 如果用户没有访问权限,返回 false
    return false
    }
    })
    .catch(err => {
    return false // 如果请求失败,返回 false
    })
    }

Less变量

Less 是一种可编程的样式语言。

@是在less语法里面,专门定义变量的一种格式
支持我们以编程的方式来写CSS代码

1
2
3
4
5
6
7
@width: 10px;
@height: @width + 10px;

#header{
width: @width;
height: @height;
}

编译为:

1
2
3
4
#header{
width: 10px;
height: 20px
}