iHRM人力资源 - 组织架构
文章目录
- iHRM人力资源 - 组织架构
- 一、展示数据-树形组件
- 1.1 组件说明
- 1.2 树组件自定义结构获取作用域数据
- 1.2.1 说明
- 1.2.2 页面代码
- 1.2.3 获取组织架构数据-api
- 1.3 效果图
- 1.4 修改树形结构bug
- 二、添加子部门
- 2.1 表单弹层
- 2.1.1 下拉菜单点击事件
- 2.1.2 封装弹层组件
- 2.2 表单结构及校验
- 2.3 部门负责人数据
- 2.4 记录部门
- 2.5 确认功能
- 2.6 取消功能
一、展示数据-树形组件
树形组件:用层级结构展示信息,可展开或者折叠
使用element-ui组件
https://element.eleme.cn/#/zh-CN/component/tree
1.1 组件说明
<template><!--为什么这里写container和app-container? 因为我们最初的时候导入过样式--><div class="container"><!--在此容器上加了边距(上下30px,左右140px)--><div class="app-container">组织架构<!--展示树形结构--><!--此处正好是标签的属性名叫props,和vue框架中组件传递数据不是一个--><!--default-expand-all属性表示默认将所有的树形结构打开,完整写法是 :default-expand-all=“true” (一定带冒号)--><el-tree :data="depts" :props="defaultProps" default-expand-all></el-tree></div></div>
</template><script>
export default {name: 'Department',data() {return {// 数组属性depts: [{ name: '传智教育', children: [{ name: '总裁办' }, { name: '行政部' }, { name: '人事部' }] }],defaultProps: {// 当前层级数据(在这里找此层级的显示数据)label: 'name',// 子层级(其实就是层级结构,在这里找子节点)children: 'children'}}}
}
</script><style scoped>
.app-container {padding: 30px 140px;
}
</style>
效果图
1.2 树组件自定义结构获取作用域数据
1.2.1 说明
我们其实要实现下面的这种树形结构,在右侧还有操作地方,但是我们在1.1中只展示了名称没有显示右侧
因为element-ui中树形结构默认只显示节点的名称,如果我们想树形结构左边有内容右边也有内容的话,就需要对树形结构进行自定义
我们这个地方需要再使用一个element-ui的行和列组件
如下图所示,我们向实现这种结构
行 el-row
列 el-col
其实span默认是24份,我们分配每列的空间的话,分配span值即可
1.2.2 页面代码
我们代码要实现下面这种形式
<template><!--为什么这里写container和app-container? 因为我们最初的时候导入过样式--><div class="container"><!--在此容器上加了边距(上下30px,左右140px)--><div class="app-container">组织架构<!--展示树形结构--><!--此处正好是标签的属性名叫props,和vue框架中组件传递数据不是一个--><!--default-expand-all属性表示默认将所有的树形结构打开,完整写法是 :default-expand-all=“true” (一定带冒号)--><el-tree :data="depts" :props="defaultProps" default-expand-all><!--v-slot标签只能使用在template标签上--><!--这个data数据其实是el-tree给的,下面的template模板会不断的去循环,有多少个节点就会循环多少次,把每一个数据都塞到了data里面--><template v-slot="{data}"><!--节点结构。使用el-tree组件的插槽--><!--会读取这个节点结构并循环渲染到页面上--><!--我们如果把type布局模式设置为"flex",就可以用厚民安的两个属性,justify="space-between"表示两头对其,align="middle"表示垂直居中--><el-row type="flex" justify="space-between" align="middle" style="width: 100%;height: 40px"><!--一行里面有两列,第一列是组织名称,第二列是管理员和操作(下拉菜单)--><el-col>{{ data.name }}</el-col><!--:span="4"一定带着冒号,因为人家想要一个数字类型--><el-col :span="6"><span class="tree-manager">{{ data.managerName }}</span><!--下拉菜单组件--><el-dropdown><!--显示区域内容--><span class="el-dropdown-link">操作<i class="el-icon-arrow-down el-icon--right"></i></span><!--下拉菜单的选项--><el-dropdown-menu slot="dropdown"><el-dropdown-item>添加子部门</el-dropdown-item><el-dropdown-item>编辑部门</el-dropdown-item><el-dropdown-item>删除</el-dropdown-item></el-dropdown-menu></el-dropdown></el-col></el-row></template></el-tree></div></div>
</template><script>
import { getDepartment } from '@/api/department'
import { transListToTreeData } from '@/utils'export default {name: 'Department',data() {return {// 数组属性depts: [''],defaultProps: {// 当前层级数据(在这里找此层级的显示数据)label: 'name',// 子层级(其实就是层级结构,在这里找子节点)children: 'children'}}},// 页面初始化的时候会调用这个函数created() {// 获取部门数据,这个方法是下面methods里面的,不是api里面的this.getDepartment()},methods: {async getDepartment() {// 下面这个方法是import导入的api请求方法const result = await getDepartment()// 但是我们获取到的数据是列表的形式,没有层级结构,我们要使用递归的方式完成树形结构this.depts = transListToTreeData(result, 0)}}
}
</script><style scoped>
.app-container {padding: 30px 140px;
}.tree-manager {width: 50px;display: inline-block;
//margin: 10px; margin-right: 50px;
}
</style>
1.2.3 获取组织架构数据-api
- 封装获取组织架构的API
// 引入封装的axios工具
import request from '@/utils/request'/*** 获取组织架构数据*/
export function getDepartment() {// request发送登录请求,会得到一个promise结果并将其返回return request({// 请求地址url: '/company/department',// 请求方式method: 'GET'// 请求参数,但是这里没有请求参数// data: Data// 在ES6中上面data: Data可以简写为 data})
}
- 初始化后调用
// 页面初始化的时候会调用这个函数created() {// 获取部门数据,这个方法是下面methods里面的,不是api里面的this.getDepartment()},methods: {async getDepartment() {// 下面这个方法是import导入的api请求方法const result = await getDepartment()// 但是我们获取到的数据是列表的形式,没有层级结构,我们要使用递归的方式完成树形结构this.depts = transListToTreeData(result, 0)}}
- 赋值数据
这里我们要使用递归的方式将后端传输过来的列表数据变成树形结构
下图所示的情况
-
首先分析数据的关联管理
层级结构怎么通过数据体现出来的
如下图所示,其实就是pid与id的关系
{"success": true,"code": 10000,"data": [{"id": 1,"pid": 0,"name": "传智教育","code": "CZJY","managerId": 1,"managerName": "管理员","introduce": "总部","createTime": "2022-10-26 09:13:37"},{"id": 2,"pid": 1,"name": "总裁办","code": "ZCB","managerId": 1,"managerName": "管理员","introduce": "公司战略部","createTime": "2022-10-26 09:13:37"},{"id": 3,"pid": 1,"name": "行政部","code": "XZB","managerId": 4,"managerName": "黑马文吉星","introduce": "行政部","createTime": "2022-10-26 09:13:39"},......................],"message": "获取组织架构数据成功"
}
- 封装递归函数根据关系转化层级结构
/*** 列表数据转树形数据* rootValue: 其实就是pid(父id)*/
export function transListToTreeData(list, rootValue) {const arr = []list.forEach(item => {if (item.pid === rootValue) {// 找到了匹配的节点arr.push(item)// 当前节点的id和当前节点的字节点的pid相等// 下面的方法其实就是找当前节点的子节点const children = transListToTreeData(list, item.id) // 找到的节点的子节点item.children = children // 将子节点赋值给当前节点// 我们先push再赋值childern也没关系,因为是一个对象,地址是一样的}})return arr
}
1.3 效果图
1.4 修改树形结构bug
当我们点击“操作”的时候,整个树形结构会关闭或者打开,很不方便,然后我们可以将expand-on-click-nod属性设置为false,说明只有点击箭头图标的时候才会展开或者收缩节点
<!--展示树形结构-->
<!--此处正好是标签的属性名叫props,和vue框架中组件传递数据不是一个-->
<!--default-expand-all属性表示默认将所有的树形结构打开,完整写法是 :default-expand-all=“true” (一定带冒号)-->
<el-tree :data="depts" :props="defaultProps" default-expand-all :expand-on-click-node="false">
二、添加子部门
2.1 表单弹层
我们可以将“添加”和“修改”功能公用同一个弹层,也就是“添加”和“修改”的时候公用同一个组件
大体分为两步:
- 注册添加子部门事件
- 封装弹层组件
- 控制弹层显示隐藏
在如下位置填写
2.1.1 下拉菜单点击事件
<!--下拉菜单组件--><!--@command是下拉菜单的执行方法,当点击下拉菜单中的某一项的时候,就会执行operateDept方法--><el-dropdown @command="operateDept"><!--显示区域内容--><span class="el-dropdown-link">操作<i class="el-icon-arrow-down el-icon--right"></i>
</span><!--下拉菜单的选项--><el-dropdown-menu slot="dropdown"><el-dropdown-item command="add">添加子部门</el-dropdown-item><el-dropdown-item command="edit">编辑部门</el-dropdown-item><el-dropdown-item command="del">删除</el-dropdown-item></el-dropdown-menu></el-dropdown>
当点击某一个下拉框的时候,就会将command的值传输到type
operateDept(type) {alert(type)
}
效果为如下所示,这样我们就能根据不同的按钮做出不同的功能了
2.1.2 封装弹层组件
<template><!--:visible用来控制显示和隐藏,由于我们是一个组件,所以我们需要外部传入一个参数来控制显示还是隐藏--><!--@close用于监视关闭弹层(点击右上角×号的时候,就会执行此函数)--><el-dialog title="新增部门" :visible="showDialog" @close="close"><!--放置弹层内容--></el-dialog>
</template><script>
export default {// 接收外部传输过来的值props: {// 参数showDialog,类型限制为Boolean,默认值为falseshowDialog: { type: Boolean, default: false }},methods: {close() {// 修改父组件的值,子传给父亲// this.$emit可以触发一个自定义事件(父组件需要接收这个时间),然后把false这个值传出去// this.$emit('',false) 但是我们先不用这个方法// 这里我们使用了 sync修饰,表示会接受子组件的事件,也就是update:showDialog这个事件,然后会把值赋值给showDialogthis.$emit('update:showDialog', false)}}
}
</script>
在index.vue组件中进行引用
<template><!--为什么这里写container和app-container? 因为我们最初的时候导入过样式--><div class="container"><!--在此容器上加了边距(上下30px,左右140px)--><div class="app-container">组织架构<!--展示树形结构--><!--此处正好是标签的属性名叫props,和vue框架中组件传递数据不是一个--><!--default-expand-all属性表示默认将所有的树形结构打开,完整写法是 :default-expand-all=“true” (一定带冒号)--><el-tree :data="depts" :props="defaultProps" default-expand-all :expand-on-click-node="false"><!--v-slot标签只能使用在template标签上--><!--这个data数据其实是el-tree给的,下面的template模板会不断的去循环,有多少个节点就会循环多少次,把每一个数据都塞到了data里面--><template v-slot="{data}"><!--节点结构。使用el-tree组件的插槽--><!--会读取这个节点结构并循环渲染到页面上--><!--我们如果把type布局模式设置为"flex",就可以用厚民安的两个属性,justify="space-between"表示两头对其,align="middle"表示垂直居中--><el-row type="flex" justify="space-between" align="middle" style="width: 100%;height: 40px"><!--一行里面有两列,第一列是组织名称,第二列是管理员和操作(下拉菜单)--><el-col>{{ data.name }}</el-col><!--:span="4"一定带着冒号,因为人家想要一个数字类型--><el-col :span="6"><span class="tree-manager">{{ data.managerName }}</span><!--下拉菜单组件--><!--@command是下拉菜单的执行方法,当点击下拉菜单中的某一项的时候,就会执行operateDept方法--><el-dropdown @command="operateDept" ><!--显示区域内容--><span class="el-dropdown-link">操作<i class="el-icon-arrow-down el-icon--right"></i></span><!--下拉菜单的选项--><el-dropdown-menu slot="dropdown"><el-dropdown-item command="add">添加子部门</el-dropdown-item><el-dropdown-item command="edit">编辑部门</el-dropdown-item><el-dropdown-item command="del">删除</el-dropdown-item></el-dropdown-menu></el-dropdown></el-col></el-row></template></el-tree></div><!--放置弹层--><!--:show-dialog 是我们在add-dept组件中定义的props--><!--sync修饰,表示会接受子组件的事件,也就是update:showDialog这个事件,然后会把值赋值给下面的showDialog--><add-dept :show-dialog.sync="showDialog"></add-dept></div>
</template><script>
import { getDepartment } from '@/api/department'
import { transListToTreeData } from '@/utils'
// 引入封装的弹层组件
import AddDept from './components/add-dept.vue'export default {name: 'Department',components: { AddDept },// 完成AddDept组件局部注册comments: { AddDept },data() {return {// 数组属性depts: [''],defaultProps: {// 当前层级数据(在这里找此层级的显示数据)label: 'name',// 子层级(其实就是层级结构,在这里找子节点)children: 'children'},// 控制弹层的显示和隐藏showDialog: false}},// 页面初始化的时候会调用这个函数created() {// 获取部门数据,这个方法是下面methods里面的,不是api里面的this.getDepartment()},methods: {async getDepartment() {// 下面这个方法是import导入的api请求方法const result = await getDepartment()// 但是我们获取到的数据是列表的形式,没有层级结构,我们要使用递归的方式完成树形结构this.depts = transListToTreeData(result, 0)},operateDept(type) {if (type === 'add') {// 添加子部门// 显示弹层组件this.showDialog = true}}}
}
</script><style scoped>
.app-container {padding: 30px 140px;
}.tree-manager {width: 50px;display: inline-block;
//margin: 10px; margin-right: 50px;
}
</style>
2.2 表单结构及校验
下面是基本校验
也要完成业务校验:
如:
- 部门名称和已有部门不重复
- 部门编码和已有编码不重复
如果不重复的话,就实行添加功能
<template><!--:visible用来控制显示和隐藏,由于我们是一个组件,所以我们需要外部传入一个参数来控制显示还是隐藏--><!--@close用于监视关闭弹层(点击右上角×号的时候,就会执行此函数)--><el-dialog title="新增部门" :visible="showDialog" @close="close"><!--放置弹层内容--><!--label-width设置文本的宽度,这样文本框左边的字就能对其了--><el-form :model="formDara" :rules="rules" ref="addDept" label-width="120px"><el-form-item prop="name" label="部门名称"><!--place-holder是文本提示信息--><el-input v-model="formDara.name" placeholder="2-10个字符" style="width: 80%" size="mini"></el-input></el-form-item><el-form-item prop="code" label="部门编码"><el-input v-model="formDara.code" placeholder="2-10个字符" style="width: 80%" size="mini"></el-input></el-form-item><el-form-item prop="managerId" label="部门负责人"><!--这个地方是一个下拉菜单--><el-select v-model="formDara.managerId" placeholder="请选择负责人" style="width: 80%" size="mini"><!--下拉选项,循环managerList--><!--label其实是下拉选项中所显示的字段名称;v-model的值为当前被选中的el-option的value属性值--><el-option v-for="item in managerList" :key="item.id" :label="item.username" :value="item.id"></el-option></el-select></el-form-item><el-form-item prop="introduce" label="部门介绍"><!--文本域,4行--><el-input v-model="formDara.introduce" type="textarea" :rows="4" placeholder="1-100个字符" style="width: 80%"size="mini"></el-input></el-form-item><!--按钮部分,并且要实现居中--><el-form-item><!--我们可以使用行和列实现居中布局--><!--justify="center" 水平方式居中--><el-row type="flex" justify="center"><el-col :span="12"><el-button type="primary" size="mini">确定</el-button><el-button size="mini">取消</el-button></el-col></el-row></el-form-item></el-form></el-dialog>
</template><script>
import { getDepartment } from '@/api/department'export default {// 接收外部传输过来的值props: {// 参数showDialog,类型限制为Boolean,默认值为falseshowDialog: { type: Boolean, default: false }},data() {return {formDara: {code: '', // 部门编码introduce: '', // 部门介绍managerId: '', // 部门负责人idname: '', // 部门名称,pid: '' // 父级部门的id},rules: {// 部门编码code: [{ required: true, message: '部门编码不能为空', trigger: 'blur' },{ min: 2, max: 10, message: '部门编码的长度2-10个字符' },// 自定义业务校验,部门编码不能重复{trigger: 'blur', validator: async(rule, value, callback) => {// value值是输入的编码值const result = await getDepartment()// result实际是一个数组,然后查看数组中是否存在用户输入的value值// 我们可以使用some()方法,如果存在就返回true,不存在就返回falseif (result.some(item => item.code === value)) {// 校验失败时的错误对象callback(new Error('部门中已经有该编码'))} else {// 校验成功时的对象callback()}}}],// 部门介绍introduce: [{ required: true, message: '部门介绍不能为空', trigger: 'blur' },{ min: 2, max: 10, message: '部门名称的长度1-100个字符' }],// 部门负责人idmanagerId:[{ required: true, message: '部门负责人id不能为空', trigger: 'blur' }],// 部门名称name:[{ required: true, message: '部门名称不能为空', trigger: 'blur' },{ min: 2, max: 10, message: '部门名称的长度2-10个字符' },{trigger: 'blur', validator: async(rule, value, callback) => {// value值是输入的编码值const result = await getDepartment()// result实际是一个数组,然后查看数组中是否存在用户输入的value值// 我们可以使用some()方法,如果存在就返回true,不存在就返回falseif (result.some(item => item.name === value)) {// 校验失败时的错误对象callback(new Error('部门中已经有该名称'))} else {// 校验成功时的对象callback()}}}]// pid: '' // 父级部门的id}}},methods: {......}
}
</script>
2.3 部门负责人数据
如下图所示
实现步骤:
- 获取负责人的列表
- 绑定下拉组件
Api
/*** 获取部门负责人的数据*/
export function getManagerList() {return request({url: '/sys/user/simple',// 请求方式method: 'GET'})
}
组件代码
data() {return {// 存储负责人的列表managerList: [],}
},
methods: {async getManagerList() {this.managerList = await getManagerList()}
},
created() {this.getManagerList()
},
<el-form-item prop="managerId" label="部门负责人"><!--这个地方是一个下拉菜单--><el-select v-model="formDara.managerId" placeholder="请选择负责人" style="width: 80%" size="mini"><!--下拉选项,循环managerList--><!--label其实是下拉选项中所显示的字段名称;v-model的值为当前被选中的el-option的value属性值--><el-option v-for="item in managerList" :key="item.id" :label="item.username" :value="item.id"></el-option></el-select></el-form-item>
2.4 记录部门
我们要在PHP研发部下面添加其子部门,这个时候我们要知道PHP研发部的id才可以
当我们点击PHP研发部的时候,我们需要将其id传输给AddDept组件
我们可以先看一下父组件
<template><!--为什么这里写container和app-container? 因为我们最初的时候导入过样式--><div class="container"><!--在此容器上加了边距(上下30px,左右140px)--><div class="app-container">组织架构<!--展示树形结构--><!--此处正好是标签的属性名叫props,和vue框架中组件传递数据不是一个--><!--default-expand-all属性表示默认将所有的树形结构打开,完整写法是 :default-expand-all=“true” (一定带冒号)--><el-tree :data="depts" :props="defaultProps" default-expand-all :expand-on-click-node="false"><!--v-slot标签只能使用在template标签上--><!--这个data数据其实是el-tree给的,下面的template模板会不断的去循环,有多少个节点就会循环多少次,把每一个数据都塞到了data里面--><template v-slot="{data}"><!--节点结构。使用el-tree组件的插槽--><!--会读取这个节点结构并循环渲染到页面上--><!--我们如果把type布局模式设置为"flex",就可以用厚民安的两个属性,justify="space-between"表示两头对其,align="middle"表示垂直居中--><el-row type="flex" justify="space-between" align="middle" style="width: 100%;height: 40px"><!--一行里面有两列,第一列是组织名称,第二列是管理员和操作(下拉菜单)--><el-col>{{ data.name }}</el-col><!--:span="4"一定带着冒号,因为人家想要一个数字类型--><el-col :span="6"><span class="tree-manager">{{ data.managerName }}</span><!--下拉菜单组件--><!--@command是下拉菜单的执行方法,当点击下拉菜单中的某一项的时候,就会执行operateDept方法--><!--$event实参表示类型,也就是下面command中的值,表示事件所携带的默认参数,如果不传data.id的话,默认传入的就是$event实参--><el-dropdown @command="operateDept($event,data.id)"><!--显示区域内容--><span class="el-dropdown-link">操作<i class="el-icon-arrow-down el-icon--right"></i></span><!--下拉菜单的选项--><el-dropdown-menu slot="dropdown"><el-dropdown-item command="add">添加子部门</el-dropdown-item><el-dropdown-item command="edit">编辑部门</el-dropdown-item><el-dropdown-item command="del">删除</el-dropdown-item></el-dropdown-menu></el-dropdown></el-col></el-row></template></el-tree></div><!--放置弹层--><!--:show-dialog 是我们在add-dept组件中定义的props--><!--sync修饰,表示会接受子组件的事件,也就是update:showDialog这个事件,然后会把值赋值给下面的showDialog--><add-dept :current-node-id="currentNodeId" :show-dialog.sync="showDialog"></add-dept></div>
</template>
methods: {operateDept($event, id) {if ($event === 'add') {// 添加子部门// 显示弹层组件this.showDialog = true// 当前点击节点的idthis.currentNodeId = id}},.................
},data() {return {// 存储当前点击idcurrentNodeId: null,............// 控制弹层的显示和隐藏showDialog: false}}
子组件进行接收
// 接收外部传输过来的值
props: {// 参数showDialog,类型限制为Boolean,默认值为falseshowDialog: { type: Boolean, default: false },currentNodeId: {type: Number,default: null}
}
2.5 确认功能
点击“确认”时,首先进行表单校验,表单校验通过之后,可以将数据提交到后端也就是调用新增接口,如果调用接口成功我们就可以通知父组件更新(组织架构列表要更新),也要重置一下表单数据,再点开后是空白的情况,最后关闭弹层
按钮
<el-button @click="btnOK" type="primary" size="mini">确定</el-button>
api请求
/*** 新增组织接口*/
export function addDepartment(data) {return request({url: '/company/department',// 请求方式method: 'POST',data: data})
}
方法
methods: {close() {// 重置表单this.$refs.addDept.resetFields()// 修改父组件的值,子传给父亲// this.$emit可以触发一个自定义事件(父组件需要接收这个时间),然后把false这个值传出去// this.$emit('',false) 但是我们先不用这个方法// 这里我们使用了 sync修饰,表示会接受子组件的事件,也就是update:showDialog这个事件,然后会把值赋值给showDialogthis.$emit('update:showDialog', false)},async getManagerList() {this.managerList = await getManagerList()},// 点击确定时调用此方法btnOK() {this.$refs.addDept.validate(async isOK => {if (isOK) {// 校验已经通过// ...this.formDara 表示相当于把formDara数据进行了拷贝,考到了一个新对象里面// pid: this.currentNodeId表示将formDara中的pid赋值上currentNodeIdawait addDepartment({ ...this.formDara, pid: this.currentNodeId })// 此时可以通知父组件更新,也就是子传父,可以选择触发一个自定义事件(父组件要监听这个事件)this.$emit('updateDepartment')// 提示消息this.$message.success('新增部门成功')// 关闭this.close()}})}}
父组件index.vue
子组件会触发自定义事件updateDepartment
<!--放置弹层-->
<!--:show-dialog 是我们在add-dept组件中定义的props-->
<!--sync修饰,表示会接受子组件的事件,也就是update:showDialog这个事件,然后会把值赋值给下面的showDialog-->
<!--自定义事件updateDepartment,子组件触发,父组件执行getDepartment方法,刷新组织结构-->
<add-dept @updateDepartment="getDepartment" :current-node-id="currentNodeId" :show-dialog.sync="showDialog"
></add-dept>
2.6 取消功能
点击取消后,重置表单,关闭弹层即可
<el-button @click="close" size="mini">取消</el-button>