目标
:使用element-UI组件布局组织架构的基本布局
组织架构产品prd
一个企业的组织架构是该企业的灵魂,组织架构多常采用树形金字塔式结构,本章节,我们布局出页面的基本结构
首先实现头部的结构,采用element的行列布局
江苏传智播客教育科技股份有限公司 负责人 操作 添加子部门
样式
接下来,实现树形的结构,采用element的tree组件, 如图效果
树形组件属性
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
default-expand-all | 是否默认展开所有节点 | boolean | — | — |
data | 展示数据 | array | — | — |
node-key | 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 | String | — | — |
props | 配置选项,具体看下表 | object | — | — |
props属性
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
label | 指定节点标签为节点对象的某个属性值 | string, function(data, node) | — | — |
children | 指定子树为节点对象的某个属性值 | string | — | — |
disabled | 指定节点选择框是否禁用为节点对象的某个属性值 | boolean, function(data, node) | — | — |
isLeaf | 指定节点是否为叶子节点,仅在指定了 lazy 属性的情况下生效 | boolean, function(data, node) | — | — |
data是组成树形数据的关键,如下的数据便能构建树形数据
[{ label: '一级 1', children: [{ label: '二级 1-1', children: [{ label: '三级 1-1-1' }] }] }, { label: '一级 2', children: [{ label: '二级 2-1', children: [{ label: '三级 2-1-1' }] }, { label: '二级 2-2', children: [{ label: '三级 2-2-1' }] }] }, { label: '一级 3', children: [{ label: '二级 3-1', children: [{ label: '三级 3-1-1' }] }, { label: '二级 3-2', children: [{ label: '三级 3-2-1' }] }] }]
由此,我们首先实现静态数据的组织架构
export default { data() { return { defaultProps: { label: 'name' }, departs: [ { name: '总裁办', children: [{ name: '董事会' }] }, { name: '行政部' }, { name: '人事部' } ] } } }
接下来,对每个层级节点增加显示内容,此时需要用到tree的插槽
{{ data.name }} {{ data.manager }} 操作 添加子部门 编辑部门 删除部门
最终形成静态结构效果
提交代码
本节任务
:完成树形结构的显示
目标
: 将树形的操作内容单独抽提成组件
通过第一个章节,我们发现,树形的顶级内容实际和子节点的内容是一致的,此时可以将该部分抽提成一个组件,节省代码
组件 src/views/departments/components/tree-tools.vue
{{ treeNode.name }} {{ treeNode.manager }} 操作 添加子部门 编辑部门 删除部门
接下来,在src/views/departments/index.vue
进行代码的简化
上面代码中,company变量需要在data中定义
company: { name: '江苏传智播客教育科技股份有限公司', manager: '负责人' },
同时,由于在两个位置都使用了该组件,但是放置在最上层的组件是不需要显示 删除部门
和编辑部门
的
所以,增加一个新的属性 isRoot(是否根节点)
进行控制
props: { treeNode: { required: true, // 设置当前数据为必填 type: Object // 类型是Object }, isRoot: { type: Boolean, default: false } }
组件中, 根据isRoot判断显示
编辑部门 删除部门
通过封装,代码看上去更加紧凑,简洁,这就是封装的魅力
提交代码
本节任务
:将树形内容单独抽提组件
目标
获取真实的组织架构数据,并将其转化成树形数据显示在页面上
现在基本的静态结构已经形成,接下来需要获取真实的数据
首先,封装获取组织架构的请求 src/api/departments.js
/** * * * 获取组织架构数据 * **/ export function getDepartments() { return request({ url: '/company/department' }) }
在钩子函数中调用接口
import TreeTools from './components/tree-tools' import { getDepartments } from '@/api/departments' export default { components: { TreeTools }, data() { return { company: { }, // 就是头部的数据结构 departs: [], defaultProps: { label: 'name' // 表示 从这个属性显示内容 } } }, created() { this.getDepartments() // 调用自身的方法 }, methods: { async getDepartments() { const result = await getDepartments() this.company = { name: result.companyName, manager: '负责人' } this.departs = result.depts // 需要将其转化成树形结构 console.log(result) } } }
然后,我们需要将列表型的数据,转化成树形数据,这里需要用到递归算法
封装一个工具方法,src/utils/index.js
/** * * * 将列表型的数据转化成树形数据 => 递归算法 => 自身调用自身 => 一定条件不能一样, 否则就会死循环 * 遍历树形 有一个重点 要先找一个头儿 * ***/ export function tranListToTreeData(list, rootValue) { var arr = [] list.forEach(item => { if (item.pid === rootValue) { // 找到之后 就要去找 item 下面有没有子节点 const children = tranListToTreeData(list, item.id) if (children.length) { // 如果children的长度大于0 说明找到了子节点 item.children = children } arr.push(item) // 将内容加入到数组中 } }) return arr }
调用转化方法,转化树形结构
this.company = { name: result.companyName, manager: '负责人' } // 这里定义一个空串 因为 它是根 所有的子节点的数据pid 都是 "" this.departs = tranListToTreeData(result.depts, '')
这样一来,树形数据就有了,下一章节,就可以针对部门进行操作
提交代码
本节任务
获取组织架构数据,并进行树形处理
目标
实现操作功能的删除功能
首先,封装删除功能模块 src/api/departments.js
/** * * 根据id根据部门 接口是根据restful的规则设计的 删除 delete 新增 post 修改put 获取 get * **/ export function delDepartments(id) { return request({ url: `/company/department/${id}`, method: 'delete' }) }
然后,在tree-tools组件中,监听下拉菜单的点击事件 src/views/departments/index.vue
操作 添加子部门 编辑部门 删除部门
dropdown下拉菜单的监听事件command
// 操作节点调用的方法 operateDepts(type) { if (type === 'add') { // 添加子部门的操作 } else if (type === 'edit') { // 编辑部门的操作 } else { // 删除操作 } }
删除之前,提示用户是否删除,然后调用删除接口
// 操作节点调用的方法 operateDepts(type) { if (type === 'add') { // 添加子部门的操作 } else if (type === 'edit') { // 编辑部门的操作 } else { // 删除操作 this.$confirm('确定要删除该部门吗').then(() => { // 如果点击了确定就会进入then return delDepartments(this.treeNode.id) // 返回promise对象 }).then(() => { // 如果删除成功了 就会进入这里 }) } }
上面代码中,我们已经成功删除了员工数据,但是怎么通知父组件进行更新呢
在前面的课程中,我们已经学习过可以通过自定义事件this.$emit
的方式来进行
// 如果删除成功了 就会进入这里 this.$emit('delDepts') // 触发自定义事件 this.$message.success('删除部门成功')
父组件监听事件 src/views/department/index.vue
提交代码
本节任务
:删除部门功能实现
目标
:实现新增部门功能的组件建立
首先, 封装新增部门的api模块 src/api/departments.js
/** * 新增部门接口 * * ****/ export function addDepartments(data) { return request({ url: '/company/department', method: 'post', data }) }
然后,我们需要构建一个新增部门的窗体组件 src/views/department/components/add-dept.vue
其中的交互设计如下
设计要求
确定 取消
然后,我们需要用属性控制组件的显示或者隐藏
// 需要传入一个props变量来控制 显示或者隐藏 props: { showDialog: { type: Boolean, default: false } }
在departments/index.vue
中引入该组件
import AddDept from './components/add-dept' // 引入新增部门组件 export default { components: { AddDept } }
定义控制窗体显示的变量showDialog
data() { return { showDialog: false // 显示窗体 } },
当点击新增部门时,弹出组件
注意,点击新增时tree-tools组件,所以这里,我们依然需要子组件调用父组件
子组件触发新增事件· src/views/departments/tree-tools.vue
if (type === 'add') { // 添加子部门的操作 // 告诉父组件 显示弹层 this.$emit('addDepts', this.treeNode) // 为何传出treeNode 因为是添加子部门 需要当前部门的数据 }
父组件监听事件
2处添加
方法中弹出层,记录在哪个节点下添加子部门
addDepts(node) { this.showDialog = true // 显示弹层 // 因为node是当前的点击的部门, 此时这个部门应该记录下来, this.node = node }
提交代码
本节任务
:新增部门功能-建立组件
目标
完成新增部门功能的规则校验和数据提交部分
部门名称(name):必填 1-50个字符 / 同级部门中禁止出现重复部门
部门编码(code):必填 1-50个字符 / 部门编码在整个模块中都不允许重复
部门负责人(manager):必填
部门介绍 ( introduce):必填 1-300个字符
定义数据结构
formData: { name: '', // 部门名称 code: '', // 部门编码 manager: '', // 部门管理者 introduce: '' // 部门介绍 },
完成表单校验需要的前置条件
el-form配置model和rules属性
el-form-item配置prop属性
表单进行v-model双向绑定
根据这些要求,校验规则
data() { return { // 定义表单数据 formData: { name: '', // 部门名称 code: '', // 部门编码 manager: '', // 部门管理者 introduce: '' // 部门介绍 }, // 定义校验规则 rules: { name: [ { required: true, message: '部门名称不能为空', trigger: 'blur' }, { min: 1, max: 50, message: '部门名称要求1-50个字符', trigger: 'blur' } ], code: [ { required: true, message: '部门编码不能为空', trigger: 'blur' }, { min: 1, max: 50, message: '部门编码要求1-50个字符', trigger: 'blur' } ], manager: [{ required: true, message: '部门负责人不能为空', trigger: 'blur' }], introduce: [ { required: true, message: '部门介绍不能为空', trigger: 'blur' }, { trigger: 'blur', min: 1, max: 300, message: '部门介绍要求1-50个字符' } ] } } }
注意
:部门名称和部门编码的规则 有两条我们需要通过自定义校验函数validator
来实现
首先,在校验名称和编码时,要获取最新的组织架构,这也是我们这里trigger采用blur的原因,因为change对于访问的频率过高,我们需要控制访问频率
// 首先获取最新的组织架构数据 const { depts } = await getDepartments()
部门名称不能和
同级别
的重复,这里注意,我们需要找到所有同级别的数据,进行校验,所以还需要另一个参数pid
props: { // 用来控制窗体是否显示或者隐藏 showDialog: { type: Boolean, default: false }, // 当前操作的节点 treeNode: { type: Object, default: null } },
根据当前部门id,找到所有子部门相关的数据,判断是否重复
// 现在定义一个函数 这个函数的目的是 去找 同级部门下 是否有重复的部门名称 const checkNameRepeat = async(rule, value, callback) => { // 先要获取最新的组织架构数据 const { depts } = await getDepartments() // depts是所有的部门数据 // 如何去找技术部所有的子节点 const isRepeat = depts.filter(item => item.pid === this.treeNode.id).some(item => item.name === value) isRepeat ? callback(new Error(`同级部门下已经有${value}的部门了`)) : callback() }
检查部门编码的过程同理
// 检查编码重复 const checkCodeRepeat = async(rule, value, callback) => { // 先要获取最新的组织架构数据 const { depts } = await getDepartments() const isRepeat = depts.some(item => item.code === value && value) // 这里加一个 value不为空 因为我们的部门有可能没有code isRepeat ? callback(new Error(`组织架构中已经有部门使用${value}编码`)) : callback() }
在规则中定义
// 定义校验规则 rules: { name: [ { required: true, message: '部门名称不能为空', trigger: 'blur' }, { min: 1, max: 50, message: '部门名称要求1-50个字符', trigger: 'blur' }, // 自定义函数的形式校验 {trigger: 'blur',validator: checkNameRepeat } ], code: [ { required: true, message: '部门编码不能为空', trigger: 'blur' }, { min: 1, max: 50, message: '部门编码要求1-50个字符', trigger: 'blur' }, { trigger: 'blur',validator: checkCodeRepeat} ], manager: [{ required: true, message: '部门负责人不能为空', trigger: 'blur' }], introduce: [ { required: true, message: '部门介绍不能为空', trigger: 'blur' }, { trigger: 'blur', min: 1, max: 300, message: '部门介绍要求1-50个字符' } ] }
需要注意
:在最根级的tree-tools
组件中,由于treenode属性中没有id,id便是undefined,但是通过undefined进行等值判断是寻找不到对应的根节点的, 所以在传值时,我们将id属性设置为 “”
src/views/departments/index.vue
async getDepartments() { const result = await getDepartments() this.departs = tranListToTreeData(result.depts, '') this.company = { name: result.companyName, manager: '负责人', id: '' } },
提交代码
本节任务
:完成新增部门的规则校验
目标
:获取新增表单中的部门负责人下拉数据
在上节的表单中,部门负责人是下拉数据,我们应该从
员工接口
中获取该数据
首先,封装获取简单员工列表的模块 src/api/employees.js
import request from '@/utils/request' /** * 获取员工的简单列表 * **/ export function getEmployeeSimple() { return request({ url: '/sys/user/simple' }) }
然后,在add-dept.vue
中的select聚焦事件focus
中调用该接口,因为我们要获取实时的最新数据
获取员工列表
import { getEmployeeSimple } from '@/api/employees' methods: { // 获取员工简单列表数据 async getEmployeeSimple() { this.peoples = await getEmployeeSimple() } } data:{ peoples: [] // 接收获取的员工简单列表的数据 }
提交代码
本节任务
:新增部门功能-部门负责人数据
目标
: 完成新增模块的提交-取消-关闭等功能
当点击新增页面的确定按钮时,我们需要完成对表单的整体校验,如果校验成功,进行提交
首先,在点击确定时,校验表单
给el-form定义一个ref属性
// 点击确定时触发 btnOK() { this.$refs.deptForm.validate(isOK => { if (isOK) { // 表示可以提交了 } }) }
然后,在校验通过时,调用新增接口
因为是添加子部门,所以我们需要将新增的部门pid设置成当前部门的id,新增的部门就成了自己的子部门
// 点击确定时触发 btnOK() { this.$refs.deptForm.validate(async isOK => { if (isOK) { // 表示可以提交了 await addDepartments({ ...this.formData, pid: this.treeNode.id }) // 调用新增接口 添加父部门的id } }) }
同样,在新增成功之后,调用告诉父组件,重新拉取数据
this.$emit('addDepts')
父组件
本节注意
:同学们可能会疑惑,我们tree-tools.vue
和add-dept.vue
两个组件都触发了addDepts事件,不冲突吗?
这里,我们触发的自定义事件都是组件自身的,他们之间没有任何关系,只是名字相同而已,大家不要混淆
这里我们学习一个新的技巧,
sync修饰符
按照常规,想要让父组件更新showDialog
的话,需要这样做
// 子组件 this.$emit('changedialog', false) //触发事件 // 父组件// JS 里面 method(value) { this.showDialog = value }
但是,vuejs为我们提供了
sync修饰符
,它提供了一种简写模式 也就是
// 子组件 update:固定写法 (update:props名称, 值) this.$emit('update:showDialog', false) //触发事件 // 父组件 sync修饰符
只要用sync修饰,就可以省略父组件的监听和方法,直接将值赋值给showDialog