从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等知识:
P2 项目_02_项目_初始化
- 全局安装vue脚手架:
npm i @vue/cli -g - 创建vue项目:
vue create 项目名称,选中vue2,选中vuex、route路由、eslint、babel等插件 - 进入项目,清空模板内容,加入大事件相关资源点击获取,调整目录结构:
P3 项目_03_项目_ESLint介绍
ESLint的规范文档:
- 规范文档1: www.verydoc.net/eslint/cn/0…
- 规范文档2: standardjs.com/rules.zhcn.…
- 规范文档3: eslint.cn/docs/rules/
- vue规则: eslint.vuejs.org/rules/
P4 P4项目_04_项目_ESLint插件使用
vscode搜索插件:eslint
webstorm设置:
可以继承规则、更改规则:
项目根目录下的配置文件:.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对象,设置校验规则,添加自定义校验:
P12 项目_10.01_注册功能_点击事件和拿到数据
提交按钮,JS兜底校验,通过ref定位到对应form表单,调用其validate方法,return false会阻止按钮默认行为,并提示校验不通过的项。
P13 项目_10.02_注册功能_封装接口方法完成调用
在兜底校验回调函数中,判断校验通过后,调用API接口提交表单数据,
把回调函数改为异步async,然后直接从API接口结果中解构出响应对象,
一旦响应成功,给用户报提示,然后路由跳转到登录页。
P14 项目_11_登录页面_标签准备和校验_跳转页面
准备登录页面的表单内容和按钮,点击“去注册”可以通过路由跳转注册页面
在主页的路由中添加redirect重定向,未登录访问首页时自动跳转登录页
P15 项目_12_登录功能_实现
实现登录页面的API接口和拿到返回响应结果
P16 项目_13_token保存到vuex中
使用vuex全局状态管理,通过mutations方法来操作state(可追踪),而不是直接手动修改state
... mapMutations ( ['updateToken'] )
P17项目_14_vuex本地持久化配置
npmjs.com搜索vuex-persistedstate,提示这个插件过期不维护了,vue还有像pinia等其他方案可选;
P18项目_15_布局页面准备和跳转
分析主页布局页面的结构(头部,左边导航栏,右边内容区域),使用el-container容器布局,直接复制代码粘贴。
配置主页的路由跳转去掉重定向,加入主页component
P19 项目_16_退出登录
在组件身上绑定的事件名称都需要组件内部触发,如下解析:
elementUI的确认弹框,使用es6的promise异步返回用户点击确定还是取消,在then(确定)和catch(取消)中处理逻辑:
P20 项目_17_布局_获取用户信息
获取用户信息需要传token标识,通过状态管理vuex拿到state里面的token并放到请求的headers里头
(后面可以在全局axios拦截器添加token)
P21 项目_18_保存用户信息到vuex中
除了保存token,还需要把登录用户信息保存到vuex里(不用每次都请求获取),
mutations是同步操作状态(store.commit(mutation)),actions是内部含有异步操作的代码(store.dipatch(action))
actions可以通过mapActions(['method'])或者直接store.dipatch(action)调用
P22&P23
P23 项目_20_避免重复请求用户信息_退出登录清除用户信息
目前实现的功能中,退出登录重新登录,不刷新页面,不会重新请求用户信息
所以,把获取用户信息的触发放到全局路由守卫中
(其实,用户登录之后的token、用户信息的获取保存,session过期这些内容不应该参杂到业务需求逻辑代码中,而是在全局路由守卫或者API请求拦截器中,或者用一个独立的模块,来处理)
P23 : 内容就是用户退出之后清空当前用户的登录信息(token和用户信息等等一切登录用户相关的内容)
(个人认为,还是跟上面说的一样,要独立出来放到一个独立的模块中处理)
P24 项目_21_getters使用_铺设用户信息
P25 项目_22_侧边栏导航的组件标签准备
学会elementUI的使用:
- 查文档找组件
- 复制组件html代码和js代码
- 学会使用文档中说明的属性来设置达到自己的效果,比如导航菜单可以结合路由使用:
P27 项目_24_axios请求拦截器_统一携带token
跟上面 P22&P23 提到的权限控制拦截应该放到一个独立的模块的思想一致。
(但是一个生产系统前端是复杂的,不止处理token和用户信息,还有用户权限等等)
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(),页面就会卡住空白
P29 项目_26_axios响应拦截器_统一判断401做被动退出
用户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_首页组件创建和路由配置
首页的内容区域属于二级路由
一级路由需要以斜杆/开头,二级以下路由直接以名称开头
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),然后添加当前二级路由。
注意点:
从地址栏或者其他地方直接路由到某个二级路由,左侧导航菜单不会跟着改变,因此需要设置绑定默认激活菜单项为当前路由路径:
P34 项目_31_个人中心_基本资料调接口更新保存
- 添加formbiaodan
- 添加接口API
- 点击按钮,兜底校验,提交数据
- 成功后刷新状态管理store,按需跳转路由router等等
P35 项目_32_个人中心_基本资料重置按钮实现
调用elementUI el-form的resetFields()方法清空重置输入框
好像disabled的输入框内容不会重置
(resetFields()是把输入框内容重置为初始值,而初始值是在第一次渲染时的值,如果第一次渲染之前把值改了,那就是改了初始值(懒加载的问题))
P36 P37
文件input样式不好改,通过点击按钮触发文件input的选择文件,然后监听文件选择事件,
拿到文件后
- 通过URL转成内存中的文件地址,然后赋予给预览图片的src来显示
- 转成base64字符串,然后赋予给src
P38 项目_35_个人中心_更换头像_调用接口保存并让vuex更新
- 添加上传头像接口,把base64的图片当成参数
- 上传成功后,dispatch => vuex的action,更新用户个人信息中的图片
P39 项目_36_个人中心_重置密码_页面准备和路由
- 明确目标,准备标签、样式、校验
- 添加重置密码的二级路由
P40 项目_37_个人中心_重置密码_自定义校验
自定义校验:成功调用callback不传参数,失败传Error说明失败原因(会变成红色提示字)
P41 项目_38_个人中心_重置密码_完成功能
- 前端校验两次密码一样
- 请求后端接口校验原密码
- 成功后清除登录用户的信息,路由到登录页面
P42 项目_39_文章分类_页面组件和路由
添加文字分类页面,并添加该组件的二级路由
P43 项目_40_文章分类_铺设数据
请求后台接口,把文章分类列表数据 显示在列表页面
el-table 只需要设置 :data = 数据对象list,会自动循环每一行的数据展示
每列使用prop属性设置具体字段名,使用type=index 设置本列为序号列
P44 项目_41_文章分类_添加分类_对话框准备
使用el-dialog组件弹框展示增加文章分类form表单
- 介绍el-dialog组件可以设置的各种属性值
- :visable=变量,在父组件中改变变量来控制子组件的打开关闭
- :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等,如下:
作用于
P48 项目_45_文章分类_修改分类_区分后调用接口
同一个按钮区分 新增 和 修改
使用一个变量,或者判断id是否存在
P49 项目_46_文章分类_修改分类_dialog和form和回显共用的bug
一进来,分类列表显示后,直接点击修改,那么会展示修改的内容,
但是再点击新增后,看到的是修改的内容,而不是空白表单,那是因为:
- 点击修改按钮,visible=true显示弹框表单
- 因为是delay懒加载,所以这时候表单组件还没挂载
- 然后把修改的内容回显在表单中
- 等待同步代码写完后,这些操作全部放到一个队列中被一次性调用,
- 而,表单的重置按钮是触发重置回第一次初始的值,而第一次初始的值就是点击修改按钮后设置的回显值
- 所以等到点新增再点重置,就会重置为修改的回显值。
(vue会把多个操作放到队列中,然后等待下一次渲染时执行)
所以,显示表单时,先调用nextTick让组件的懒加载渲染出来,然后在回调函数中设置回显:
P50 项目_47_文章分类_删除分类_功能完成
通过接口删除id
然后重新渲染列表
文章分类到此完成!!
P51 项目_48_文章列表_组件创建和路由
组件页面的表头,配置路由
P52 项目_49_发表文章_对话框准备
发表文章,弹框dialog,点击关闭,弹出确认框,确认框返回一个promise对象
P53 项目_50_文章管理_发表文章_表单的2个准备
请求接口获取下拉列表数据,并回显到select的option中
P54 项目_51_文章管理_发表文章_富文本编辑器使用
使用富文本编辑器(使用npm引入富文本插件组件)
P55 项目_52_修改组件内的样式
关于 scoped:
- webpack打包会给每个scoped组件加上一个data-v-hash...的属性值
- 如果引用了子组件,那么子组件除了有自己scoped的data-v-hash...,还会给一个父组件的data-v-hash...
- 子组件如果用了slot,那么
- 子组件的根节点会给一个父组件的data-v-hash...
- 属于子组件所有节点会自带自己的一个data-v-hash...
- 子组件中写在父组件的slot节点(以及根节点)会带一个自定义的data-v-hash...
- (也就是说),子组件自己生成一个data-v-hash...,子组件在父组件的slot内容又生成一个data-v-hash...
关于 scoped 的样式:
- 所有scoped中的样式都会在后面加上[data-v-hash...]的属性选择器
- 根据上面webpack的生成规则,可以预见样式的作用范围
使用::v-deep
- 这个::v-deep 会把[data-v-hash...]提到前面来,比如
- [data-v-hash...]
(这里有空格).main ... { color:red; }
- [data-v-hash...]
- 因此,这个
::v-deep,会使得样式应用于子组件中除了根节点的其他元素节点,以及所有嵌套的其他子组件节点,层层穿透
P56 项目_53_文章管理_发表文章_封面标签准备和选择图片
标签准备
P57 项目_54_文章管理_发表文章_封面图片预览
图片预览
P58 项目_55_Vue代码里如何引入相对路径图片
意思总结:
对于html标签中的原生的资源路径,webpack打包时会统一把资源合并到一个文件夹,然后处理修改资源相对路径
而对于js变量中直接写的字符串路径,webpack不会去调整资源和该字符串路径的位置
因此,动态绑定引入的资源,要用import src from '../src.png'先导入进来,然后再当成一个相对路径去赋予给比如src
这样webpack打包会自行处理这个相对路径
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的规则,相当于整合进来)
P63 项目_60_封面的校验使用
使用el-form的validateFields去校验封面图片的src是否为null,然后统一通过下方红色字来提示
P64 项目_61.0_发布文章调用接口_关闭对话框重置数据
各种Content-Type对应的参数传值:
- Content-Type: multipart/form-data 使用 new FormData()
- Content-Type: application/x-www-form-urlencoded 使用 new URLSearchParams()
关闭对话框重置,其实resetFields重置为初始值之后会触发校验(如果置空不了的项就会报红色字提示):
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就没这个好处)
- P82项目_78_打包发布_dist瘦身
- P83项目_79_打包发布_cdn介绍
- P84项目_80_打包发布_cdn地址引入
- P85项目_81_打包发布_开发环境不排除第三方包
知识点:npm run dev,其实也是使用public/index.html这个文件作为入口(替换其中的id=app的节点)
开发环境不排除依赖,然后index.html文件中注释掉引入cdn即可
- P86项目_82_打包发布_总结2个知识点_2种环境自动识别
cdn资源的根据环境来自动引入:
配置webpack 插件 传入参数: