人资项目思路总结
- 输入框检验规则, 注意这里的手机号校验规则进行了封装
- 点击登录按钮
- 兜底校验, 通过后执行vuex中actions的postLogin方法, 传递获取到的token, 调用mutations中的updateToken给state中的token赋值(这里的token赋值是对本地cookies中token的提取)
- 并且这里使用cookies进行本地存取, 在vuex文件中导入utils的auth文件中设置对于token的方法
- (其中postLogin中的请求发送封装在api中, 并通过api中的request方法执行@/utils/request中请求命令)
- 在@/utils/request中设置请求基地址(这里可以直接设置成相对路径的形式 /api, 默认能将基地址设置成请求服务器的地址, 并且基地址能够在环境变量文件中设置, 通过process.env_调用环境变量)
- 数据脱壳问题, 可以通过响应拦截器中return response.data ,这样获取成功的数据会少一层包裹, 并且将响应的状态码中不是成功的状态码用Promise.reject()主动抛出, 可以在try catch方法中捕获错误, 实现axios的错误和后台设置的异常状态错误显示一致, 因为axios 只会抛出4和5开头的错误, 而后台主动设置的异常是2开头的
- 针对跨域问题, 可以通过在vue.config.js中的dev.server中进行代理服务器的设置
- 拦截器中的权限设置, 在发送请求的时候, 先判断本地是否存在token, 如果有token, 则对每次请求的请求头中进行权限设置(注意要设置成Bearer + 空格 + token)
- 登录成功, 进入主页
- 路由守卫设置, 前置路由守卫能够判断有无token的情况下跳转(逻辑为有token时, 如果去登录页, 则跳转首页, 如果不为登录页, 则放行; 无token时, 如果去白名单中路径则放行, 如果不为登录页, 则跳转至登录页) , 并且给前置后置首页设置跳转进度可视化
- 封装获取用户数据的api, 在vuex中调用api发请求进行存储, 在前置守卫中调用actions中发请求的方法(注意这里的dispatch需要设置await, 将异步请求同步, 不然会在还未获取用户数据的时候, 就执行了跳转, 此时界面渲染尚未完成; 同时还要注意这里的请求分为用户信息请求和用户基本数据请求, 需要先请求用户信息拿到useId之后, 再进行用户基本数据请求, 合并请求得到的两个对象, 并存入state中)
- 计算属性的设置, 在vuex中设置全局getters模块, 能够在全局获取模块中的变量
- 退出功能, 退出时清空userInfo和token, 跳转至首页
- 登录成功后进入指定的页面功能, 利用router.push(path:'/login', query:{return_url:指定路径}), 实现跳转指定页面; 退出后在url中保留退出前的路径功能
this.$router.push(
'/login?return_url=' + encodeURIComponent(this.$route.fullPath)
)
- token失效后跳转至首页, 并要能保留退出前页面的路径(注意这个是在响应拦截器中设置, 因为失效状态码是后台提供, 需要在拦截器中捕获, 这里要在响应拦截中通过location.hash拿到路径)
- 优化代码, 如果vuex的userInfo对象中的userId已经存在, 前置守卫拦截中不发请求, 如果没有则发请求
- 动态路由配置, 在router的index.js中设置动态路由参数和静态路由合并挂载(这里要注意动态路由的写法, 需要在访问如, layout 组件下建立加载新组件, 通过对子路由设置为空)
- 修改左侧菜单的svg图片和title, 设置页面标题(对document.title进行设置, 导入项目中封装的模块设置, 这里可以通过路由对象中的meta拿到title)
- departments 组件的实现, 在element拿到静态代码, 封装当前页面的请求的api, 在当前页面调用并渲染页面(注意这里后台响应的数据不够规则, 需要根据其中的pid进行处理, 通过老师的代码将平铺的数据转成树形数据)
- 点击添加子部门弹出对话框并发送请求(封装api 并按需导入, 注意对话框的关闭功能需要子向父传值, 并且取消其他默认关闭, 注意对象中属性与后面修改请求的数据保持一致), 对el-select 组件进行循环渲染
- 点击新增对话框中确定, 表单验证, 兜底校验, 封装api发送请求(注意请求需要传递一个对象, 需要对对象拼接pid ), 利用try catch 实现提示, 并且需要子传父修改showDialog 的关闭弹窗, 调用封装方法更新页面中结构树
- 点击编辑按钮, 弹出对话框, 封装api发送请求并自动渲染表格中数据, 点击确定按钮, 发送请求弹出提示框, 修改父组件中showDialog关闭对话框 (注意这里对话框的隐藏不会销毁组件, 因此需要用v-if手动销毁组件)
- 添加一级部门(注意一级添加的id为''), 复用添加对话框逻辑, 删除功能(如果不是原生标签, 就需要用对标签上的事件绑定native修饰符)
- 部门编码输入框表单验证(在添加框中, 要求不能和部门中任一部门编码重合; 在编辑框中, 要求不能和除了自己部门以外的任一部门编码重合), 移出事件触发验证函数, 利用从父组件传过来的id 通过map 方法拿到所有部门编码的数组, 如果是编辑框要通过filter 去除自己部门的编码, 最后进行验证
- 部门名称输入框表单验证(在添加框中, 要求部门名称不能和同级部门名称重合; 在编辑框中, 要求部门名称不能和除了自己以外的同级部门名称重合), 焦点移出事件触发验证函数, 添加框中, 利用从父组件中传过来的id, 拿到所有数据中pid 等于id 的对象并放入数组中, 此时的数组为点击部门的子部门数组; 编辑框中, 利用从父组件传过来的id, 找到id 对应的对象中的pid, 利用pid 来找到所有子部门并放入数组, 并且需要在数组中去除当前点击部门名称, 最后进行验证
- 公司设置页面, 拿到表格和分页的静态页面(注意查询参数的请求中, 要用params传参), 封装请求并在created 中发请求, 获取数据渲染页面, 点击删除按钮, 弹出提示框, 封装请求根据id 删除数据 (注意判断当页面数据剩一个并且page大于1时, 点击删除要page - 1); 点击新增弹出对话框, 点击确定发送请求(注意新增时bug 的处理, 当前页面数据满时, 新增数据要跳转至最后一页, 要先设置total++); 设置isEdit 变量, 点击编辑按钮拿到当前行的数据并进行回填(注意点击对话框关闭时要清空表单对象的数据并清空表单验证)
- 封装公共组件pageTools, 这里需要进行全局组件注册操作, 需要在component的index.js中的install 函数中全局注册组件, 并在main.js 中注册Vue.use 挂载插件, 然后就可以在全局使用组件(注意在全局组件中要设置组件名name, 不然报错看不懂)
- 员工管理页面, 拿到静态页面, 封装api发送请求并渲染页面(注意要对聘用形式进行数据处理, 在课程资料中有constant 文件, 在employee.vue 中进行数据转化, 并渲染到页面; 还有入职时间的处理, element中有个格式化时间的属性); 点击删除按钮, 根据id 删除当前行
- 新增员工功能, 封装对话框组件, 点击新增员工, 弹出对话框, 根据constant中文件渲染聘用形式, 点击部门输入框, 发送请求拿到平铺数据后转化成树形数据并渲染部门下拉菜单(注意树的下拉菜单有个事件 node-click, 能点击哪个渲染就拿到对象的名字), 点击菜单项能在输入框中获取对应名字; 新增数据后同样要注意修复分页功能的bug(在删除和新增的时候都有一个bug要修复)(同时还要注意在子组件中点击取消和确定, 都要进行子传父关闭窗口并渲染数据)
- Excel 导入功能, 点击employee.vue 页面中的 excel导入, 路由跳转, 先在employee 文件夹中创建empImput.vue 文件, 在静态路由中配置路由参数, 从花裤衩的项目中拿到excel导入的代码(注意其中tui-editor 包名需要修改的问题,去issues社区看, 并且注意xlxs 包版本不同的问题, 重新下载对应版本), 将uploadExcel 进行全局注册, 并在empimport.vue 中调用.
- 在empimport.vue 中对传入数据中文属性名转化成英文属性名, 要进行数据深拷贝处理(利用map 方法, 可以通过Object.keys 获取属性名, 再配合forEach 方法), 然后要对excel 数字转时间格式进行处理(去网上找处理代码, 放到utils的index.js中)
- Excel 导出功能, 从花裤衩项目中拿到代码, 注意要下两个包, 点击文件导出的时候, 执行hExport 由于路由懒加载返回的是一个promise , 所以可以改成同步代码的形式, 发请求拿到所有数据, 数据放入到格式转化的函数中处理; header 数组是需要拿到对象中的属性名, data 数组需要拿到对象中所有的属性值, 数据格式转化后传入export_json_to_excel中(这个是导入花裤衩源代码中的方法), 得到最终的Excel 文件
- 点击查看按钮, 利用动态路径传参, 将id传到用户修改详情页, 登录账户分页和个人详情分页逻辑一样, 都只需要进来渲染数据, 点击更新发送请求修改数据
- 封装并全局注册图片上传组件, 下载cos-js-sdk-v5 第三方包导入并创建实例, 将腾讯云存储库信息填入并上传图片, 利用element 中el-progress 标签组件实现上传进度可视化, 将图片上传组件引入个人详情页面, 注意这里对图片的操作需要进行子传父和父传子, 需要用到v-model 的原理(即在子组件中用value 接收url, 用input 事件传递url)
- 注册全局图片组件, 能够实现当后台的图片链接不能正常显示图片时 或者 后台没有图片时, 能够显示默认图片( element 中的el-image 标签能够找到), 封装好后在employee.vue 组件中引入
- 权限管理页面, 拿到静态页面代码, 点击最上面的添加按钮, 能够添加页面级权限, 点击表格中的添加按钮, 能够添加按钮级权限.(注意页面级type为1, pid为'0', 按钮级type为2, pid 为当前点击行的id) 点击编辑按钮和删除按钮, 各自完成其功能, 设置关闭事件, 关闭时清空数据并清空校验规则
- 权限设计板块, 用户分配角色, 封装点击员工管理中分配角色弹出的对话框组件assignRole.vue, 打开页面发送请求渲染el-checkbox-group 复选框, 注意这里的v-model 设置的是勾选复选框的label值, 所以复选框的label 要设置成id, 要显示的name 设置用插值表达式设置在双标签中; 数据回填功能, 点击分配角色按钮, 发送请求拿到id 数组并赋值给v-model 绑定的数组实现回填
- 角色分配权限, 封装点击员工管理中分配角色弹出的对话框组件assignPermission.vue, 打开页面发送请求拿到并转成树形数据渲染页面, 将树改成显示复选框状态, 绑定node-key="id", 就是说选中状态通过id 来表现, 通过
this.$refs.refTree.setCheckedKeys(请求拿到的id数组)回填数据, 点击页面确定按钮, 通过getCheckedKeys 方法拿到树中选中状态的id 数组, 用来发送请求 - 动态路由设置, 通过router 上的addRoutes(路由对象) 方法动态注册路由, 先在vuex 中存储静态路由对象数组, 通过mutations 中方法能够将动态路由数组与静态路由数组合并, 修改Sidebar\index.vue 中routes 方法(这个方法返回的路由对象最终生成在左侧菜单栏中), 然后在前置路由守卫中拿到用户信息请求后, 进行根据用户信息中的menus 字符串数组, 对所有动态路由对象数组进行过滤, 过滤后注册路由并存储到vuex 中, 以达到不用权限的用户的menus 来显示不同的左侧菜单栏 (注意要将404路由对象放在最后, 同时还要补上官方解决白屏问题的代码, 还有退出再进来时, 会重复注册动态路由, 要在退出时调用花裤衩中resetRouter 方法)
next({
...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
replace: true // 重进一次, 不保留重复历史
})
- 按钮级权限控制, 注册全局指令并在需要控制的元素上使用指令, 通过用户信息角色中的points 对元素中指令绑定的字符串进行匹配, 如果匹配上则显示, 匹配不上则不显示
- 首页, 拿到静态页面代码, 取用vuex中数据设置用户名, 封装全局日历组件并调用, 封装全局雷达图组件(从antv-g2 中拿到代码, 注意要下@antv/data-set @antv/g2两个包, 并且要注意代码要写在mounted 函数中)
- 国际化, 下载vue-i18n 包, 创建i18n 实例进行设置并挂载到vue 上, 封装国际化下拉菜单的组件, 通过下拉菜单中command="zh" 属性, @command="changeLanguage" 能够拿到点击菜单项中command的内容, 并通过this.$i18n.locale = lang 对配置进行设置, 实现语言切换
- 封装屏幕全屏切换功能组件, 下载screenfull 包, 实现点击全屏图标, 调用screenfull.toggle(), 网页全屏显示, 点击按钮实现字体图标切换
:icon-class="isFullScreen ? 'exit-fullscreen' : 'fullscreen'" - 在vue.config.js 中配置代码并实现打包自动删除日志代码
chainWebpack(config) {
config.optimization.minimizer('terser').tap((args) => {
args[0].terserOptions.co mpress.drop_console = true
return args
})
}
- 项目打包, 在vue.config.js中配置, 修改externals 对象并配置自定义cdn 对象, 并自定义production 生产环境的代码, 注入cdn配置到html 模板, 再用模板语法将css 和 js 引入到index.html 中
项目中出错总结
- 这里面的登录跳转的逻辑当后面有参数路径时就跳转到制定页面, 如果没路径时return_url为空, 就会跳转到首页
async doLogin() {
try {
// 在组件中调用带命名空间的action
// dispatch是异步的,需要加async await
await this.$store.dispatch('user/userLogin', this.loginForm)
// 登录成功,路由跳转
+ this.$router.push(this.$route.query.return_url || '/')
} catch (err) {
alert('用户登录,失败')
console.log('用户登录,失败', err)
}
}
- 基地址的设置, 项目中设置/api 就行, 因为所有的请求都需要以这个开头
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
})
- 前端拿到需求如何应对: 先评估能不能做(现在市面上有没有解决方案, 自己能不能解决, 如果说完成需求需要后台的配合, 要预留出后台写好功能的时间, 并且预留出正常写完功能后解决bug的时间)
npm install失败的分析与解决方案,以及修复完成的代码地址
本失败原因是由于tui-editor(富文本编辑器插件)更名造成的,现在已经更名为toast-ui/editor(以下第一步) 并且该插件还进行了文件名的更名(以下第二步)以及方法名的更名(以下第三步)
解决方案如下: 1.首先将package.json中的tui-editor那一行修改为"@toast-ui/editor": "^3.1.3",
2.进入\src\components\MarkdownEditor\index.vue文件,将他的所有import删除换成下面四行 import 'codemirror/lib/codemirror.css' import '@toast-ui/editor/dist/toastui-editor.css' import Editor from '@toast-ui/editor' import defaultOptions from './default-options'
3.把该页面(还是第二条中的文件)的getValue和setValue分别换成getMarkdown和setMarkdown 把页面中的所有tui-editor全部替换为@toast-ui/editor
4.保存文件,npm install 搞定
第一板块
vue-element-admin 后台管理系统---模板
通过git clone gitee.com/mirrors/vue… 下载框架(这个是集成方案vue-element-admin, 功能很全, 可以用来借鉴, 但不方便开发), 一般用的是基础版vue-admin-template
vue-admin-template 后台管理系统---基础版
文件查看
-
build 用来查看项目上线后的结果
-
dist 项目上线文件
-
mock 用来模拟接口, 提供虚拟数据(一般在后台接口还没有写好, 但前端需要数据的时候)
-
public 静态页面
-
src 所有的业务代码
-
tests 单元测试, 比如针对时间的测试(一般前端不怎么需要单元测试, 因为网页能直接呈现效果, 多用于后端)
- editorconfig 用来统一编辑器配置
- 三个环境变量: 开发, 生产, 测试
-
.eslintignore 有些文件不能用 eslint 约束语法, 比如打包后的混淆代码
-
.eslitrc.js 书写 eslint 书写约束规则
-
.gitignore
-
.travis.yml 持续集成(Travis CI)的配置文件(持续集成指的是只要代码有变更,就自动运行构建和测试,反馈运行结果。确保符合预期以后,再将新代码”集成”到主干。)
-
babel.config.js
-
jest.config.js 专门用来做测试的框架jest
-
jsconfig.json
-
package.json 项目的描述信息
-
postcss.config.js 后处理器postcss, 自动加前缀做兼容处理
-
vue.config.js 脚手架配置文件
src 中
├── src # 源代码
│ ├── api # 所有请求
│ ├── assets # 主题 字体等静态资源
│ ├── components # 复用组件
│ ├── icons # 项目所有 svg icons
│ ├── layout # 全局 布局组件
│ ├── router # 路由
│ ├── store # 全局 store管理
│ ├── styles # 全局样式
│ ├── utils # 全局公用方法
│ ├── vendor # 公用vendor
│ ├── views # views 所有页面
│ ├── App.vue # 入口页面
│ ├── main.js # 入口文件 加载组件 初始化等
│ └── permission.js # 权限管理
│ └── settings.js # 配置文件
熟悉这张图
sass语法
- 通过
$定义变量(可以定义函数, 比如) &为父选择器;@import '路径'引入sass文件,@include引用文件中的函数- 代码后需要加分号
table组件中发送请求过程
为了实现高复用性, 高维护性
mock 部分
- 在mock文件夹中创建一个js文件
- 在index.js中导入
注意:
- mock 是前端实现真接口 假数据
- 一般在开发阶段时使用,上线之后就不会用了
- 本地用: 在mock文件夹中设置; 线上用: 在fastmock网站进行设置
- 当后台接口写好之后, 就可以删掉
mock文件夹, 然后在main.js中删掉这段代码, 然后在删掉vue.config.js中第39行before: require('./mock/mock-server.js')
if (process.env.NODE_ENV === 'production') {
const { mockXHR } = require('../mock')
mockXHR()
}
准备后端服务器
- 下载mongodb 并安装, 注意要配置环境变量
- 从仓库中下载代码, 下包并运行
注意
-
数据库 mysql mongodb(No-SQL类似于js对象, 一般前端开发用得多)
-
用fastmock设置假接口, 用postman测试接口
-
注意在组件中的引用将@设置为以src为起始路径
第二板块
响应拦截器中主动抛出错误
axios 主动抛出的错误
- 这里是通过axios对象的create方法创建了一个 axios 实例
注意: 我们可以对实例进行传参并发送请求, 比如这里的 service 是一个axios实例可以设置参数, 可以直接对实例进行实例.(属性名:属性值})(可以分次进行参数设置)
const instance = axios.create({
baseURL:"http://localhost:3000"
})
// 使用instance发请求
instance({
url:"/posts"
})
// 或
instance.get("/posts")
- 通过if中的 return 返回的是try中的数据, else中return的是catch中的数据 (也就是说响应拦截器中可以通过return response抛出正常返回的数据, 通过return Promise(new Error)抛出错误时的信息)
环境变量
环境变量文件对比
| 文件名称 | 对应环境 | 说明 |
|---|---|---|
| .env.development | 开发环境 | 当运行npm run dev 的时候会以此文件为配置文件,这个文件中可以定义针对开发环境的环境变量 |
| .env.production | 生产环境 | 当运行npm run build:prod 的时候会以此文件为配置文件,这个文件中可以定义针对开发环境的环境变量 |
| .env.staging | 模拟生产环境 | 可以理解为production环境的镜像, 尽最大可能来模拟产品线上的环境(硬件,网络拓扑结构,数据库数据) |
文件中变量书写格式
- 设置 => 变量的设置必须以
VUE_APP_开头 (比如VUE_APP_BASE_API = '/prod-api') - 调用 => 环境变量文件中定义的变量能够在所有文件中调用, 通过
process.env.文件中的变量名调用 - 书写格式 => 用下划线加大写字母, 环境变量的文件中写注释用 # 开头
注意
- 就是说开发的时候运行.env.development, 项目上线的时候使用.env.production, 测试模拟的时候使用.env.staging (通过vue.config.js中的命令设置进入不同的环境)
- 环境变量是由node提供, 所有修改环境变量中的数据生效需要重启服务器
跨域问题
问题产生
解决方案
- cors 会在同源策略检查之前, 查看响应头中是否有
Access-Control-Allow-Origin : *(该头是指运行哪些客户端访问, * 表示所有如果当前前端项目在允许访问的范围内, 则放行不会出现跨域问题) - JSONP, 通过js代码发送请求, 就是在网页中script标签的src属性发送请求 (局限性: 只能发送get请求, 目前不用)
代理转发 (解决跨域问题, 不用后台帮我们设置请求头)
module.exports = {
devServer: {
// ... 省略
// 代理配置
proxy: {
// 如果请求地址以/api打头,就出触发代理机制
// http://localhost:9588/api/login -> http://localhost:3000/api/login
'/api': {
target: 'http://localhost:3000' // 我们要代理的真实接口地址
}
}
}
}
}
注意: 当没有设置基地址时, 请求的就是本地服务器 (比如baseURL: '/api' 等价于http://localhost:9528/api)
注意
- 线上的项目通过跨域的形式对网站进行保护的措施
- 最佳解决方案是: 将前端的项目部署在和接口同源的服务器上
- 跨域问题是浏览器出现的问题
登录功能的思路
-
表单验证(login/index.vue)
- utils/validate.js ---> validMobile
-
调用 api/user.js中封装好的api
- 找到axios实例:utils/request.js中获取axios实例
- 设置基地址,从环境变量中拼接
-
收集用户的参数,传给上一步中的api。(页面上收集页面的数据项名与接口中一致)
-
经过请求拦截器,添加请求头(添加token, utils/request.js)
-
代理转发(vue.config.js)
-
后端服务器要启动,mongoDB也要启动;后端才能收到请求,返回数据===这里也可以使用线上接口 -
经过响应拦截器的处理(utils/request.js)
- 判断当前操作是否成功,决定是否axios报错
- 简化获取有效数据的写法(脱壳)
-
得到api调用之后结果(login.vue)
-
保存token到vuex(store/modules/user.js)
-
token做持久化(utils/auth.js)
注意
- 项目中的输入框自定义校验规则(就是用 validator 属性来自定义校验函数), 尽量封装起来, 放在 validate.js 中
- 发送请求也可以用来封装
- 开发模式不会压缩混淆代码, 生产模式会压缩混淆代码
补充
- vscode 的立方体提示, 能够自动帮我们 import 方法 (比如说在当前组件中导入user.js中的方法, 可以在组件页面直接敲出方法名, 出现立方体提示后选择, 就会在上面自动弹出导入代码)
- cookies 也能存储数据, 存储上限为4k; localStorage 存储没大小限制
- 存取token的几种方式
-
localStorage
-
persistedVuex利用第三方包自动存取
-
cookies
-
第三板块
文件功能分布
- src中api 是封装页面中的请求
- components 是复用组件
- layout 是整个页面的布局组件 (index.vue)
- router 为路由设置 (index.js)
- styles 为页面的样式设置 (index.scss)
- utils 为 工具文件夹
- auth 能实现在cookies中的token的存取
- request 能执行发送请求功能, 并且能对请求和响应进行拦截
- views 为页面组件, 能直接操控显示在页面的dom元素
- main.js 是项目的入口文件, 在这里导入全局的样式文件, 第三方插件包
- permission.js 做权限控制功能, 能够设定路由前置后置守卫
- .env 为设定环境变量的文件
路由跳转传参
这种方式能实现路由自动转码 (推荐)
这种方式也能实现路由跳转传参, 但需要调用api转码
this.$router.push(`/login?return_url=${this.$route.path}`)
- $route.path 能拿到当前跳转的路径, 但不能拿到传递参数
- $router.fullPath 能拿到传递参数
token 失效
补充
- 数组的 includes 方法, 判断to.path是否在whiteList中
whiteList.includes(to.path) - 全局路由守卫有两个钩子, beforeEach 和 afterEach( afterEach 会在路由跳转之后执行)
4. 浏览器的encodeURI() 和 encodeURIComponent 能够解码编码浏览器url中乱码
- encodeURIComponent 所有特殊字符都可以转码
- encodeURI 可以转码中文, 但不能转码特殊符号(/ & :)
注意
-
在异步发送请求的时候注意要加上 await
-
要注意项目中不同文件夹模块负责不同任务, 因此当我们需要进行某一项功能时, 首先应该想到的是这个功能应该去哪个模块中执行 (比如说请求功能, 在页面组件中点击登录
传递发送请求的消息通知vuex中的actions, 然后调用 api 中封装的请求, 通过请求再向 request 请求文件中执行请求) -
这里使用展开运算符使两个对象进行合并
前几个板块项目重点总结
- 点击登录按钮发送请求, 对请求功能的模块化封装(涉及token的存取)
- 拦截器中数据脱壳并利用Promise.reject()实现错误主动抛出
- 路由守卫的设置(注意在有token的条件下的用户数据请求发送)
- 根据用户信息中的用户id来请求用户基本数据
- 全局计算属性设置
- 退出功能的实现并且实现退出再进入能回到原来的页面(路由跳转传参)
第四板块
项目中路由配置
- 注意this.$router.options.routes 能拿到路由规则的整个对象数组
- 在路由规则中设置了 hidden 属性, 利用这个属性进行左侧显示隐藏设置
require.context 的作用
用来
document.title = `人资 - ${to.meta.title}`
原生事件触发( .native 修饰符 )
给组件触发事件, 需要子组件内部触发
- 如果封装的组件, 是否可以绑定click事件, 得看子组件内部有没有触发click事件, 如果子组件内部没有触发click事件, 可以在父组件上加
.native修饰符, 其作用是将click 绑定给子组件的根标签 - 只有原生事件(click mouseenter ...) 能使用 .native
v-model 和 .sync 区别
.sync修饰符能够在同一个组件上设置多个, 而v-model 在同一个组件中只能设置一个()- v-model 双向绑定指令就是一个语法糖, 本质上做了两件事
- 给子组件绑定一个value属性
- 给子组件绑定一个input事件, 在事件处理函数中给变量赋值
-
区别
.sync的主要作用是用来能够在子组件中修改父组件传递过来的变量, 通过在子组件中通过this.$emit('update:变量名')执行
- .sync 是给组件用的 ; v-model 既可以给组件用, 也可以给表单元素用
注意
- 动态路由需要模块化处理
- 项目中组件模块化
- 前置导航守卫可以设置多个, 也就是说每次需要做新功能, 可以分开写模块化代码
补充
- 如果要合成两个对象或者两个数组, 可以通过
[...数组1, ...数组2]进行合并 - 在组件中用
<svg-icon icon-class="icon/svg中svg图片的文件名" />可以直接呈现文件中的图标 - 渲染组件可以看 template 标签或 render 函数, render 函数用来渲染虚拟DOM树
- window.document.title 能拿到网页窗口上的标题
第五板块
组件退出时手动销毁组件 方法
- 对话框组件关闭不会销毁组件, 因此需要手动销毁组件, 通过 v-if 绑定showDialog变量销毁 (推荐)
- 利用 refs 配合 nextTick() 在dom树渲染完成后发送请求
- 利用watch 监听id的变化发请求
注意
- v-for 中的value 应该和后台接口需要的数据保持一致 (比如dialog中渲染的时候绑定的时候是username而不是id)
- 所有的请求注意都要包一层try catch
- 时刻注意功能模块化, 书写的规范化(就是如果要实现什么功能, 就去对应的文件和地方书写代码, 多多看看大佬的源码, 学习别人好的书写习惯)
补充
2. 获取组件中方法, 使用refs 获取组件 ; 因为dom的更新是异步的, 此时子组件还未创建, $nextTick() 能在dom树更新之后拿到回调数据 (尽量不要调用子组件中方法, 后期维护困难)
第六板块
部门编码输入框的校验 和 部门名称输入框的校验
方法1: 这里形参使用了解构的办法, 解构每个item对象中的单个属性获取值操作, 需要用小括号包起来; 并且返回一个对象, 对象中同样在解构进行赋值操作; 同时还要注意这里用的是map, 因为map 会根据数组中的元素个数返回同样元素个数的数组, 正好符合这里的需求(需要挑出大对象中几个需要的项, 形成一个新的数组)
this.originList = res.data.depts.map(({ id, pid, code, name }) => ({ id, pid, code, name }))
方法2: 可以通过map 返回每个对象中的code 形成新数组, 再通过includes 方法判断value 是否在map 返回的数组中
注意: 这里要判断在编辑当前部门的时候, 将当前部门的编码排除在校验数组外
删除功能代码优化
注意: 所有的返回的Promise 对象都能用这种写法, 就不用try catch 来嵌套代码了 (promise 对象报错时都需要用catch 来捕获 )
补充
- 数组的方法中find 能拿到数组中符合条件的元素
- this.$comfirm 是一个Promise 对象
注意
2.
第七板块
Vue.use 安装插件
实际开发中开发插件并注册的方式
注意
- 在Vue 上挂载方法需要在 new Vue之前
- 这里的obj 就是作为 Vue 的插件
- 如果注册的插件是一个对象, 在对象中需要写一个 install 方法
- install 函数会自动执行
- install 函数中会传入 Vue 函数
注意
- 一般来说, 用回调函数的形式返回数据是异步代码, 也就是说只要碰到回调函数就可以考虑是不是可以写成同步代码不嵌套的形式
- 这里的操作就是说我需要在请求拿到真实数据之前, 先将本地的total 和page 更新, 使页面中分页渲染先执行, 再获取符合渲染页面的数据 (这是一种处理页面渲染和数据不同步的思路)
补充
- element中表单的方法validate, 返回的是一个Promise 对象
const valid = this.$refs.roleForm.validate()
if (!valid) return
- element中表单的方法validate, 会将第一次关闭后表单中的内容当做默认填充内容(这个功能不是太好用, 不过这个方法能够重置表单验证提示, clearValidate 方法也能重置表单验证)
4. 在main.js中全局注册组件, 并且注意PageTools.name 指的是组件中name 属性
设的值
import PageTools from '@/components/PageTools'
Vue.component(PageTools.name, PageTools)
第八版块
注意
- 以后工作的时候, 如果多个模块相似, 一定要在第一个模块尽可能的规范代码, 方便后面的书写能直接复用
- 这里要注意value-format 是在上传后台之前的处理, 将转化后的时间格式传给后台, 需要和后台配合; 同时还要注意value 要设置成item.id 不然就是将中文传给后台, 到时候渲染页面时, 看不到数据
-
动态路由表的设置是为了配合后面进行权限设置(只对特定权限的人开放特定的权限), 静态路由表是所有人都可以访问的表
-
用.catch 或 try catch 捕捉错误的功能有点区别, 用try catch 能够在捕捉错误时就不会执行成功时代码, 如果用.catch 捕捉错误后, 还会执行后面的代码
补充
- css 渲染和 dom 渲染没有联系, js 会阻塞dom渲染, 注意外链各种文件, js文件要放在body的最后(浏览器的加载机制)
- defer 会在dom加载完后再执行, 一定程度上相当于window.onload 的作用
- css 和 dom 的渲染, 各自的解析器将css 或 dom 解析成对象, 再合成渲染树
2. {ob: Observer} 表示响应数据, 一般定义在vue 的data 中的数据是响应式的, 原理是数据劫持( API: Object.defineProperty() ), 可以监听到对象的属性改变. (但vue2中类似 arr : [1,2,3] ,arr[0] = 'a' 是劫持不到的) 3. Vue3 响应式原理: proxy(代理) 4. 注意这里属性名用[] 可以解析变量, 这里使用reduce 方法是为了进行对象的累加和
第九板块
前后端交互 导入导出 Excel
Excel 导入(前端处理格式问题时)
文件导入的时候, 执行handleSuccess, 进行数据转化后, 拿到转化后的数据发送请求, 并跳转到上一级路由
Excel 导出(前端处理格式问题时)
点击文件导出的时候, 执行hExport 由于路由懒加载返回的是一个promise , 所以可以改成同步代码的形式, 发请求拿到所有数据, 数据放入到格式转化的函数中处理; header 数组是需要拿到对象中的属性名, data 数组需要拿到对象中所有的属性值, 数据格式转化后传入export_json_to_excel中(这个是导入花裤衩源代码中的方法), 得到最终的Excel 文件
图片存储
由于在本地服务器直接存储图片成本高, 一般公司将图片存在云端, 云端返回图片地址, 公司再将地址存在本地服务器中
element 中upload 标签(用来上传图片)
封装上传图片的组件
- 导入第三方并创建实例
// 下面的代码是固定写法
const COS = require('cos-js-sdk-v5')
// 填写自己腾讯云cos中的key和id (密钥)
const cos = new COS({
SecretId: 'AKID53oLekgOs8AyYtzrVu52mIZcVBesra4L', // 身份识别ID
SecretKey: '6ybz82h7HDuNbaKp62jBRVKuFLCoBJCr' // 身份秘钥
})
- 点击上传拿到文件对象, 并进行上传的操作
upload(res) {
if (res.file) {
// 执行上传操作
cos.putObject({
Bucket: 'web73-1311807434', /* 存储桶 */
Region: 'ap-nanjing', /* 存储桶所在地域,必须字段 */
Key: res.file.name, /* 文件名 */
StorageClass: 'STANDARD', // 上传模式, 标准模式
Body: res.file // 上传文件对象
}, (err, data) => {
console.log(err || data)
// 上传成功之后
if (data.statusCode === 200) {
this.imageUrl = `https://${data.Location}`
}
})
}
},
注意
- 这里的
= {}为默认值处理, 当没有传进来东西的时候, 就使用空的对象; 同样其中的multiHeader = [] 和 merges = [] 也是做默认值处理
- 这里通过动态路径传参, 就是说在跳转的时候必须同时传过去id, 在跳转后的页面可以通过 $route.params.id 接收传递过来的id
补充
-
$router.back() 能够跳转回上一级的路由
-
代码中2 可以代表to, 4 可以代表for
-
研究下解构, 赋值参数默认值
-
element 的这个标签
el-tab-pane能够实现, 点击不同的标签显示不同的内容
- element 中环形进度条标签
el-progress
第十板块
上传头像
在这里上传头像的组件是个人详情页中的子组件, 修改头像并显示的操作需要用到子传父, 进来页面回填数据需要用到父传子, 而这个双向传递的数据都是同一个数据, 因此和v-model 的作用相同, 所以根据v-model 的原理, 可以通过在父组件中绑定v-model, 并且在子组件中用接收v-model默认传过来的 value 变量, 在子组件获取到图片url 时, 通过$emit('input', 'value变量对应的数据')将数据传回父组件
RBAC 基于角色的权限控制
由于直接对用户进行权限分配很复杂, 在原来两层的前提下, 插入角色一层, 在角色层对不同权限进行封装, 再对不同的人分配不同的角色, 目的就是为了让整个结构变得更加清晰点(描述: 用户不会直接面向权限, 而是面向角色)
el-checkbox-group 复选框
注意这里的v-for 循环了所有的复选框, label 是要传给后台的数据, 并且能够在点击复选框的时候将label 中内容放入roleIds 数组中, 通过在标签中的差值表达式用来渲染页面文字
<el-checkbox-group v-model="roleIds">
<el-checkbox v-for="item in roles" :key="item.id" :label="item.id">{{ item.name }}</el-checkbox>
</el-checkbox-group>
prop 的传递是异步的
注意
- 权限分为 页面级权限 和 按钮级权限
- 注意这里三个按钮都会弹出同一个对话框, 但是每个按钮功能会不一样, 添加权限和添加按钮的区别在于, 能够进行修改pid和type 不一样, 所以要封装函数传递不同的参数, 点击编辑按钮时, 能够进行数据回填, 对话框关闭时要清空formData 数据, 可以通过在表单标签上的@closed 事件中清空formData数据和清空表单验证, 就不用每次点确定的时候再清空了
补充
- v-model 能够写在
input表单标签中, 并且能够写在组件标签上 (注意写在组件标签上时, 在子组件中要根据v-model 的原理进行接收value 和传递input 事件的操作) - less 和 scss 中的样式穿透
- el-image 标签能够处理图片异常情况, 即当捕捉图片不能正常显示或者没有图片的情况并设定默认图片
- el-table 树形结构的表格实现, 当 row 中包含
children字段时,被视为树形数据。渲染树形数据时,必须要指定row-key="id"。
第十一板块
tree 树形组件
属性
- default-expand-all 打开所有树分支
- node-key="id" 将树的复选框选中状态绑定id, 配合setCheckedKeys(res.data.permIds)方法, 进行复选框的数据回填
- show-checkbox 使树节点附有复选框的功能
- check-strictly 取消默认的全选反选功能
方法
-
this.$refs.refTree.
setCheckedKeys(res.data.permIds)这个方法用来回填树的复选框 -
this.$refs.permissionTree.
getCheckedKeys()通过获取树中复选框绑定的node-key 中内容(为一个数据)
多选框回填 (这里是根据list的数据中的id 作为key, 根据请求数据中permIds 匹配id 进行回填)
- 给tree补充属性node-key
<!-- 权限点数据展示 -->
<el-tree
ref="refTree"
:data="permissionData"
:props="{ label: 'name' }"
:default-expand-all="true"
:show-checkbox="true"
:check-strictly="true"
+ node-key="id"
/>
- 调用setCheckedKeys
// 获取角色现有的权限
async loadRoleDetail() {
const res = await getRoleDetail(this.roleId)
console.log('获取角色现有的权限', res.data.permIds)
// 回填
this.$refs.refTree.setCheckedKeys(res.data.permIds)
}
动态添加路由
用router.addRoutes(数组) 方法实现动态添加路由, 方法中放入的是数组 (数组中的每个模块都是在配置路由规则, router.addRoutes(asyncRoutes) )
自定义指令
这是全局注册的方式, insert 函数有两个参数, 第一个参数能拿到触发命令的 dom 元素, 第二个参数能拿到一个对象, 对象中的value属性, 能拿到命令上绑定的值
权限控制-显示左侧菜单栏
promise 还能够这样使用
动态路由筛选
这里是基于asyncRoutes 对象数组 根据 请求用户基本数据中的menu 进行过滤对象数组, 并将静态路由合并
// 解决刷新出现的白屏bug
next({
...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
replace: true // 重进一次, 不保留重复历史
})
- 注意这里的await 能够拿到vuex 中promise 对象return 的值
- 这里的asyncRoutes 是所有的动态路由对象
- 过滤是根据menu 中的字符串 与 asyncRoutes 中对象的 name 进行比较, 如果有就保留
- 404网页注意要放在路由注册的最后一个
- addRoutes 方法能够注册动态路由
- next 代码团是为了解决白屏问题
按钮级权限控制
通过自定义指令实现按钮级权限控制, 能让不同的人赋予不同的小功能显示或隐藏
注意
- 页面中点击按钮弹出的对话框, 如果说对话框中内容非常简单并且不能被复用, 那么就不用另外再封装组件, 直接就在当前页面写; 但如果说一个功能复用性强 或者说 功能多到代码堆砌得很多的话, 那么就另外封装组件
第十二板块
国际化
要下载vue-i18n 的包并创建实例, 导出实例后, 在main.js 中挂载到vue 上, 自定义的变量能够通过
$t.('配置对象中的变量')
// 进行多语言支持配置
import Vue from 'vue' // 引入Vue
import VueI18n from 'vue-i18n' // 引入国际化的插件包
import locale from 'element-ui/lib/locale'
import elementEN from 'element-ui/lib/locale/lang/en' // 引入饿了么的英文包
import elementZH from 'element-ui/lib/locale/lang/zh-CN' // 引入饿了么的中文包
Vue.use(VueI18n) // 全局注册国际化包
// 创建国际化插件的实例
const i18n = new VueI18n({
// 指定语言类型 zh表示中文 en表示英文
locale: 'zh',
// 将elementUI语言包加入到插件语言数据里
messages: {
// 英文环境下的语言数据
en: {
...elementEN
},
// 中文环境下的语言数据
zh: {
...elementZH
}
}
})
// 配置elementUI 语言转换关系
locale.i18n((key, value) => i18n.t(key, value))
export default i18n
注意
<el-dropdown trigger="click" @command="changeLanguage">
<div>
<svg-icon style="color:#fff;font-size:20px" icon-class="language" />
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="zh" :disabled="'zh'=== $i18n.locale ">中文</el-dropdown-item>
<el-dropdown-item command="en" :disabled="'en'=== $i18n.locale ">en</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
- 下拉菜单的@command 事件能够拿到下拉选项中command 绑定的值
- 并且通过
$i18n.locale能够拿到i18n 创建实例对象配置中的locale 的值
全屏切换
- 下载 screenfull 的包, 然后调用screenfull.toggle() 能够实现页面全屏切换
- 通过对screenfull 实例注册 onchange 事件能够在屏幕大小改变时, 能通过isFullscreen 属性拿到是否全屏的布尔值
screenfull.on('change', () => {
// console.log('当前是否是全屏', screenfull.isFullscreen)
this.isFullScreen = screenfull.isFullscreen
})
补充
- 通过前后端协作实现无感刷新, 设置两个 token, 当短期token 失效后, 客户端向服务器再次发送请求, 重新获取两个 token 和 用户信息
注意
- vue.config.js 中的publicPath 设置为'./' 时是为了在打包文件后能够直接打开dist下的index.html, 如果是'/' 的话就不能打开
第十三板块
- 项目中需要首先需要打包这三个包
- 将externals 对象拿出来单独处理, 并设置cdn 对象
let externals = {}
let cdn = { css: [], js: [] }
const isProduction = process.env.NODE_ENV === 'production' // 判断是否是生产环境
if (isProduction) {
externals = {
/**
* externals 对象属性解析:
* '包名' : '在项目中引入的名字'
*/
'vue': 'Vue',
'element-ui': 'ELEMENT',
'xlsx': 'XLSX'
}
cdn = {
css: [
'https://unpkg.com/element-ui/lib/theme-chalk/index.css' // element-ui css 样式表
],
js: [
// vue must at first!
'https://unpkg.com/vue@2.6.12/dist/vue.js', // vuejs
'https://unpkg.com/element-ui/lib/index.js', // element-ui js
'https://cdn.jsdelivr.net/npm/xlsx@0.16.6/dist/xlsx.full.min.js', // xlsx
]
}
}
- 配合之前设置的cdn 对象中的css 和 js 设置, 注入到index.html 中
+ config.plugin('html').tap(args => {
+ args[0].cdn = cdn // 配置cdn给插件
+ return args
+ })
- 通过模板语法实现在index.html 上注入css 和js
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title>
<!-- 引入样式 -->
+ <% for(var css of htmlWebpackPlugin.options.cdn.css) { %>
+ <link rel="stylesheet" href="<%=css%>">
+ <% } %>
<!-- 引入JS -->
+ <% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
+ <script src="<%=js%>"></script>
+ <% } %>
</head>
当在router 实例中 设置mode 为history 时出现的页面访问问题 及 解决
要在koa 服务器的app.js 中设置 (将每次刷新的操作)
const Koa = require('koa')
const serve = require('koa-static');
const { historyApiFallback } = require('koa2-connect-history-api-fallback');
const app = new Koa();
// 这句话 的意思是除接口之外所有的请求都发送给了 index.html
app.use(historyApiFallback({
whiteList: ['/api']
})); // 这里的whiteList是 白名单的意思
app.use(serve(__dirname + "/public")); //将public下的代码静态化
app.listen(3333, () => {
console.log('人资项目启动')
})
注意:
- 刚打包的项目index.html 不能打开是因为有跨域问题
补充
-
所有网页默认请求为index.html , 就是说这里地址如果不写index.html 也会请求到同样的内容
-
请求时# 后面的内容不会发送给后台, 也就是说如果没有# 就会向后台发送请求 (项目中对router 实例设置mode 为history 就是去掉了# , 使原来不请求服务器的操作变为需要请求的操作)
-
请求过程中中间件