学习要点记录:Vue2_大事件项目(B站视频)

84 阅读26分钟

从2013年尤雨溪提交第一个git commit开始,vue版本v0.0.0开始了成长之路

从v1的Object.defineProperties监听对象属性改变,和addEventListener('input')来实现双向绑定

到v2版本加入虚拟DOM节点vnode,变成Model-View-ModelView的响应式框架

到v3版本的使用Proxy代理对象的优化,性能的提升,API风格优化等等

vue的十年,始终保持求精和开放的态度,这也有益于其生态和成为最流行的渐进式mvvm框架之一

序:

暨本人学习了html DOM BOM、css3样式规范(及CSSOM)、ES6(JavaScript的ES2015规范)之后,在使用dayjs、jQuery等等基础库和fetch、promise等原生js来编写网站的基础上,自认为学习一门前端框架语言对网站应用快速构建将会更有帮助。

通过网上了解,angular和react更加老牌,而vue则是参考了前两者的痛点之后应运而生,也相对容易上手,因此,就此开始学习vue和在将来使用vue。

本来是想直接从vue3开始学习使用的,但总觉得vue3已经发展偏于成熟,里头的很多模式和风格都是在vue2基础上优化过了,很多核心的代码都被“优化封装”了,对于小白来说不好理解其内在的原理,因此先过一遍vue2的写法,vue2虽然有比较多的条条框框,也因此更加容易理解vue的内在层级结构。

(过一遍源码会特别费精力,本人通过网上的MVVM原理模拟、vue原理剖析等一些文章视频旁敲侧击,大概了解了vue是个什么回事)

学与用结合

作为小白而言,一开始对vue知识还有很多空白,总是通过遇到问题再查官方文档和google,这样的效率并不高,还是系统性学习能够在短时间内掌握更广的知识面,剩下零散的知识点以后在遇到时再补充。

刚好看到一个B站视频讲解vue2结合vue2版的elementUI的使用,感谢UP主的无私分享,有需要的朋友可以过去点个赞:

b站链接: www.bilibili.com/video/BV12d…

后端API: www.apifox.cn/apidoc/shar…

那么,以下内容,就是对本视频集合(将近100个视频)做一个视频内容要点总结

以便回顾和查阅


P1 项目_01_项目-介绍

总结: 使用vue2+elementUI编写一个后台管理系统,功能包括登录注册退出、个人中心,elementUI菜单导航、elementUI列表table、echarts图表等的使用。

运用vue2的路由管理vue-router、全局状态管理vuex、API请求插件axios等知识:

image.png

image.png

P2 项目_02_项目_初始化

  1. 全局安装vue脚手架:npm i @vue/cli -g
  2. 创建vue项目:vue create 项目名称,选中vue2,选中vuex、route路由、eslint、babel等插件
  3. 进入项目,清空模板内容,加入大事件相关资源点击获取,调整目录结构: image.png

P3 项目_03_项目_ESLint介绍

ESLint的规范文档:

  1. 规范文档1: www.verydoc.net/eslint/cn/0…
  2. 规范文档2: standardjs.com/rules.zhcn.…
  3. 规范文档3: eslint.cn/docs/rules/
  4. vue规则: eslint.vuejs.org/rules/

P4 P4项目_04_项目_ESLint插件使用

vscode搜索插件:eslint

webstorm设置: image.png

可以继承规则、更改规则:

项目根目录下的配置文件:.eslintrc.js

module.exports = {
  root: true,
  env: {
    node: true
  },
  
  //继承规则
  extends: [
    'standard',
    'plugin:vue/recommended'
  ],
  parserOptions: {
    parser: '@babel/eslint-parser'
  },
  
  //更改规则
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    // semi: 2,
    'comma-dangle': [1, 'never'],
    'prefer-const': [0],
    'no-unused-vars': 1,
    'vue/multi-word-component-names': 'off',
    'vue/max-attributes-per-line': 'off'
  }
}

P5 项目_05_项目_组件库配置

#全局安装
pnpm install

# 引入element-ui
pnpm i element-ui

import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.use(ElementUI/*, { size: 'small', zIndex: 3000 } */)

P6 项目_06_项目_封装请求库

引入axios

pnpm install axios

封装请求:

import axios from 'axios'

// process.env.NODE_ENV

const headerToken = 'Authorization'

// create an axios instance
const axiosInstance = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 100 * 1000 // request timeout
  // headers: {'Content-Type': 'application/x-www-form-urlencoded'}
})

