08.人力资源管理项目 -员工管理==>弹窗组件封装、部门选择树形控件、输入屏蔽、表单数据重置、dayjs优化时间渲染、删除功能中分页异常的处理
在弹窗准备的工作上,这里与组织架构处一致,我们任然将其独立出来成子组件,主要不要忘记对组件进行注册,然后在控制dialog的显示隐藏上使用一个变量来控制,再利用子组件中的‘确认’和‘取消’两个按钮通过.sync的方式来快速传值空值对话框的显示和隐藏。
添加员工的表单校验功能:重点在校验的几个属性 ref,model,rules,prop, 其中ref用于获取dom对象已经表单的兜底校验,model用来绑定表单数据,rules则是表单的校验规则,这些都要写在el-form中;而prop则是对应model绑定的表单数据的具体单元项,写在el-form-item中;同时还要用在具体的输入框中用v-model绑定表单数据。
聘用列表中label是显示给用户看的值,而value是实质上v-model绑定的值。
部门的渲染及选择,与组织架构中的相类似,任然需要使用到el-tree组件,利用表单的foucs方法来控制el-tree的显示和隐藏(foucs为获得焦点事件,与之对应的blur为失去焦点事件);在选择部门时有专门的node-click事件,要注意只有叶子节点可被选择,因此可以利用他们的children属性来判断是否为叶子节点;同时,由于表单输入框可修改,必须采取措施使其不可更改,因此需要采用watch侦听事件来避免手动输入数据(可以用字符串表达式的写法来监听对象里的某个属性)。
添加功能的完成及重置表单功能,很常规,调用接口提交数据完成添加,使用el-dialog的close事件重置表单。
时间处理,利用dayjs库可以快速实现。
删除员工功能,很常规,调用接口后更新页面数据即可,值得注意的是在处理删除最后一页仅有一项数据的情况下要进行特殊处理,使得页码page–。
10.员工管理-添加员工-弹窗准备
目标
- 创建弹窗和表单
- 点击新增员工显示
- 点击确定和取消关闭弹窗
讲解
分析
- 将弹框抽取成单独的组件
- 在父组件中使用 el-dialog 组件展示封装的弹框组件
- 在子组件中 form 组件实现页面的布局
实现-弹窗准备
- 新建添加表单组件,
src\views\employees\components\empDialog.vue
<template>
<!-- 表单 -->
<el-form label-width="120px">
<el-form-item label="姓名">
<el-input style="width:50%" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="手机">
<el-input style="width:50%" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="入职时间">
<el-date-picker style="width:50%" placeholder="请选择入职时间" />
</el-form-item>
<el-form-item label="聘用形式">
<el-select style="width:50%" placeholder="请选择" value="" />
</el-form-item>
<el-form-item label="工号">
<el-input style="width:50%" placeholder="请输入工号" />
</el-form-item>
<el-form-item label="部门">
<el-input style="width:50%" placeholder="请选择部门" />
</el-form-item>
<el-form-item label="转正时间">
<el-date-picker style="width:50%" placeholder="请选择转正时间" />
</el-form-item>
<el-form-item>
<el-button>取消</el-button>
<el-button type="primary">确定</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
name: 'EmpDialog'
}
</script>
<style lang="scss" scoped></style>
-
在父组件中引入
注意这里, 封装的是弹窗上面的表单组件, 而在父用的时候包一个el-dialog
import EmpDialog from './components/empDialog' export default { components: { EmpDialog } }
<!-- 新增员工弹框组件 --> <el-dialog title="新增员工"> <emp-dialog /> </el-dialog>
实现-弹窗出现
-
定义控制添加员工组件的展示的状态
data () { return { // ...其他省略 showDialog: false // 添加员工组件的展示 } }
-
用变量控制dialog是否显示
<!-- 新增员工弹框组件 --> <el-dialog title="新增员工" :visible.sync="showDialog"> <emp-dialog /> </el-dialog>
-
点击新增员工按钮, 绑定事件方法, 为了让弹窗出现
<el-button type="primary" size="small" @click="addEmpShowDialogFn">新增员工</el-button>
-
实现对应方法, 让弹窗出现
// 新增员工->点击出弹窗 addEmpShowDialogFn() { this.showDialog = true }
实现-弹窗消失
- 添加对话框的关闭功能
在子组件中抛出事件 ,来由于现在控制弹框显示与否的属性是在父组件中申明的,如果我们想点击取消或者确定按钮进行弹框关闭,我们需要通过触发自定义事件的方式进行子传父
。
但是我们前面讲过子传父, 双向绑定数据可以用.sync实现
- 在子组件中, 用$emit触发.sync语法糖绑定的带格式的事件
<el-form-item>
<el-button @click="addCancel">取消</el-button>
<el-button type="primary" @click="addSubmit">确定</el-button>
</el-form-item>
methods: {
// 点击取消按钮
addCancel() {
this.$emit('update:sDialog', false)
},
// 点击确定按钮
addSubmit() {
this.$emit('update:sDialog', false)
}
}
-
在父组件中,给子组件传入要绑定的数据参数, 在用.sync配合子组件update自定义事件, 改变父里Vue数据
<!-- 新增员工弹框组件 --> <el-dialog title="新增员工" :visible.sync="showDialog"> <emp-dialog :s-dialog.sync="showDialog" /> </el-dialog>
小结
-
这次我们把什么封装到了empDialog.vue组件里?
答案- 把整个表单部分放到某个组件里
-
子组件里确定和取消, 是如何关闭的弹窗的?
答案- 子组件内触发update格式的自定义事件, 外面要用.sync语法糖配合
11.员工管理-添加员工-表单校验
目标
完成添加员工弹框的表单校验
分析
- 在 data 中定义表单数据以及校验规则
- 在 template 中应用规则
- el-form : rules, model, ref
- el-form-item: prop
- 表单上v-model绑定数据对象
- 注意: 数据对象和规则对象的key和prop以及v-model值一致
- 手动兜底校验
- this.$refs.xxx.validate(valid => { })
实现
-
按后端接口要求,准备表单数据来定义数据项
key名建议和后台对应, 这样可以直接把对象发给后台
data() { return { // 添加表单字段 formData: { username: '', // 用户名 mobile: '', // 手机号 formOfEmployment: '', // 聘用形式 workNumber: '', // 工号 departmentName: '', // 部门 timeOfEntry: '', // 入职时间 correctionTime: '' // 转正时间 } } }
-
添加表单验证,定义验证规则
data() { return { // ...其他省略 // 添加表单的校验字段 rules: { username: [ { required: true, message: '用户姓名不能为空', trigger: 'blur' }, { min: 1, max: 4, message: '用户姓名为1-4位', trigger: 'blur' } ], mobile: [ { required: true, message: '手机号不能为空', trigger: 'blur' }, { pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' } ], formOfEmployment: [ { required: true, message: '聘用形式不能为空', trigger: 'blur' } ], workNumber: [ { required: true, message: '工号不能为空', trigger: 'blur' } ], departmentName: [ { required: true, message: '部门不能为空', trigger: 'change' } ], timeOfEntry: [ { required: true, message: '请选择入职时间', trigger: 'blur' } ] } } }
-
绑定表单数据和规则
<el-form ref="addForm" :model="formData" :rules="rules" label-width="120px"> <el-form-item label="姓名" prop="username"> <el-input v-model="formData.username" style="width:50%" placeholder="请输入姓名" /> </el-form-item> <el-form-item label="手机" prop="mobile"> <el-input v-model="formData.mobile" style="width:50%" placeholder="请输入手机号" /> </el-form-item> <el-form-item label="入职时间" prop="timeOfEntry"> <el-date-picker v-model="formData.timeOfEntry" style="width:50%" placeholder="请选择入职时间" /> </el-form-item> <el-form-item label="聘用形式" prop="formOfEmployment"> <el-select v-model="formData.formOfEmployment" style="width:50%" placeholder="请选择" /> </el-form-item> <el-form-item label="工号" prop="workNumber"> <el-input v-model="formData.workNumber" style="width:50%" placeholder="请输入工号" /> </el-form-item> <el-form-item label="部门" prop="departmentName"> <el-input v-model="formData.departmentName" style="width:50%" placeholder="请选择部门" /> </el-form-item> <el-form-item label="转正时间" prop="correctionTime"> <el-date-picker v-model="formData.correctionTime" style="width:50%" placeholder="请选择转正时间" /> </el-form-item> <el-form-item> <el-button @click="addCancel">取消</el-button> <el-button type="primary" @click="addSubmit">确定</el-button> </el-form-item> </el-form>
-
手动兜底验证
// 点击确定按钮 addSubmit() { this.$refs.addForm.validate(valid => { if (valid) { // 通过表单校验 this.$emit('update:sDialog', false) } }) }
-
效果: 点击取消关闭, 点击确定无法关闭
小结
-
表单校验相关代码放在index.vue还是empDialog.vue中?
答案- 在empDialog.vue中, 表单整体在里面, 于是在里面校验即可
-
elementUI表单校验需要注意哪些地方?
答案- el-form上绑定ref, form, rules这3个属性和值
- el-form-item上绑定prop值为表单和规则对象的key字符串
- 表单上用v-model关联页面数据, 值绑定到form指定的表单数据对象上
12.员工管理-添加员工-聘用列表
目标
实现添加员工表单聘用形式渲染
分析
- 聘用形式的数据直接从常量中取。这里的常量可以从
src\constant\employees.js
中获取 - EmployeeEnum.hireType 表示聘用形式数组对象
实现
- 在
employees/components/empDialog.vue
引入常量数据
// 导入需要枚举的导入
import EmployeeEnum from '@/api/constant/employees'
-
引用枚举对象中的 hireType 属性
data() { return { // ...其他省略 hireType: EmployeeEnum.hireType, // 聘用形式数据绑定 } }
-
渲染常量数据
注意:根据接口的要求,要传递的数据是聘用形式的编号,而不是中文描述,所以这里的设置 value 值必须是 id
<el-form-item label="聘用形式" prop="formOfEmployment">
<el-select v-model="formData.formOfEmployment" style="width:50%" placeholder="请选择">
<el-option
v-for="item in hireType"
:key="item.id"
:label="item.value"
:value="item.id"
/>
</el-select>
</el-form-item>
- 查看效果
小结
-
我们为何聘用形式, 不需要接口获取下拉列表数据?
答案- 因为这个无需接口, 我们需要传递1和2值给后台就可以
-
el-option的label属性和value属性作用是?
答案- label是标签上显示给用户看的文字, value是用户选择对应行绑定给v-model使用的值
13.员工管理-添加员工-部门渲染
目标
实现添加员工表单部门数据渲染
讲解
分析
员工的部门数据渲染和组织架构的数据一致,同时我们也需要使用树型组件的方式向渲染
- 发请求获取数据
- 对数据进行格式转换(数组转树)
- 绑定到 el-tree 组件
实现
- 在
views/employees/index.vue
主页面中, 导入获取部门数据渲染的 API 方法以及格式化树行组件的方法
// 引入api方法
import { departmentsListAPI } from '@/api'
// 树形结构的转换方法
import { transTree } from '@/utils'
-
在页面打开的时候,获取到树行组件数据的方法
export default { // ...其他省略 data() { return { // ...其他省略 treeData: [] // 存储部门列表(树结构) } }, created() { // ...其他 // 调用获取部门列表的方法 this.getDepartments() }, methods: { // ...其他 // 获取树行组件的数据 async getDepartments() { const res = await departmentsListAPI() if (!res.success) return this.$message.error(res.message) this.treeData = transTree(res.data.depts, '') console.log(this.treeData) } } }
-
传给表单内存组件
<!-- 新增员工弹框组件 --> <el-dialog title="新增员工" :visible.sync="showDialog"> <emp-dialog :s-dialog.sync="showDialog" :tree-data="treeData" /> </el-dialog>
-
在
views/employees/components/empDialog.vue
中props接收父传过来的树形结构的部门列表数据props: { treeData: { type: Array, default: _ => [] } },
-
用el-tree在表单el-form-item内, 输入框下面展示部门列表
<el-form-item label="部门" prop="departmentName"> <el-input v-model="formData.departmentName" style="width:50%" placeholder="请选择部门" /> <div class="tree-box"> <el-tree :data="treeData" default-expand-all :props="{ label: 'name' }" /> </div> </el-form-item>
-
最后的效果如下
小结
-
网络请求一般写在哪里?
答案- 其实页面组件或者封装弹窗组件都可以, 但是不要在复用组件里, 所以我们建议在页面组件使用, 然后传值进去, 写一起不乱
-
el-form-item表单域组件内可否放非表单组件?
答案- 可以, 它就在那一行显示
14.员工管理-添加员工-部门优化
目标
当用户在点击 部门
时才能够加载对应的树形组件
讲解
分析
- 在 el-input 上补充 @foucs 方法
- 当用户在 input 框上点击了,在对应的回调函数中获取部门信息
实现
-
在 el-input 上补充 @foucs 方法
<el-input v-model="formData.departmentName" style="width:50%" placeholder="请选择部门" @focus="departmentNameFocus" />
-
方法定义
// 部门输入框聚焦 departmentNameFocus() { }
-
定义变量, 通知el-tree是否展示
data () { return { // ...其他 showTree: false // 是否显示树形组件 } }
-
设置在el-tree身上
<div v-if="showTree" class="tree-box"> <el-tree :data="treeData" default-expand-all :props="{ label: 'name' }" /> </div>
-
在对应的事件处理程序中控制 tree 组件的隐藏和展示
methods: { // 选择部门-输入框获取焦点 departmentNameFocus() { this.showTree = true } }
小结
-
我们如何用输入框, 自己模拟一个下拉菜单?
答案- 输入框监听聚焦的事件, 然后控制下拉菜单是否出现
15.员工管理-添加员工-选择部门
目标
点选部门时,将选中的部门信息展示到部门输入框中
讲解
分析
- 给 el-tree 添加点击事件:node-click 事件,节点被点击时的回调
- 只有叶子节点(没有下级的节点)才能被选上注意
实现
- 给 el-tree 添加点击 node-click 事件
<div v-if="showTree" class="tree-box">
<el-tree
:data="treeData"
default-expand-all
:props="{ label: 'name' }"
@node-click="treeClick"
/>
</div>
- 在 node-click 事件对应的事件处理程序中进行业务逻辑的处理
methods: {
// 树形控件-点击事件
treeClick(data) {
// 如果当前部门还有子部门,则不能被选中
if (data && data.children) {
return
}
// 把当前选中的节点显示在 input 框中
this.formData.departmentName = data.name
// 隐藏 tree
this.showTree = false
}
}
小结
-
如何区分点击节点行是否还有下属节点?
答案- el-tree提供行节点的点击事件, 并且传入这行绑定的数据对象
- 根据对象是否有children字段和值, 如果有不能隐藏阻止代码往下走
16.员工管理-添加员工-部门输入屏蔽
目标
虽然点击tree组件能填充, 但是用户还可以输入修改, 会造成数据不一致性
讲解
思路
- 直接禁用输入框, 但是会变成禁用效果太丑
- 记录原来输入框的值, 检测v-model变化, 就把原来点击的值覆盖回去
实现
-
data定义变量, 保存点击tree组件时部门名字, 用于一会替换输入框内容
data() { return { // ...其他省略 clickDepartName: '' // 点击的部门名字 } }
-
在el-tree点击事件保存点击的部门名字
// 树形控件-点击事件 treeClick(data) { // ...其他省略 // 临时保存点击的部门名字 this.clickDepartName = data.name }
-
watch监听v-model表单对象值变化, 如果不一致, 直接覆盖
watch: { 'formData.departmentName'(newVal) { if (newVal !== this.clickDepartName) { this.formData.departmentName = this.clickDepartName } } },
小结
-
☆ watch如何监听对象里某个属性, 值的变化呢?
答案- 可以用字符串表达式的写法作为配置对象里的key
17.员工管理-添加员工-功能完成
目标
完成具体的新增员工功能
讲解
思路
- 封装实现添加员工功能的 API 方法, 并在组件中导入
- 当用户点击确认添加后,需要调用封装的 API 方法
- 添加员工成功之后,通知父组件:
- 关闭弹层
- 更新表格数据
实现
- 在
src/api/employees.js
中封装 api 新增员工
/**
* @description: 添加员工
* @param {*} data
* @return {*}
*/
export function addEmployeeAPI(data) {
return request({
method: 'post',
url: '/sys/user',
data
})
}
-
在子组件
src/views/employee/components/empDialog.vue
中, 点击确定按钮, 把表单的值传出去, 然后关闭弹窗// 点击确定按钮 addSubmit() { this.$refs.addForm.validate(valid => { if (valid) { // 通过表单校验 // 传值出去 this.$emit('addEmpEV', { ...this.formData }) // 关闭弹窗 this.$emit('update:sDialog', false) } }) },
-
在
src\views\employees\index.vue
父组件中导入 APIimport { addEmployeeAPI } from '@/api'
-
在父组件
src\views\employees\index.vue
中,监听子组件中发射的事件<!-- 新增员工弹框组件 --> <el-dialog title="新增员工" :visible.sync="showDialog"> <emp-dialog :s-dialog.sync="showDialog" :tree-data="treeData" @addEmpEV="addEmpFn" /> </el-dialog>
// 新增员工->事件触发 async addEmpFn(formData) { const res = await addEmployeeAPI(formData) if (res.success) { this.$message.success(res.message) // 提示后, 重新请求列表 this.getEmployeeList() } else { this.$message.error(res.message) } }
小结
-
添加员工实现的思路是?
答案- 在子组件内点击确定事件处理函数中, 子传父, 拷贝表单数据对象给父组件
- 在父组件内绑定自定义事件接收, 调用定义好的API接口发给后台, 给用户提示, 然后重新请求此页的列表
18.员工管理-添加员工-表单数据重置
目标
在弹层隐藏时,将表单数据恢复到之前的状态。
讲解
分析
在用户执行了如下操作,将弹层隐藏时,将表单数据恢复到之前的状态。
- 点击了取消、确定
- 点击了弹层中右上角的关闭
- 点击了ESC
- 点击了弹层的遮罩区域)
可以分开写, 很麻烦,但是有相同效果, el-dialog都会触发close关闭事件
实现
-
在父组件
src\views\employees\index.vue
中,监听close 事件<!-- 新增员工弹框组件 --> <el-dialog title="新增员工" :visible.sync="showDialog" @close="addEmpDialogCloseFn" > <emp-dialog ref="addEmpDialog" :s-dialog.sync="showDialog" :tree-data="treeData" @addEmpEV="addEmpFn" /> </el-dialog>
-
定义 close 事件处理程序, 设置ref, 获取到子组件表单对象, 用el-form组件对象的resetFields方法置空表单
// 新增员工->弹窗关闭事件 addEmpDialogCloseFn() { this.$refs.addEmpDialog.$refs.addForm.resetFields() }
小结
-
表单数据重置如何做的?
答案- 第一个$refs获取到子组件对象的this,然后第二个$refs获取到子组件范围内的el-form组件对象
- 目标获取到el-form组件对象, 调用resetFields方法, 清空表单
-
在什么时机, 清空表单的?
答案- 点击确定和取消, 都会触发el-dialog的close事件方法
- 在close事件方法中, 清空表单
作业(自己做)
员工原理-员工列表-时间渲染问题
模板
解决添加员工后时间渲染问题
讲解
分析
- 在渲染的时候将时间进行格式化
- 格式化的方式
- ☆ 使用第三方的库:dayjs 实现
实现
- 下载 dayjs 并导入到当前组件中
npm install dayjs --save
-
引入dayjs后使用, 然后创建方法准备使用
import dayjs from 'dayjs'
methods: { // 格式化入职日期的方法 formatTime(time) { return dayjs(time).format('YYYY-MM-DD') } }
-
改造页面结构
<el-table-column label="入职时间" prop="timeOfEntry"> <template v-slot="scope"> {{ formatTime(scope.row.timeOfEntry) }} </template> </el-table-column>
小结
-
时间处理的原理是什么?
答案- 把年月日的日期取出来, 拼接自己格式的字符串返回
员工管理-删除员工-功能完整实现
目标
实现删除员工的功能
讲解
分析
- 点击删除,弹出询问提示框
- 用户点击确认后,调用删除员工api,删除成功之后再去更新页面数据
- 用户点击取消后,给用户取消提示
实现
-
封装删除员工 api,在
src\api\employees.js
中补充一个api/** * @description: 删除员工 * @param {*} id 员工id * @return {*} */ export function delEmployeeAPI(id) { return request({ method: 'delete', url: `/sys/user/${id}` }) }
-
调整模板, 给删除按钮绑定点击事件和作用域插槽
<el-table-column label="操作" width="200"> <template slot-scope="scope"> <el-button type="text" size="small">查看</el-button> <el-button type="text" size="small">分配角色</el-button> <el-button type="text" size="small" @click="delEmp(scope.row.id)">删除</el-button> </template> </el-table-column>
-
删除的逻辑功能
methods: { // ...其他 async delEmp(id) { // 显示删除询问对话框 const delRes = await this.$confirm('此操作将永久删除该角色, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).catch(err => err) // 用户点击了取消,给用户进行提示 if (delRes === 'cancel') return this.$message.info('您取消了删除') const res = await delEmployeeAPI(id) if (!res.success) return this.$message.error(res.message) // 删除成功后的提示 this.$message.success(res.message) // 重新获取数据 this.getEmployeeList() } }
小结
-
删除员工实现的思路是什么?
答案- 删除按钮绑定点击事件, 传递要删除的员工id
- 调用接口, 成功后, 再重新获取页面数据
员工管理-删除员工-分页异常处理
问题
如果删除第4页的最一条数据之后,页面会显示不正常
讲解
原因
如果删除第 4 页的最一条数据之后,再发请求,还是求的第 4 页,而此时,已经求不到第 4 页的数据了。
分析
在删除成功之后,去检测一下,是否当前删除的是当前页最后一条数据,如果是,就把页码 -1,再发请求
代码
methods: {
async delEmp(id) {
// 显示删除询问对话框
const delRes = await this.$confirm('此操作将永久删除该角色, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).catch(err => err)
// 用户点击了取消,给用户进行提示
if (delRes === 'cancel') return this.$message.info('您取消了删除')
const res = await delEmployee(id)
if (!res.success) return this.$message.error(res.message)
// 删除成功后的提示
this.$message.success(res.message)
// 判断是不是最后一条数据
if (this.employeesList.length === 1) {
this.query.page--
if (this.query.page <= 0) {
this.query.page = 1
}
}
// 重新获取数据
this.getEmployeeList()
}
}
小结
-
删除员工_分页列表异常原因?
答案- 数组里最后一条数据, 页面空白了, 但是Pagination组件自己删除了最后一个分页标签
- 但是js中的变量不会被Pagination组件影响, 所以请求的参数还是之前最后一页的页码
-
删除员工_分页列表异常如何处理?
答案- Pagination自己变了, 但是JS里的参数, 我们需要写代码, 判断如果数组剩下最后一条了, 让页码-1再发请求即可