# rz_biji **Repository Path**: ifercarly/rzbiji ## Basic Information - **Project Name**: rz_biji - **Description**: 人资项目记录 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2021-07-15 - **Last Updated**: 2021-08-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: 项目记录 ## README ## 0. 关于包管理工具 Yarn ```bash npm i -g yarn # 全局安装 yarn 这个工具 yarn -v # 查看版本号,能出来就证明安装成功 yarn # 安装 package.json 内部依赖所有包,相当于 npm i yarn add vue # npm i vue yarn add webpack -D # npm i webpack -D ``` ## 1. 前端如何处理跨域 反向代理:代理了真实的服务器,变成了 `webpack-dev-server` 创建的服务器和真实服务器之间发请求,服务器之间发请求是不存在跨域的,`webpack-dev-server` 拿到数据之后再给前端,前端和 `webpack-dev-server` 创建的服务器之间也是不存在跨域的 如果说你是用 `vue-cli` 创建的项目,只需要在 `vue.config.js` 这个配置文件中,修改 `devServer` 中 proxy 选项就可以了 其实 `devServer` 这个选项最终操作的是这个插件 `webpack-dev-server` ## 2. 登录的流程 ```vue handleLogin() { // 手动校验 this.$refs.loginForm.validate(async isOk => { if (isOk) { try { this.loading = true // 1. 有可能 push 之后展示的组件需要用到 Vuex 中的信息 // 2. 如果页面做了访问控制,如果不加 await,会先进行跳转,此时还没有 token,可能都跳转不了 await this['user/login'](this.loginForm) this.$router.push('/') } catch (error) { console.log(error) } finally { this.loading = false } } }) } ``` ## 3. 如何做界面访问控制 image-20210716102541591 ## 4. 获取用户信息在时机 1、第一种方式:在 NavBar 组件里面去触发获取用户信息的 action 2、第二种方式:在路由导航守卫里面去触发获取用户信息的 action ![image-20210716162350764](README.assets/image-20210716162350764.png) ## 5. 使用默认导出数据的几种方式 1、第一种方式 `sub.js` ```js export default (a, b) => a + b; ``` `test.js` ```js // sum 叫啥无所谓 import sum from './sub.js'; console.log(sum(1, 2)); ``` 2、第二种方式 `test.js` ```js // mod 叫啥无所谓 import * as mod from './sub.js'; // console.log(mod); // { default: (a, b) => a + b } console.log(mod.default(1, 2)); ``` 3、第三种方式 `sub.js` ```js export default (a, b) => a + b; ``` test.js ```js import { default as sub } from './sub.js'; console.log(sub(1, 3)); ``` 4、第四种方式 `sub.js` ```js export default (a, b) => a + b; ``` `test.js` ```js export { default } from './sub.js'; ``` `index.js` ```js import { default as sum } from './test.js'; console.log(sum(1, 2)); ``` 5、第五种方式 `sub.js` ```js export default (a, b) => a + b; ``` `test.js` ```js export { default as sum } from './sub.js'; ``` `index.js` ```js import { sum } from './test.js'; console.log(sum(1, 2)); ``` ## 6. TOKEN 的超时处理 ### 6.1 纯前端处理 ![image-20210717094046137](README.assets/image-20210717094046137.png) ### 6.2 响应拦截器里面根据后端返回的状态标识进行处理 ## 7. 弹出超时,代码的执行过程是怎样的? ![image-20210717101255799](README.assets/image-20210717101255799.png) 刷新,整个项目的代码会重新跑一次! 1、permission.js ![image-20210717103530300](README.assets/image-20210717103530300.png) 2、src\store\modules\user.js ![image-20210717103601933](README.assets/image-20210717103601933.png) 3、src\utils\request.js 请求拦截器里面如果正常返回内容,会先到响应拦截器的第一个参数,如果返回的是一个错误的 Promise,会先到响应拦截器的第二个参数 ![image-20210717103630623](README.assets/image-20210717103630623.png) 4、src\utils\request.js ![image-20210717103701928](README.assets/image-20210717103701928.png) 5、src\store\modules\user.js ![image-20210717103735803](README.assets/image-20210717103735803.png) ## 8. mixins 和 extends 执行顺序 ```vue ``` ## 9. 了解 Vue.extend ```html Document
``` 动态弹框案例 toast.js ```js import Vue from 'vue' import MyToast from './MyToast.vue' // 期望根据传过来的模板,生成构造函数 const MyToastConstructor = Vue.extend(MyToast) function $mytoast(show, msg, duration = 2000) { // 可以根据这个构造函数生成组件实例 const toastCmp = new MyToastConstructor({ el: document.createElement('div'), // 把生成的组件实例挂到 div 上 data() { return { show, msg } } }) // 把组件怼到页面上面 document.body.appendChild(toastCmp.$el) setTimeout(() => { document.body.removeChild(toastCmp.$el) }, duration) } export default { install(Vue) { Vue.prototype.$mytoast = $mytoast } } ``` `MyToast.vue` ```vue ``` main.js ```js import Toast from './toast' Vue.use(Toast) ``` App.vue ```vue ``` ## 10. array-to-tree ### 0. 将数组转成树 需求 ```javascript const arr = [ { id: 'a', pid: '', name: '总裁办' }, { id: 'b', pid: '', name: '行政部' }, { id: 'c', pid: '', name: '财务部' }, { id: 'd', pid: 'c', name: '财务核算部' }, { id: 'e', pid: 'c', name: '税务管理部' } ]; ``` 结果 ```javascript const arr = [ { id: 'a', pid: '', name: '总裁办' }, { id: 'b', pid: '', name: '行政部' }, { id: 'c', pid: '', name: '财务部', children: [ { id: 'd', pid: 'c', name: '财务核算部' }, { id: 'e', pid: 'c', name: '税务管理部' } ], }, ]; ``` ### 1. 递归 ```javascript const fn = (list, rootId) => { const arr = []; list.forEach(item => { if (item.pid === rootId) { const children = fn(list, item.id); if (children.length) { item.children = children; } arr.push(item); } }); return arr; }; ``` 拆分 ```javascript const fn1 = (list, rootId) => { const arr = []; list.forEach(item => { if (item.pid === rootId) { const children = fn2(list, item.id); if (children.length) { item.children = children; } arr.push(item); } }); return arr; }; const fn2 = (list, rootId) => { const arr = []; list.forEach(item => { if (item.pid === rootId) { const children = fn3(list, item.id); if (children.length) { item.children = children; } arr.push(item); } }); return arr; }; const fn3 = (list, rootId) => { const arr = []; list.forEach(item => { if (item.pid === rootId) { // 代码执行不会用到 fn4,所以没有定义 fn4 也不会出错 const children = fn4(list, item.id); if (children.length) { item.children = children; } arr.push(item); } }); return arr; }; ``` **调用 fn1 第 1 次循环** 1、第 1 次调用 fn1,传递`完整数据 list`,此时 rootId 为 "" 2、新建数组,循环 list,判断第 1 项的 pid 是否等于 "",发现相等,进入 if 3、调用 fn2,传递`完整数据 list`,rootId 为当前项的 id,就是 'a' 4、新建数组,循环 list,依次判断每一项的 pid 是否等于 'a' 5、发现没有 pid 等于 'a',所以不会进入 if,等循环结束,返回空数组给 fn2,然后赋值给 children 6、此时 fn1 函数调用时的第 1 次循环,把 item 装进 arr **调用 fn1 第 2 次循环** 1、循环 list,判断第 2 项的 pid 是否等于 "",发现相等,进入 if 2、调用 fn2,传递`完整数据 list`,rootId 为当前项的 id,就是 'b' 3、新建数组,循环 list,依次判断每一项的 pid 是否等于 'b' 4、发现没有 pid 等于 'b',所以不会进入 if,等循环结束,返回空数组给 fn2,然后赋值给 children 5、此时 fn1 函数调用时的第 2 次循环,把 item 装进 arr **调用 fn1 第 3 次循环** 1、循环 list,判断第 3 项的 pid 是否等于 "",发现相等,进入 if 2、调用 fn2,传递`完整数据 list`,rootId 为当前项的 id,就是 'c' 3、新建数组,循环 list,依次判断每一项的 pid 是否等于 'c' 4、发现第 4 项的 pid 等于 'c',进入 if(注意调用 fn2 的循环还没有走完!) 5、调用 fn3,传递`完整数据 list`,rootId 为当前项的 id,就是 'd' 6、新建数组,循环 list,依次判断每一项的 pid 是否等于 'd' 7、发现没有 pid 等于 'd',所以不会进入 if,等循环结束,返回空数组给 fn3,然后赋值给 children 8、此时 fn2 函数调用时的第 4 次循环,把 item 装进 arr,注意这个 arr 是 fn2 函数中的 arr 9、继续,发现第 5 项的 pid 等于 'c',进入 if 10、调用 fn3,传递`完整数据 list`,rootId 为当前项的 id,就是 'e' 11、新建数组,循环 list,依次判断每一项的 pid 是否等于 'e' 12、发现没有 pid 等于 'e',所以不会进入 if,等循环结束,返回空数组给 fn3,然后赋值给 children 13、此时 fn2 函数调用时的第 5 次循环,把 item 装进 arr,注意这个 arr 是 fn2 函数中的 arr 14、fn2 执行完毕返回的 arr 中就包含了最后 2 项,返回给 fn2 调用处 15、... **调用 fn1 第 4 次循环** 继续循环到第 4 项、第 5 项的 pid 都不等于 "",最终 fn1 函数返回前面收集好的 arr ### 2. array-to-tree ```javascript const arrayToTree = require('array-to-tree'); arrayToTree(arr, { parentProperty: 'pid', customID: 'id' }); // result is ok ``` ### 3. performant-array-to-tree 更建议用这个,怎么用,自己找答案 ```js this.list = arrayToTree(await getPermissionList(), { parentId: 'pid', // 设置嵌套的字段 rootParentIds: { '0': true }, // 设置根节点 dataField: null // 不要嵌套,children 里面直接是对象 }) ``` ## 11. 关于 Excel 的解析 xlsx:拿到上传的文件交给 xlsx 解析成具体的数据 ## 12. ES2020 动态导入 ```html Document ``` index.js ```js export const sum = (a, b) => a + b; ``` 注意:要在服务器环境下测试 ## 13. 图片上传组件 1、图片预览 ```vue ``` 2、根据 `fileList` 的长度控制上传按钮显示与否 ```vue ``` 3、展示本地图片 ```vue ``` 4、上传之前的校验 ```vue ``` 5、自定义上传操作 ```vue ``` 6、上传成功后展示腾讯云返回的图片地址 ```js export default { methods: { upload(params) { if (params.file) { cos.putObject({ Bucket: 'ifer-1253924894', // 存储桶 Region: 'ap-nanjing', // 地域 Key: params.file.name, // 文件名 Body: params.file, // 要上传的文件对象 StorageClass: 'STANDARD', // 上传的模式类型 直接默认 标准模式即可 }, (err, data) => { if (!err && data.statusCode === 200) { this.fileList = this.fileList.map(item => { if (item.uid === this.currentFileUid) { return { url: 'http://' + data.Location, upload: true } } return item }) } }) } } } } ``` 7、上传进度的处理 ```js export default { methods: { beforeUpload(file) { // #1 this.showPercent = true }, upload(params) { if (params.file) { cos.putObject({ // #2 onProgress: (params) => { this.percent = params.percent * 100 } }, (err, data) => { if (!err && data.statusCode === 200) { // #3 setTimeout(() => { this.showPercent = false // 隐藏进度条 this.percent = 0 // 进度归0 }, 2000) } }) } } } } ``` 8、展示【个人详情】中的员工头像和员工照片 ```js export default { created() { this.getPersonalDetail() }, methods: { async getUserDetailById() { this.userInfo = await getUserDetailById(this.userId) // 有 staffPhoto 并且去除左右空格后还是有的,排除了 ' ' 这种情况 if (this.userInfo.staffPhoto && this.userInfo.staffPhoto.trim()) { // 这里我们赋值,同时需要给赋值的地址一个标记 upload: true this.$refs.staffPhoto.fileList = [{ url: this.userInfo.staffPhoto, upload: true }] } }, } } ``` 9、保存信息的时候【带上照片】 ```js export default { methods: { async savePersonal() { const fileList = this.$refs.myStaffPhoto.fileList // 判断是不是有一个没有上传完成 if (fileList.some(item => !item.upload)) { this.$message.warning('您当前还有图片没有上传完成!') return } await updatePersonal({ ...this.formData, staffPhoto: fileList && fileList.length ? fileList[0].url : ' ' }) this.$message.success('保存基础信息成功') } } } ``` ## 14. 图片转二维码 `qr-code` ## 15. 自定义打印区域 `vue-print-nb` ## 16. 页面访问和权限控制 ![image-20210730085342592](README.assets/image-20210730085342592.png) ## 17. 按钮/功能级别的权限控制 ## 18. 全屏/切换主题 ## 19. 国际化(重点掌握 i18n) ## 20. 总结 1、背景 2、功能 3、技术栈:`vue-element-admin` 4、项目中的难点和亮点 5、解决方案 ![image-20210802113627147](README.assets/image-20210802113627147.png)