P7 项目_07_项目_git使用

安装git

项目初始化git init

P8 项目_08_登录和注册页面以及路由准备

准备两个页面(内容先不写),配置页面的路由:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    component: () => import('@/views/layout') //懒加载import()
  },
  {
    path: '/reg',
    component: () => import('@/views/Register')
  },
  {
    path: '/login',
    component: () => import('@/views/Login')
  }
]

const router = new VueRouter({
  routes
})


export default router

P9 项目_09.01_注册页面_标签和样式准备

注册页面 背景图片 注册输入框居中显示

P10 项目_09.02_注册页面_表单标签和变量准备

添加注册框内的表单(输入框和按钮),调整下样式,data里添加表单对象变量

P11 项目_09.03_注册页面_表单校验

el-form自带校验规则,添加rules对象,设置校验规则,添加自定义校验:

image.png

P12 项目_10.01_注册功能_点击事件和拿到数据

提交按钮,JS兜底校验,通过ref定位到对应form表单,调用其validate方法,return false会阻止按钮默认行为,并提示校验不通过的项。

image.png

P13 项目_10.02_注册功能_封装接口方法完成调用

在兜底校验回调函数中,判断校验通过后,调用API接口提交表单数据,

把回调函数改为异步async,然后直接从API接口结果中解构出响应对象,

一旦响应成功,给用户报提示,然后路由跳转到登录页。

image.png

P14 项目_11_登录页面_标签准备和校验_跳转页面

准备登录页面的表单内容和按钮,点击“去注册”可以通过路由跳转注册页面

在主页的路由中添加redirect重定向,未登录访问首页时自动跳转登录页

image.png

P15 项目_12_登录功能_实现

实现登录页面的API接口和拿到返回响应结果

P16 项目_13_token保存到vuex中

使用vuex全局状态管理,通过mutations方法来操作state(可追踪),而不是直接手动修改state

...    mapMutations  (   ['updateToken']   )  

image.png

P17项目_14_vuex本地持久化配置

npmjs.com搜索vuex-persistedstate,提示这个插件过期不维护了,vue还有像pinia等其他方案可选;

image.png

image.png

P18项目_15_布局页面准备和跳转

分析主页布局页面的结构(头部,左边导航栏,右边内容区域),使用el-container容器布局,直接复制代码粘贴。

配置主页的路由跳转去掉重定向,加入主页component

P19 项目_16_退出登录

在组件身上绑定的事件名称都需要组件内部触发,如下解析:

image.png

elementUI的确认弹框,使用es6的promise异步返回用户点击确定还是取消,在then(确定)和catch(取消)中处理逻辑:

image.png

P20 项目_17_布局_获取用户信息

获取用户信息需要传token标识,通过状态管理vuex拿到state里面的token并放到请求的headers里头

(后面可以在全局axios拦截器添加token)

image.png

P21 项目_18_保存用户信息到vuex中

除了保存token,还需要把登录用户信息保存到vuex里(不用每次都请求获取),

