项目代码:https://github.com/jiang-zi/vue-event
所需的前置知识
- Vue 基础
- axios
- element-ui
- npm 与 模块化
- 能使用 npm 维护项目中的依赖包
- ES6 模块化语法
项目初始化
基于 vue-cli 初始化 Vue2 模板的项目
目的:为后面的项目开发做准备。
一 、创建项目 vue create 项目名称
二 、vue-cli 手动选择要安装的功能,选择第三个
- Default ([Vue 2] babel, eslint)
- Default (Vue 3) ([Vue 3] babel, eslint)
- Manually select features
三 、初始化 vue-cli 的核心步骤:
-
Manually select features
- (*) Choose Vue version
- (*) Babel
- ( ) TypeScript
- ( ) Progressive Web App (PWA) Support
- (*) Router
- (*) Vuex
- (*) CSS Pre-processors
- (*) Linter / Formatter
- ( ) Unit Testing
- ( ) E2E Testing
-
Choose a version of Vue.js that you want to start the project with (Use arrow keys)
-
Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)
-
Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
- Sass/SCSS (with dart-sass)
- Sass/SCSS (with node-sass)
- Less
- Stylus
-
Pick a linter / formatter config: (Use arrow keys)
- ESLint + Airbnb config
- ESLint + Standard config
- ESLint + Prettier
-
Pick additional lint features: (Press <space> to select,<a> to toggle all, <i> to invert selection)
- (*) Lint on save
- ( ) Lint and fix on commit
-
Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
- In dedicated config files
- In package.json
-
Save this as a preset for future projects? (y/N)
注册功能
路由实现组件切换
-
新建两个页面组件 在 views 下新建 Reg/Reg.vue 和 Login/Login.vue -
在 router/index.js 中配置路由规则 -
在 App.vue 中使用 router-view 组件配置路由出口 -
在浏览器输入 http://localhost:8081/#/login 或 http://localhost:8081/#/reg 实现切换
渲染注册表单
- 去 element-ui 官网复制 form 组件的前四行, 补上结束标签
- 根据需求完善数据项的设置
- 美化样式
注册表单校验
-
检查复制过来的表单组件中重要的属性是否存在
- model
- rules
- ref
- prop (form-item)
-
在 rules 对象中写校验规则(复制官网代码进行修改) -
注册按钮点击事件中做兜底校验
注册功能实现
- 通过兜底校验后发送 ajax 请求, 携带数据对象
- 获取服务器返回的结果后根据状态码进行判断, 并提示用户
- 注册成功后使用编程式导航跳转至登录页
登录功能
渲染登录页面和跳转注册
- 去笔记中直接复制(课后练习建议从头来一遍)
- 给
el-link 添加点击事件, 编程式导航跳转至 /reg
点击按钮发起请求
- 给登录按钮绑定点击事件, 做兜底校验
- 如果通过校验就发起请求
- 获取请求结果, 进行判断并提示用户成功或失败
存储 token 至 Vuex
- 在
store/index.js 中定义子模块及数据, 并开启命名空间 - 定义 mutation 函数, 用于修改 Vuex 中的 token
- 登录成功时触发 mutation 的函数, 并传递 token
token 持久化
-
手动 (自己实现)
- 在 mutation 函数中使用
localStorage.setItem() 存储 token - 在 state 初始化值的时候使用
localStorage.getItem() 取出 token -
插件 (自动实现)
安装该插件后, 会自动持久化存储 Vuex 中所有的数据, 如需进行进一步的配置, 请查看官网
- 下包
yarn add vuex-persistedstate - 导入
- 安装插件
登录成功跳转至首页
- 登录成功后使用编程式导航跳转至
/ 后台主页 - 配置路由规则, 新建
Main.vue 首页 - 将笔记中
Main.vue 的结构样式复制过来
退出登录
- 找到退出登录按钮, 给
menu-item 绑定点击事件 - 用户点击退出时使用
$confirm 确认框提醒用户是否真的要退出 - 用户确认退出时, 清空 Vuex 中的 token 并跳转至
/login 登录页
侧边栏菜单
渲染用户信息
- 去笔记中复制静态页面及 CSS 样式
- 在 Vuex 中定义
userInfo 数据 - 在
actions 中封装一个获取用户信息的函数 - 发起请求获取用户信息 (通过
context 可以获取到 state ) - 将获取到的用户信息传递给
mutations 进行修改 userInfo - 在
Main.vue 的 created 中使用 this.$store.dispatch() 触发 actions 函数执行 - 将数据导入
Main.vue (使用辅助函数) - 渲染数据
渲染侧边菜单布局
-
去官网复制 el-menu 的代码并进行学习
-
el-menu: 导航菜单容器
-
el-menu-item: 没有子菜单的项目
-
el-submenu: 有子菜单的项目 -
复制笔记中的样式进行美化
渲染侧边菜单数据
-
在 Main.vue 中封装 getMenus 函数, 在 created 中调用, 发起请求获取菜单数据并存入 data -
由于循环渲染的菜单项有两种类型, 所以需要使用 template 标签包裹 -
在 template 标签上写 v-for 循环渲染
- template 是虚拟标签, 不能绑定 key, 需要将 key 设置在子元素上
-
在两个子菜单标签上通过 v-if 进行判断具体显示哪个菜单项 -
使用插值表达式和 v-bind 动态渲染内容
-
el-submenu 还需要继续使用 v-for 渲染子菜单 -
开启路由模式方便后续开发
优化请求 token
-
在 main.js 中给 axios 添加请求拦截器 -
拦截时携带 token 给请求头
- 在 main.js 中可以直接使用 store 对象获取到 Vuex 中的数据
-
之前手动携带 token 的地方可以删除请求头配置
权限控制
未登录禁止访问首页 - 导航守卫
-
在 router/index.js 中配置全局前置导航守卫 -
判断条件有 2 个需要进行拦截:
- 本地没有 token
- 不是访问 /login 和 /reg 页面时
已登录但 Token 过期 - axios 的响应拦截器
- 添加 axios 响应拦截器, 判断状态码为 401 的情况
- 给用户友好的提示
- 跳转至 /login 页面并清空 token 和 userInfo
渲染首页
- 创建 views/Menus/Home/Home.vue 组件, 复制笔记中的代码
- 观察代码发现需要 echarts, 所以下载 echarts 包
- 希望访问 /home 时切换到该组件显示, 配置 Main 的子路由
- 在 Main.vue 中写一个 router-view 作为路由出口
- 给 Main 路由规则配置重定向
个人中心 - 基本资料
使用路由渲染组件
- 创建 views/Menus/User/UserInfo.vue 组件, 复制笔记中的代码
- 配置路由规则, 同 Home 组件
渲染表单
- 去官网找到 form 组件进行复制
- 根据需求修改成项目需要的样式
- 在 UserInfo.vue 中导入辅助函数并映射 userInfo 数据
- 在 created 中将 userInfo 赋值给 userForm, 需要使用浅拷贝, 确保两个对象互不影响
- 在 userRules 中添加表单校验规则
重置表单
- 给重置按钮绑定点击事件
- 调用表单对象的
resetFields() 方法
更新用户信息
- 点击提交进行兜底校验
- 校验通过后发送请求更新用户信息
- 获取结果进行判断, 并提醒用户
- 如果更新成功, 重新发起请求获取最新的用户信息
个人中心 - 更换头像
使用路由渲染组件
- 创建 views/Menus/User/UserAvatar.vue 组件, 复制笔记中的代码
- 配置路由规则, 同上
选择图片
- 添加文件选择框在按钮上方, 但由于文件选择框太丑, 所以将其隐藏
- 给选择图片按钮绑定点击事件, 触发文件选择框的点击事件
- 给文件框绑定 change 事件
- 当用户选择图片时触发 change 事件, 并获取 e.target.files
- 根据 files 的长度来判断用户是否选择了图片
渲染预览图片
-
使用 FileReader 将 File 对象转换为 BASE64 字符串 // 使用 FileReader 将 File 对象转换为 BASE64 并设置给 avatar
// 创建 FileReader
const fr = new FileReader()
// 读取 File 对象
fr.readAsDataURL(e.target.files[0])
// 绑定事件, 必须使用箭头函数, 因为要访问 data 中的数据
fr.onload = e => {
// console.log(e.target.result)
// 触发事件后赋值
this.avatar = e.target.result
}
-
转换为 BASE64 后将字符串赋值给 avatar -
准备两个 img 根据 avatar 的值进行 v-if 选择渲染 -
渲染完成后对上传按钮的 disabled 进行判断
更新用户头像
- 给按钮绑定点击事件, 发送请求携带 avatar 数据
- 根据响应的状态提示用户
- 重新获取用户信息
个人中心 - 修改密码
使用路由渲染组件
- 创建 views/Menus/User/UserPwd.vue 组件, 复制笔记中的代码
- 配置路由规则, 同上
表单校验
- 编写基础表单校验规则 (如果非常熟练可复制)
- 使用自定义校验规则进行两次密码相同校验
重置表单
- 给重置按钮绑定点击事件
- 调用表单对象的
resetFields() 方法
修改用户密码
- 给按钮绑定点击事件, 点击时进行兜底校验
- 校验通过后发起请求
- 获取结果并根据状态提示用户
- 重置表单
文章管理 - 文章分类
使用路由渲染组件
- 创建 views/Menus/Article/ArtCate.vue 组件, 复制笔记中的代码
- 配置路由规则, 同上
获取分类数据
- 在 created 中发起请求
- 获取数据判断结果
- 将数据存入 data 中
渲染表格
-
去 Element-UI 的官网复制表格代码 -
根据需求改造表格代码并渲染数据
- table-column 设置 type 为 index 就是序号列
-
修改样式注意权重问题
渲染添加分类 Dialog
- 去 Element-UI 的官网复制最基础的 dialog 代码
- 根据需求修改绑定的数据和样式
- 在 dialog 中添加一个 form 组件, 两者结合使用
- 数据绑定并新增校验规则
关闭时重置表单
- 给 dialog 绑定事件: closed
- 事件触发时调用表单的重置方法即可
添加文章分类功能实现
- 给添加按钮绑定点击事件, 事件触发时进行兜底校验
- 校验通过后发起请求将用户输入的内容提交给后台
- 获取响应结果, 根据结果提醒用户
- 重新发起请求渲染页面
- 关闭 dialog
渲染修改分类 Dialog
- 将添加分类的 dialog 完整的复制一份
- 根据需求修改或添加所有的文本和数据变量
点击编辑按钮让 dialog 显示 (先不考虑功能实现, 仅做展示)- 给编辑按钮绑定点击事件, 传入当前行数据 (作用域插槽: scope.row)
- 判断 id 是否为 1 或 2 并提醒用户不允许修改
- 将 row 浅拷贝给 editForm, 展示当前点击编辑的分类数据
- 将 editVisible 设置为 true (显示 dialog)
编辑文章分类功能实现
- 给确定按钮绑定点击事件, 事件触发时进行兜底校验
- 校验通过后发起请求将用户输入的内容提交给后台
- 获取响应结果, 根据结果提醒用户
- 重新发起请求渲染页面
- 关闭 dialog
删除文章分类
- 给删除按钮绑定点击事件, 传入 id
- 事件触发判断 id 是否为 1 或 2 并提醒用户不允许修改
- 发起请求给后台进行删除 (查询参数)
- 获取响应结果, 根据结果提醒用户
- 重新发起请求渲染页面
文章管理 - 发表文章
使用路由渲染组件
- 创建 views/Menus/Article/ArtList.vue 组件, 复制笔记中的代码
- 配置路由规则, 同上
弹出发表文章 Dialog
- 去 Element-UI 官网复制 Dialog 组件代码, 设置 fullscreen 属性变成全屏 dialog
- 绑定 :before-close 属性, 做关闭前的回调, 确保用户点击关闭不会直接关掉 dialog
- 当用户关闭时 this.$confirm 提醒用户, 是否确定关闭
- 如果确定关闭就关闭 dialog
渲染文章标题和文章分类表单
- 去 Element-UI 官网复制 Form 组件的代码
- 根据需求修改为我们想要的效果
- 双向绑定数据并设置表单校验规则
渲染文章分类数据
- 发送请求获取分类列表数据
- 使用 v-for 循环渲染 el-option
- 动态绑定 value 和 label
渲染富文本编辑器
- 下包 vue-quill-editor
- 按照官方文档导入并全局注册
- 使用组件, 并双向绑定数据
- 使用深度选择器修改样式 (min-height)
- 添加表单校验规则
渲染文章封面
- 去笔记中将静态结构复制到指定区域
- 添加 form-item
- 写 img 标签并修改样式
- 准备一个文件选择框, 将其设置为隐藏
- 准备一个按钮
用户选择图片
- 给选择封面按钮绑定点击事件, 用户点击时触发文件选择框的点击事件
- 给文件选择框绑定 change 事件
- 在 change 事件中使用事件对象获取用户选择的文件对象
- 判断用户选择的文件对象, 进行存储到表单或清空表单的封面图片
渲染预览图片
- 当用户选择图片后, 使用 FileReader 将图片对象转成 BASE64 字符串
- 设置给预览图片的 src
- 当用户取消选择后, 导入本地的默认图片, 将其设置给图片的 src
发布前准备工作
- 渲染两个按钮, 并绑定点击事件
- 事件触发时进行表单的兜底校验
- 通过校验后设置 state 为
'已发布' 或 '草稿' - 关闭 dialog 时清空所有表单数据及预览的图片
发布文章
- 使用 FormData 填装参数
- 发起请求, 携带 FormData
- 根据结果提醒用户
- 关闭 dialog
- 将来实现了文章列表功能后, 还需要重新发请求获取最新的文章列表数据
文章管理 - 文章列表
渲染列表数据
- 封装获取列表数据的函数, 在 created 中调用获取数据存入 data 中
- 去 Element-UI 官网复制 Table 组件进行数据渲染
- 发表文章完成后, 调用封装的获取列表函数, 重新渲染页面
格式化日期
- 下包 dayjs
- 引入到组件中
- 封装一个 formatDate 的函数
- 在函数中格式化日期后返回
- 使用作用域插槽获取日期数据, 调用函数, 传入日期数据并使用插值表达式渲染
分页功能
- 去 Element-UI 官网复制 Pagination 组件
- 根据需求进行修改 (q.pagenum / q.pagesize / total)
- 获取数据时将 total 存入 data 中
- 结合 current-change 和 size-change 事件实现分页功能
- 当前页发生变化时将 q 的 pagenum 重新赋值并发起请求
- pagesize 同理
筛选功能
- 渲染文章分类的下拉菜单, 进行双向绑定 q 的属性
- 当用户点击筛选按钮时触发点击事件
- 点击事件中将 q.pagenum 设置为 1 后重新发起请求渲染数据
重置筛选
- 给重置按钮绑定点击事件
- 事件触发时将所有请求参数清空
- 重新发起请求渲染数据
预览文章 - 获取文章数据
- 使用作用域插槽将文章标题改为 el-link 组件
- 给标题绑定点击事件, 并传入 id
- 事件触发时根据 id 获取文章详情数据, 存入 data 中
- 将 dialog 显示出来
预览文章 - 渲染数据
- 将获取到的数据使用插值表达式渲染到对应的区域
- 设置样式
删除文章
- 使用作用域插槽包裹 el-button
- 给 el-button 绑定点击事件, 并传入 id
- 事件触发时根据 id 删除文章
- 重新发起请求渲染文章列表数据
优化删除的操作
发起请求删除文章完成以后,做如下判断在渲染文章列表
this.$message.success('删除成功!')
// 如果在刷新数据之前,当前页的数据只有 1 条,
// 而且,当前的页码值 > 1,
// 则说明当前页已没有数据可显示,需要让页码值 -1
+ if (this.artList.length === 1 && this.q.pagenum > 1) {
+ this.q.pagenum--
+ }
// 重新发起请求渲染文章列表数据
this.initArtList()
打包发布
生成打包报告
-
打开 package.json 配置文件,为 scripts 节点下的 build 命令添加 --report 参数: {
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build --report",
"lint": "vue-cli-service lint"
}
}
-
重新运行打包的命令: npm run build 或 yarn bulid
-
打包完成后,发现在 dist 目录下多了一个名为 report.html 的文件。在浏览器中打开此文件,会看到详细的打包报告。
基于 externals 配置 CDN 加速
-
未配置 externals 之前:
- 凡是
import 导入的第三方模块,在最终打包完成后,会被合并到 chunk-vendors.js 中 - 缺点:导致单个文件的体积过大
-
配置了 externals 之后:
- webpack 在进行打包时,会把
externals 节点下声明的第三方包排除在外 - 因此最终打包生成的 js 文件中,不会包含
externals 节点下的包 - 好处:优化了打包后项目的体积。
初步配置 externals 节点
-
在项目根目录下创建 vue.config.js 配置文件,在里面新增 configureWebpack 节点如下: module.exports = {
// 省略其它代码...
?
// 增强 vue-cli 的 webpack 配置项
configureWebpack: {
// 打包优化
externals: {
// import导包的包名: window全局的成员名称
echarts: 'echarts',
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios',
dayjs: 'dayjs',
'element-ui': 'ELEMENT',
'vue-quill-editor': 'VueQuillEditor',
'vuex-persistedstate': 'createPersistedState'
}
}
}
-
在 /public/index.html 文件中,新增如下的资源引用:
<script src="https://cdn.jsdelivr.net/npm/echarts@5.2.2/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router@3.2.0/dist/vue-router.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuex@3.4.0/dist/vuex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.24.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dayjs@1.10.7/dayjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/element-ui@2.15.6/lib/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quill@1.3.7/dist/quill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-quill-editor@3.0.6/dist/vue-quill-editor.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuex-persistedstate@4.1.0/dist/vuex-persistedstate.umd.js"></script>
-
在 main.js 中注释掉 element-ui 的样式和 quill 的样式: // 1. 导入 element-ui 组件库的样式
// import 'element-ui/lib/theme-chalk/index.css'
?
// 2. 导入 quill 的样式
// import 'quill/dist/quill.core.css'
// import 'quill/dist/quill.snow.css'
// import 'quill/dist/quill.bubble.css'
-
在 /public/index.html 文件的 <title></title> 标签之后,引入需要的 css 样式:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.15.6/lib/theme-chalk/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/quill@1.3.7/dist/quill.core.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/quill@1.3.7/dist/quill.snow.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/quill@1.3.7/dist/quill.bubble.css">
配置路由懒加载 优化性能
参考 vue-router 的官方文档,进行路由懒加载的配置
配置路由懒加载的核心步骤:
-
运行如下的命令,安装 babel 插件: npm install --save-dev @babel/plugin-syntax-dynamic-import
-
修改项目根目录下的 babel.config.js 配置文件,新增 plugins 节点: module.exports = {
presets: ['@vue/cli-plugin-babel/preset'],
// 实现路由组件按需导入的 babel 插件
+ plugins: ['@babel/plugin-syntax-dynamic-import']
}
-
在 /src/router/index.js 模块中,基于 const 组件 = () => import('组件的存放路径') 语法,改造每个路由组件的导入方式。例如: // 路由懒加载 vue 异步组件技术 导入
const Reg = () => import('@/views/Reg/Reg.vue')
const Login = () => import('../views/Login/login.vue')
const Main = () => import('@/views/main/main')
const Home = () => import('../views/menus/Home/Home.vue')
const UserInfo = () => import('../views/menus/User/UserInfo.vue')
const UserAvatar = () => import('../views/menus/User/UserAvatar.vue')
const UserPwd = () => import('../views/menus/User/UserPwd.vue')
const ArtCate = () => import('../views/menus/Article/ArtCate.vue')
const ArtList = () => import('../views/menus/Article/ArtList.vue')
项目地址:https://github.com/jiang-zi/vue-event
|