mutations是同步操作状态(store.commit(mutation)),actions是内部含有异步操作的代码(store.dipatch(action)

actions可以通过mapActions(['method'])或者直接store.dipatch(action)调用

image.png

P22&P23

P22 项目_19_在路由守卫中获取用户信息

P23 项目_20_避免重复请求用户信息_退出登录清除用户信息

目前实现的功能中,退出登录重新登录,不刷新页面,不会重新请求用户信息

所以,把获取用户信息的触发放到全局路由守卫中

(其实,用户登录之后的token、用户信息的获取保存,session过期这些内容不应该参杂到业务需求逻辑代码中,而是在全局路由守卫或者API请求拦截器中,或者用一个独立的模块,来处理)

image.png

P23 : 内容就是用户退出之后清空当前用户的登录信息(token和用户信息等等一切登录用户相关的内容)

(个人认为,还是跟上面说的一样,要独立出来放到一个独立的模块中处理)

P24 项目_21_getters使用_铺设用户信息

image.png image.png

P25 项目_22_侧边栏导航的组件标签准备

学会elementUI的使用:

  1. 查文档找组件
  2. 复制组件html代码和js代码
  3. 学会使用文档中说明的属性来设置达到自己的效果,比如导航菜单可以结合路由使用: image.png

image.png

image.png

P27 项目_24_axios请求拦截器_统一携带token

跟上面 P22&P23 提到的权限控制拦截应该放到一个独立的模块的思想一致。

(但是一个生产系统前端是复杂的,不止处理token和用户信息,还有用户权限等等)

image.png

axiosInstance.interceptors.request.use(
  config => {
    // 请求增加时间戳
    config.params = {
      _t: new Date().getTime(),
      ...config.params
    }
    if (config.method === 'post') {
      Object.keys(config.data).forEach(item => {
        if (config.data[item] === null) {
          delete config.data[item]
        }
      })
      // 引入了application/json 的 Content-Type
      if (config.headers['Content-Type'].includes('x-www-form-urlencoded')) {
        config.data = qs.stringify(config.data)
      }
      if (!config.headers['Content-Type']) { // post Content-Type default to application
        config.headers.set('Content-Type', 'application/json;charset=UTF-8')
      }
    }

    if (store.state.token) {
      config.headers[headerToken] = store.state.token
    } else {
      // router.push('/login')
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

P28 项目_25_权限拦截控制_未登录无法看到正常页面

在路由守卫中判断用户是否登录,否则使用next(/login)跳转到登录页面

注意:所有路由跳转(包括next)都还会经过路由守卫,所以要判断登录注册页面放行。

不执行next(),页面就会卡住空白

image.png

P29 项目_26_axios响应拦截器_统一判断401做被动退出

跟上面 P22&P23P27 提到的一样,

用户token过期后,统一拦截,路由跳转到登录页面

axiosInstance.interceptors.response.use(
  response => {
    const res = response.data
    let errorText = errorCodeMap.get(res.code)
    if (errorText) {
      alert(errorText ?? '系统繁忙')
      return Promise.reject(res)
    }
    if (response.config.returnResponse) { // request请求传过来的returnResponse在response中还在
      return response
    } else {
      return res
    }
  },
  error => {
    const {
      status,
      message
    } = error?.response?.data || {}
    return Promise.reject(error)
  }
)

P30 项目_27_首页组件创建和路由配置

首页的内容区域属于二级路由

一级路由需要以斜杆/开头,二级以下路由直接以名称开头

image.png

P31 项目_28_vue项目中引入echarts

echarts文档: echarts.apache.org/handbook/zh…

pnpm install echarts

下面是全部都引入:

import * as echarts from 'echarts';

// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('main'));
// 绘制图表
myChart.setOption({
  title: {
    text: 'ECharts 入门示例'
  },
  tooltip: {},
  xAxis: {
    data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
  },
  yAxis: {},
  series: [
    {
      name: '销量',
      type: 'bar',
      data: [5, 20, 36, 10, 10, 20]
    }
  ]
});

注意: document.getElementById需要vue mounted之后才能拿到节点元素。

P32 项目_29_echarts图表的使用

使用.setOption给图表传数据,然后就可以绘制出来

option.series.type是图表的类型,比如柱状图bar、折线图line、饼状图pie

其他查询文档。

P33 项目_30_个人中心_基本资料组件准备和路由

跟之前一样,从elementUI找组件(el-card),然后添加当前二级路由。

注意点:

从地址栏或者其他地方直接路由到某个二级路由,左侧导航菜单不会跟着改变,因此需要设置绑定默认激活菜单项为当前路由路径:

image.png

P34 项目_31_个人中心_基本资料调接口更新保存

  1. 添加formbiaodan
  2. 添加接口API
  3. 点击按钮,兜底校验,提交数据
  4. 成功后刷新状态管理store,按需跳转路由router等等

P35 项目_32_个人中心_基本资料重置按钮实现

调用elementUI el-formresetFields()方法清空重置输入框

好像disabled的输入框内容不会重置

resetFields()是把输入框内容重置为初始值,而初始值是在第一次渲染时的值,如果第一次渲染之前把值改了,那就是改了初始值(懒加载的问题))

P36 P37

项目_33_个人中心_更换头像_组件准备和文件选择窗口安排

项目_34_个人中心_更换头像_选择后预览

文件input样式不好改,通过点击按钮触发文件input的选择文件,然后监听文件选择事件,

拿到文件后

  1. 通过URL转成内存中的文件地址,然后赋予给预览图片的src来显示
  2. 转成base64字符串,然后赋予给src

image.png

P38 项目_35_个人中心_更换头像_调用接口保存并让vuex更新

  1. 添加上传头像接口,把base64的图片当成参数
  2. 上传成功后,dispatch => vuex的action,更新用户个人信息中的图片

P39 项目_36_个人中心_重置密码_页面准备和路由

  1. 明确目标,准备标签、样式、校验
  2. 添加重置密码的二级路由

P40 项目_37_个人中心_重置密码_自定义校验

自定义校验:成功调用callback不传参数,失败传Error说明失败原因(会变成红色提示字)

image.png

image.png

P41 项目_38_个人中心_重置密码_完成功能

  1. 前端校验两次密码一样
  2. 请求后端接口校验原密码
  3. 成功后清除登录用户的信息,路由到登录页面

P42 项目_39_文章分类_页面组件和路由

添加文字分类页面,并添加该组件的二级路由

P43 项目_40_文章分类_铺设数据

请求后台接口,把文章分类列表数据 显示在列表页面

el-table 只需要设置 :data = 数据对象list,会自动循环每一行的数据展示

每列使用prop属性设置具体字段名,使用type=index 设置本列为序号列

P44 项目_41_文章分类_添加分类_对话框准备

使用el-dialog组件弹框展示增加文章分类form表单

  1. 介绍el-dialog组件可以设置的各种属性值
  2. :visable=变量,在父组件中改变变量来控制子组件的打开关闭
  3. :visable.sync表示双向绑定,等同于 :visable 加上 @update(接收子组件的$emit),这个sync在vue3中被移除,需要使用v-model(vue3一个标签可以绑定多个)在父组件中实现双向绑定

P45 项目_42_文章分类_添加分类_表单准备和对话框关闭重置表单

使用子组件自带的close事件,来监听关闭,关闭后清空重置数据

P46项目_43_文章分类_添加分类_调用接口并刷新列表

添加分类_调用接口并刷新列表

P47 项目_44_文章分类_修改分类_共用同一个对话框并数据回显

需求: 点击列表 某一行的按钮,获取本行数据

作用域插槽的由来github.com/vuejs/rfcs/…

用法:v-slot:name="valueObj"

使用name来标识一个具名插槽(可简写为#name="valueObj"),如果name为空,则表示是默认插槽

在子组件中:

<slot name="name" :name1="name1" />

父组件可以通过valueObj.name1获取子组件所有绑定的值!

正好,elementUI的table的el-table-column这个子组件提供了作用域插槽的值:row对象、column等,如下: 作用于 image.png

P48 项目_45_文章分类_修改分类_区分后调用接口

同一个按钮区分 新增 和 修改

使用一个变量,或者判断id是否存在

P49 项目_46_文章分类_修改分类_dialog和form和回显共用的bug

一进来,分类列表显示后,直接点击修改,那么会展示修改的内容,

但是再点击新增后,看到的是修改的内容,而不是空白表单,那是因为:

  1. 点击修改按钮,visible=true显示弹框表单
  2. 因为是delay懒加载,所以这时候表单组件还没挂载
  3. 然后把修改的内容回显在表单中
  4. 等待同步代码写完后,这些操作全部放到一个队列中被一次性调用,
  5. 而,表单的重置按钮是触发重置回第一次初始的值,而第一次初始的值就是点击修改按钮后设置的回显值
  6. 所以等到点新增再点重置,就会重置为修改的回显值。

(vue会把多个操作放到队列中,然后等待下一次渲染时执行)

所以,显示表单时,先调用nextTick让组件的懒加载渲染出来,然后在回调函数中设置回显:

image.png

P50 项目_47_文章分类_删除分类_功能完成

通过接口删除id

然后重新渲染列表

文章分类到此完成!!

P51 项目_48_文章列表_组件创建和路由

组件页面的表头,配置路由

P52 项目_49_发表文章_对话框准备

发表文章,弹框dialog,点击关闭,弹出确认框,确认框返回一个promise对象

P53 项目_50_文章管理_发表文章_表单的2个准备

请求接口获取下拉列表数据,并回显到select的option中

P54 项目_51_文章管理_发表文章_富文本编辑器使用

使用富文本编辑器(使用npm引入富文本插件组件)

P55 项目_52_修改组件内的样式

关于 scoped:

  1. webpack打包会给每个scoped组件加上一个data-v-hash...的属性值
  2. 如果引用了子组件,那么子组件除了有自己scoped的data-v-hash...,还会给一个父组件的data-v-hash...
  3. 子组件如果用了slot,那么
    • 子组件的根节点会给一个父组件的data-v-hash...
    • 属于子组件所有节点会自带自己的一个data-v-hash...
    • 子组件中写在父组件的slot节点(以及根节点)会带一个自定义的data-v-hash...
    • (也就是说),子组件自己生成一个data-v-hash...,子组件在父组件的slot内容又生成一个data-v-hash...

关于 scoped 的样式:

  1. 所有scoped中的样式都会在后面加上[data-v-hash...]的属性选择器
  2. 根据上面webpack的生成规则,可以预见样式的作用范围

使用::v-deep

  1. 这个::v-deep 会把[data-v-hash...]提到前面来,比如
    • [data-v-hash...] (这里有空格) .main ... { color:red; }
  2. 因此,这个::v-deep ,会使得样式应用于子组件中除了根节点的其他元素节点,以及所有嵌套的其他子组件节点,层层穿透

P56 项目_53_文章管理_发表文章_封面标签准备和选择图片

标签准备

P57 项目_54_文章管理_发表文章_封面图片预览

图片预览

P58 项目_55_Vue代码里如何引入相对路径图片

意思总结:

对于html标签中的原生的资源路径,webpack打包时会统一把资源合并到一个文件夹,然后处理修改资源相对路径

而对于js变量中直接写的字符串路径,webpack不会去调整资源和该字符串路径的位置

因此,动态绑定引入的资源,要用import src from '../src.png'先导入进来,然后再当成一个相对路径去赋予给比如src

这样webpack打包会自行处理这个相对路径

image.png

P59 项目_56_点击发布和草稿按钮_标记保存到表单对象里

同个按钮处理不同的逻辑,通过绑定不同的参数值到form对象里,来做去区分

P60 项目_57_发布文章做校验_发现问题

点击提交之前

兜底校验

P61 项目_58_下拉菜单的校验时机

下拉菜单的校验时机是 change改变时候, 而不是blur

P62 项目_59_富文本结合表单的校验触发

quill-editor不属于el-form自己的校验项,需要使用quill-editor自带的监听change或者input事件,

然后在input事件中触发el-form的校验规则(el-form的validateFields用来校验不属于el-form的规则,相当于整合进来)

image.png

image.png

P63 项目_60_封面的校验使用

使用el-form的validateFields去校验封面图片的src是否为null,然后统一通过下方红色字来提示

P64 项目_61.0_发布文章调用接口_关闭对话框重置数据

各种Content-Type对应的参数传值:

  1. Content-Type: multipart/form-data 使用 new FormData()
  2. Content-Type: application/x-www-form-urlencoded 使用 new URLSearchParams()

关闭对话框重置,其实resetFields重置为初始值之后会触发校验(如果置空不了的项就会报红色字提示):

image.png

P65 项目_61.1_复习总结发布文章_讲解network调试如何查看请求体格式和参数

复习总结发布文章

讲解network调试

- P66项目_62_图片预览格式和发给后台的格式

v-html

打包发布

- P78项目_74_打包发布_概念介绍

把不属于浏览器识别的内容转化成html、css、js等等

- P79项目_75_打包发布_相对路径修改

vue.config.js

module.exports = defineConfig({
    publicPath: process.env.NODE_ENV === 'production' ? "./" : './',
})

- P80项目_76_打包发布_publicPath的使用

根据process.env.NODE_ENV使用不同配置

- P81项目_77_打包发布_dist瘦身的分析_把第三方包排除掉换成cdn链接

webpack打包排除依赖,换为在页面中通过cdn方式引入

  • 减少了自己服务器部署的前端代码体积
  • 把依赖的Vue的资源使用CDN的资源路径,把带宽压力转移
  • CDN是全量引入,对于客户端而言,加载的文件体积不会减少(而且,webpack打包可能还会优化掉用不到的依赖组件,CDN就没这个好处)

image.png

image.png

- P82项目_78_打包发布_dist瘦身

- P83项目_79_打包发布_cdn介绍

- P84项目_80_打包发布_cdn地址引入

image.png

- P85项目_81_打包发布_开发环境不排除第三方包

知识点:npm run dev,其实也是使用public/index.html这个文件作为入口(替换其中的id=app的节点)

开发环境不排除依赖,然后index.html文件中注释掉引入cdn即可 image.png

- P86项目_82_打包发布_总结2个知识点_2种环境自动识别

cdn资源的根据环境来自动引入:

配置webpack 插件 传入参数: image.png

在html中使用模板语言: image.png


- P87总结_1_新的软件准备

image.png

- P88总结_2_项目的工具准备

image.png

image.png

- P89总结_3_项目的架构设计

image.png

- P90总结_4_项目中git的管理使用

以下总结内容非常实用,只可意会不可言传,自己看去: