组织架构模块
业务功能介绍
说明:组织架构模块主要管理公司所有部门信息,支持添加、删除、编辑操作
基本组件布局
**目标**
:使用element-UI组件布局组织架构的基本布局
- 头部布局
<el-card class="tree-card">
<!-- 用了一个行列布局 -->
<el-row type="flex" justify="space-between" align="middle" style="height: 40px">
<el-col :span="20">
<span>江苏传智播客教育科技股份有限公司</span>
</el-col>
<el-col :span="4">
<el-row type="flex" justify="end">
<!-- 两个内容 -->
<el-col>负责人</el-col>
<el-col>
<!-- 下拉菜单 element -->
<el-dropdown>
<span>
操作<i class="el-icon-arrow-down" />
</span>
<!-- 下拉菜单 -->
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>添加子部门</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-col>
</el-row>
</el-col>
</el-row>
</el-card>
<style scoped>
.tree-card {
padding: 30px 30px;
font-size:14px;
}
</style>
总结:
- 栅格系统的基本使用(横向有24列) el-row
- 栅格中可以再次嵌套栅格
- 栅格组件支持相关的布局(flex)
树形组件用法
目标:熟悉树形组件的基本用法
- 树形组件关键属性 | 参数 | 说明 | 类型 | 可选值 | 默认值 | | --- | --- | --- | --- | --- | | data | 展示数据 | array | — | — | | props | 配置选项,具体看下表 | object | — | — |
注意: data 中默认
label
为节点标签的文字,children
为子节点 (可以通过 props 修改默认配置 )
export default {
name: 'Departments',
data() {
return {
list: [
{
label: '企业部',
children: [
{ label: '策划部' },
{ label: '游戏部' }
]
},
{ label: '事件部' },
{ label: '小卖部' }
]
}
}
}
- 那万一后台给的树形数据, 不是label 和 children 字段名呢 ?通过 props 修改默认配置
<el-tree :data="list" :props="defaultProps" />
data() {
return {
list: [
{
name: '企业部',
children: [
{ name: '策划部' },
{ name: '游戏部' }
]
},
{ name: '事件部' },
{ name: '小卖部' }
],
defaultProps: {
label: 'name',
children: 'children'
}
}
}
总结:参考官方案例实现树形菜单的基本使用
data 提供数据
props 配置数据的属性名称
作用域插槽
目标:熟悉作用域插槽的基本用法
- 插槽基本使用
- 具名插槽
- 作用域插槽
总结:作用域插槽用于实现:通过子组件传递给父组件插槽的数据定制子组件的模板
- 子组件把数据绑定到slot标签上即可
- 父组件通过template标签的v-slot指令获取子组件的数据,从而定制子组件的模板
实现树形的静态组织架构
目标:基于tree组件实现静态组件组织架构效果
<el-tree :data="departs" :props="defaultProps" />
export default {
name: 'Departments',
data() {
return {
departs: [
{
name: '总裁办',
manager: '曹操',
children: [{ name: '董事会', manager: '曹丕' }]
},
{ name: '行政部', manager: '刘备' },
{ name: '人事部', manager: '孙权' }
],
defaultProps: {
label: 'name',
children: 'children'
}
}
}
}
- 接下来,对每个层级节点增加显示内容,此时需要用到tree的插槽
<!-- 树形菜单 -->
<el-tree :data="departs" :props="defaultProps">
<!-- 中间的代码就是插槽内容,用于定制每一行的布局效果 -->
<template v-slot="scope">
<el-row type="flex" justify="space-between" align="middle" style="height: 40px; width: 100%;">
<el-col :span="20">
<span>{{ scope.data.name }}</span>
</el-col>
<el-col :span="4">
<el-row type="flex" justify="end">
<!-- 两个内容 -->
<el-col>{{ scope.data.manager }}</el-col>
<el-col>
<!-- 下拉菜单 element -->
<el-dropdown>
<span> 操作<i class="el-icon-arrow-down" /> </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>
</el-col>
</el-row>
</template>
</el-tree>
总结:如何定制树形菜单的布局?通过作用域插槽定制每一行内容
本质上就是作用域插槽(scope.data表示每一行数据;scope.node表示每一行数据的相关状态属性(比如是否展开等信息))
scope中包含了其他很多信息,如果需要可以借助调试工具查看。
- el-tree底层实现
<template>
<div>
<ul>
<li v-for="(item, index) in data" :key="index">
<!-- slot标签之前的内容是插槽的默认值 -->
<slot :data="item">{{ item.name }}</slot>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'MyTreeTest',
props: {
data: {
type: Array,
required: true
}
}
}
</script>
- 测试代码
<!-- 树形组件测试 -->
<my-tree-test :data="departs">
<template v-slot="scope">
<div>《{{ scope.data.name }}》</div>
</template>
</my-tree-test>
data () {
return {
departs: [
{ name: 'tom' },
{ name: 'jerry' },
{ name: 'spike' },
{ name: 'kitty' }
],
总结:父组件把所有列表数据注入子组件,子组件再通过作用域插槽方式把其中一项数据传回父组件,从而方便父组件基于子组件传递的数据定制子组件的模板效果。
拆分组织架构组件
**目标**
: 将树形的操作内容单独抽提成组件
**src/views/departments/components/TreeTools.vue**
- 拆分组件
<template>
<el-row type="flex" justify="space-between" align="middle" style="height: 40px;width: 100%;">
<el-col :span="20">
<span>{{ nodeData.name }}</span>
</el-col>
<el-col :span="4">
<el-row type="flex" justify="end">
<!-- 两个内容 -->
<el-col>{{ nodeData.manager }}</el-col>
<el-col>
<!-- 下拉菜单 element -->
<el-dropdown>
<span>
操作<i class="el-icon-arrow-down" />
</span>
<!-- 下拉菜单 -->
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>添加子部门</el-dropdown-item>
<el-dropdown-item v-if="!nodeData.root">编辑部分</el-dropdown-item>
<el-dropdown-item v-if="!nodeData.root">删除部门</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-col>
</el-row>
</el-col>
</el-row>
</template>
<script>
export default {
name: 'TreeTools',
props: {
nodeData: {
type: Object,
required: true
}
}
}
</script>
- 在组织架构中应用组件
**src/views/departments/index.vue**
<template>
<div class="department-container">
<el-card class="tree-card">
<!-- 用了一个行列布局 -->
<tree-tools :node-data="titleData" />
<!-- 树形菜单 -->
<el-tree :data="departs" :props="defaultProps">
<!-- 中间的代码就是插槽内容,用于定制每一行的布局效果 -->
<template v-slot="scope">
<tree-tools :node-data="scope.data" />
</template>
</el-tree>
</el-card>
</div>
</template>
- 上面代码中,company变量需要在data中定义
// 标题的数据
titleData: {
name: '江苏传智播客教育科技股份有限公司',
manager: '负责人',
root: true
},
同时,由于在两个位置都使用了该组件,但是放置在最上层的组件是不需要显示
**删除部门**
和**编辑部门**
的所以,增加一个新的属性
**root(是否根节点)**
进行控制
export default {
props: {
// 节点的内容展示
nodeData: {
type: Object,
required: true
}
}
}
<tree-tools :node-data="company"/>
- 组件中, 根据isRoot判断显示
<!-- 下拉菜单 element -->
<el-dropdown>
<span @click.stop>操作<i class="el-icon-arrow-down" /> </span>
<!-- 下拉菜单 -->
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>添加子部门</el-dropdown-item>
<el-dropdown-item v-if="!root">编辑部分</el-dropdown-item>
<el-dropdown-item v-if="!root">删除部门</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
总结:拆分组件为了降低代码的冗余度,也方便维护
- 封装基本的树形条目的组件(抽取动态属性)
- 导入并使用组件,然后把数据注入进去
获取组织架构接口数据
**目标**
获取真实的组织架构数据,并将其转化成树形数据显示在页面上
- 封装API接口,获取数据
**src/api/departments.js**
export function reqGetDepartments() {
return request({
url: '/company/department'
})
}
- 在钩子函数中调用接口
import { reqGetDepartments } from '@/api/departments'
created () {
this.loadDepartments()
},
methods: {
async loadDepartments () {
try {
const ret = await reqGetDepartments()
this.nodeData.name = ret.data.companyName
this.departs = ret.data.depts
} catch {
this.$message.error('获取组织架构数据失败')
}
}
}
总结:封装组织架构接口方法
注意:后端返回的数据是一个普通列表,但是我们需要树形结构的数据
处理数据成需要的结构
目标
:转换组织架构数据我们需要将列表型的数据,转化成树形数据,这里需要用到递归算法
我们列表型数据的关键属性: id 和 pid, id指的是自己的部门id, pid指的是父级部门的id (空则没有父级部门)
- 封装工具函数
**src/utils/index.js**
// 把列表数据结构转换为树形数据结构
// 找到当前部门(根据部门id)的所属的所有子部门
translateListToTreeData (list, id) {
// 遍历每一个元素,并且判断每一项pid是否可以参数的pid一致,这样可以找到参数pid所属的下属部门
const arr = []
list.forEach(item => {
if (item.pid === id) {
// 如果当前部门和父级部门匹配,再去找当前部门的下级部门
// children就是item的下级所有部门
const children = this.translateListToTreeData(list, item.id)
if (children.length > 0) {
// 证明找到了当前部门的子部门
item.children = children
}
arr.push(item)
}
})
return arr
},
总结:从list中查找指定id值的所有子节点(递归查找)
- 调用转化方法,转化树形结构
methods: {
// 获取原始的组织架构列表
async loadDepartments () {
try {
const ret = await reqGetDepartments()
this.nodeData.name = ret.data.companyName
// 把列表数据转换为树形数据
this.departs = this.translateListToTreeData(ret.data.depts, '')
} catch {
this.$message.error('获取组织架构数据失败')
}
}
}
总结:通过递归算法实现列表转换为树形结构的功能(算法)
核心流程:根据当前部门的id查询list列表中的所有子部门
删除部门
**目标**
实现操作功能的删除功能
- 封装接口删除部门
- 绑定点击按钮的事件
- 首先,封装删除功能模块
**src/api/departments.js**
export function reqDelDepartments(id) {
return request({
url: `/company/department/${id}`,
method: 'delete'
})
}
- 然后,在tree-tools组件中,监听下拉菜单的点击事件
**src/views/departments/index.vue**
<!-- 下拉菜单 element -->
<el-dropdown @command="handleCommand">
<span @click.stop>
操作<i class="el-icon-arrow-down" />
</span>
<!-- 下拉菜单 -->
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="add">添加子部门</el-dropdown-item>
<el-dropdown-item v-if="!isRoot" command="edit">编辑部门</el-dropdown-item>
<el-dropdown-item v-if="!isRoot" command="del">删除部门</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
- dropdown下拉菜单的监听事件command,在el-dropdown-item组件上添加command属性
methods: {
handleAction (type) {
if (type === 'add') {
// 添加子部门
} else if (type === 'edit') {
// 编辑部门
} else if (type === 'del') {
// 删除部门
this.$confirm('确认要删除部门吗?, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
// 确认删除
const ret = await reqDelDepartments(this.nodeData.id)
// 删除成功后,刷新列表(通知父组件进行刷新)
if (ret.code === 10000) {
// 删除成功
this.$emit('on-success')
} else {
// 删除失败后提示操作
this.$message.error(ret.message)
}
}).catch(err => {
// 点击取消按钮后,err的值为cancel
if (err !== 'cancel') {
// 删除失败的错误
this.$message.error('删除部门失败!')
}
})
}
}
}
这边也可以自己注册三个点击事件, 但是自己注册需要 @click.native = "delFn"
- 用删除接口,通知父组件更新数据
删除之前,提示用户是否删除,然后调用删除接口
但是怎么通知父组件进行更新呢? 子传父, 通知父组件
this.$emit('on-success')
- 父组件监听事件
**src/views/department/index.vue**
<tree-tools @on-success='onSuccess' :node-data="scope.data" />
总结
- 绑定点击事件(基于Element组件进行绑定;基于原生click事件绑定的话,需要native修饰符)
- 删除需要提示
- 删除成功后需要通知父组件刷新列表:子向父传值
新增部门-基本功能
新建组件-准备弹层
我们需要构建一个新增部门的窗体组件
**src/views/department/components/AddDept.vue**
- 准备弹窗组件
- 控制弹窗的显示和隐藏
- 弹窗的基本布局
- 基本布局
<template>
<el-dialog title="添加部门" :visible="isShowDept" :before-close="handleClose">
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="handleClose">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
</template>
<script>
export default {
name: 'AddDept',
props: {
isShowDept: {
type: Boolean,
required: true
}
},
methods: {
handleClose () {
// 通知父组件关闭弹窗
this.$emit('on-close', false)
}
}
}
</script>
- 在
**departments/index.vue**
中引入该组件
import AddDept from './components/add-dept'
import TreeTools from './components/tree-tools'
export default {
components: {
TreeTools,
AddDept
},
}
- 定义控制窗体显示的变量
**isShowDept**
<!-- 添加部门弹窗组件 -->
<add-dept :is-show-dept="isShowDept" @on-close="isShowDept=$event" />
data() {
return {
...
isShowDept: true
}
},
总结:
- index.vue把状态位传递给AddDept.vue组件
- 点击添加部门按钮事件发生在tree-tools.vue组件中,通知父组件打开弹窗
- 点击弹窗的关闭按钮,通知父组件关闭弹窗
点击 x 关闭弹层 (子传父)
控制添加部门组件的弹窗打开和关闭
add-dept.vue
给 dialog 注册 close 事件
<el-dialog title="添加部门" :visible="showDialog" @before-close="handleClose">
AddDept.vue
子传父, 传递 false
methods: {
handleClose() {
// 子传父
this.$emit('on-close')
}
}
index.vue
父组件中, 关闭弹层
<add-dept :is-show-dept="isShowDept" @on-close="isShowDept=$event" />
<!-- 简化写法 -->
<add-dept :is-show-dept.sync='isShowDept' />
this.$emit('update:is-show-dept', false)
总结:
- 监听el-dialog组件的before-close事件
- 事件函数中通知父组件关闭弹窗
- 父子组件之间属性的双向绑定操作:修饰符sync
准备弹层的结构内容
<template>
<!-- 新增部门的弹层 -->
<el-dialog title="新增部门" :visible="showDialog">
<!-- 表单组件 el-form label-width设置label的宽度 -->
<!-- 匿名插槽 -->
<el-form label-width="120px">
<el-form-item label="部门名称">
<el-input style="width:80%" placeholder="1-50个字符" />
</el-form-item>
<el-form-item label="部门编码">
<el-input style="width:80%" placeholder="1-50个字符" />
</el-form-item>
<el-form-item label="部门负责人">
<el-select style="width:80%" placeholder="请选择" />
</el-form-item>
<el-form-item label="部门介绍">
<el-input style="width:80%" placeholder="1-300个字符" type="textarea" :rows="3" />
</el-form-item>
</el-form>
<!-- el-dialog有专门放置底部操作栏的 插槽 具名插槽 -->
<div slot="footer">
<el-button type="primary" size="small">确定</el-button>
<el-button size="small">取消</el-button>
</div>
</el-dialog>
</template>
点击[新增子部门]显示弹层组件
- 子组件触发新增事件·
**src/views/departments/TreeTools.vue**
if (type === 'add') {
// 添加部门
this.$emit('add-dept', this.nodeData.id)
}
- 父组件监听事件
<tree-tools :node-data="scope.data" @on-success="onSuccess" @add-dept="addDept" />
- 方法中弹出层,记录在哪个节点下添加子部门
data () {
return {
currentDeptId: -1
}
}
// 处理添加操作, 显示弹层
addDept (id) {
// 控制添加部门的弹窗显示,并且得到部门id
this.currentDeptId = id
this.isShowDept = true
},
总结
- 点击添加子部门时,需要记录当前部门的信息(id)
- 这个部门信息需要传递给AddDept.vue组件,因为表单提交数据时需要父级部门信息
新增部门-表单验证
完成表单基本校验条件
表单验证条件
- 部门名称(name):必填 1-50个字符 / 同级部门中禁止出现重复部门
- 部门编码(code):必填 1-50个字符 / 部门编码在整个模块中都不允许重复
- 部门负责人(manager):必填
- 部门介绍 ( introduce):必填 1-300个字符
form: {
name: '', // 部门名称
code: '', // 部门编码
manager: '', // 部门管理者
introduce: '' // 部门介绍
},
- 完成表单校验需要的前置条件
-
el-form配置model和rules属性
-
el-form-item配置prop属性
-
表单进行v-model双向绑定
-
<template>
<!-- 新增部门的弹层 -->
<el-dialog title="新增部门" :visible="showDialog">
<!-- 表单组件 el-form label-width设置label的宽度 -->
<!-- 匿名插槽 -->
<el-form label-width="120px" :model="form" :rules="rules" ref="addForm">
<el-form-item label="部门名称" prop="name">
<el-input v-model="form.name" style="width:80%" placeholder="1-50个字符" />
</el-form-item>
<el-form-item label="部门编码" prop="code">
<el-input v-model="form.code" style="width:80%" placeholder="1-50个字符" />
</el-form-item>
<el-form-item label="部门负责人" prop="manager">
<el-select style="width:80%" placeholder="请选择" />
</el-form-item>
<el-form-item label="部门介绍" prop="introduce">
<el-input v-model="form.introduce" style="width:80%" placeholder="1-300个字符" type="textarea" :rows="3" />
</el-form-item>
</el-form>
<!-- el-dialog有专门放置底部操作栏的 插槽 具名插槽 -->
<div slot="footer">
<el-button type="primary" size="small">确定</el-button>
<el-button size="small">取消</el-button>
</div>
</el-dialog>
</template>
总结:基于ElementUI表单验证:
- 绑定el-form的model和rules属性
- 绑定el-form-item的prop属性
- 绑定el-input的v-model指令
配置校验规则
- 定义校验规则
rules: {
name: [
{ required: true, message: '部门名称不能为空', trigger: ['blur', 'change'] },
{ min: 1, max: 50, message: '部门名称要求1-50个字符', trigger: ['blur', 'change'] }
],
code: [
{ required: true, message: '部门编码不能为空', trigger: ['blur', 'change'] },
{ min: 1, max: 50, message: '部门编码要求1-50个字符', trigger: ['blur', 'change'] }
],
manager: [
{ required: true, message: '部门负责人不能为空', trigger: ['blur', 'change'] }
],
introduce: [
{ required: true, message: '部门介绍不能为空', trigger: ['blur', 'change'] },
{ min: 1, max: 300, message: '部门介绍要求1-300个字符', trigger: ['blur', 'change'] }
]
}
总结:
表单验证基本流程
- el-form绑定model/rules/ref
- el-form-item绑定prop
- 表单输入域双向数据绑定
部门名称和部门编码的[自定义校验]
**注意**
:部门名称和部门编码的规则 有两条我们需要通过**自定义校验函数validator**
来实现,且判断部门名称和编码是否存在, 需要遍历, 推荐将来校验用
blur
, 失去焦点校验一次即可
- 部门名称(name):同级部门中禁止出现重复部门
- 部门编码(code):部门编码在整个模块中都不允许重复
- 保存原始部门列表数据
// 获取原始的组织架构列表
async loadDepartments () {
try {
const ret = await reqGetDepartments()
this.company.name = ret.data.companyName
// 把列表数据转换为树形数据
+ this.dlist = ret.data.depts
this.departs = this.translateListToTreeData(ret.data.depts, '')
} catch {
this.$message.error('获取组织架构数据失败')
}
}
// data中添加一行数据
// 原始的组织列表数据
+ list: [],
- 把原始部门列表数据传递给add-dept.vue组件
<add-dept :list="dlist" :dept-id="currentDeptId" :is-show-dept.sync="isShowDept" />
- 子组件接收原始列表数据
props: {
// 控制弹窗
isShowDept: {
type: Boolean,
required: true
},
// 父级部门的id
deptId: {
type: String,
required: true
},
// 所有部门的数据
list: {
type: Array,
required: true
}
},
- 提供自定义校验函数
data () {
// 自定义验证规则:验证部门名称
const validateName = (rule, value, callback) => {
// 同级部门的名称不可以重复
// 1、找到同级部门信息:根据父级部门的id
const subDepts = this.list.filter(item => {
return item.pid === this.deptId
})
// 2、判断输入的部门名称和同级部门名称是否冲突
const flag = subDepts.some(item => {
return item.name === value
})
// 判断存在性
if (flag) {
// 重复了
callback(new Error('同级部门名称不可以重复'))
} else {
// 不重复
callback()
}
}
// 自定义验证规则:验证部门编码
const validateCode = (rule, value, callback) => {
// 部门编码全局不可以重复
const flag = this.list.some(item => {
return item.code === value
})
if (flag) {
// 重复了
callback(new Error('部门编码重复了'))
} else {
// 不重复
callback()
}
}
return {
...
rules: {
name: [
{ required: true, message: '部门名称不能为空', trigger: ['blur', 'change'] },
{ min: 1, max: 50, message: '部门名称要求1-50个字符', trigger: ['blur', 'change'] },
{ validator: validateName, trigger: 'blur' }
],
code: [
{ required: true, message: '部门编码不能为空', trigger: ['blur', 'change'] },
{ min: 1, max: 50, message: '部门编码要求1-50个字符', trigger: ['blur', 'change'] },
{ validator: validateCode, trigger: 'blur' }
],
}
}
}
总结:
- 表单的自定义验证规则 validator
- 数组相关API的用法filter/some
- 父组件向子组件传递数据
处理首部内容的pid数据
这里添加根级别的部门, 需要我们手动处理下, id 需要指定为 '', 就表示是所有根级别的父节点
- 点击首部【添加子部门】实际上添加的是一级部门(一级部门的pid应该是'')
// 公司信息
company: {
name: '江苏传智播客教育科技股份有限公司',
manager: '负责人',
root: true,
+ id: ''
},
- 添加弹层控制
<tree-tools :node-data="titleData" @on-success="onSuccess" @add-dept="addDept" />
总结:点击标题中的【添加子部门】按钮,应该传递一级部门的父id,他的值为 ‘’
新增部门-部门负责人
**目标**
:获取新增表单中的部门负责人下拉数据在上节的表单中,部门负责人是下拉数据,我们应该从
**员工接口**
中获取该数据
- 首先,封装获取简单员工列表的模块
**src/api/employees.js**
import request from '@/utils/request'
// 获取员工的简单列表
export function getEmployeeSimple() {
return request({
url: '/sys/user/simple'
})
}
- created中获取员工列表
import { reqGetEmployeeSimple } from '@/api/employees'
// 负责人列表数据
plist: [],
// 触发接口调用
async handleOpen () {
// 弹窗打开时触发
try {
const ret = await getEmployeeSimple()
this.elist = ret.data
} catch {
this.$message.error('获取员工信息失败')
}
},
- 循环渲染
<el-form-item label="部门负责人" prop="manager">
<el-select v-model="form.manager" style="width:80%" placeholder="请选择">
<el-option v-for="item in elist" :key="item.id" :value="item.username" :label="item.username" />
</el-select>
</el-form-item>
总结:调接口;获取数据;填充列表
注意:调用接口的时机(打开弹窗时调用)
新增部门-提交表单
校验通过调用新增接口
当点击新增页面的确定按钮时,我们需要完成对表单的整体校验,如果校验成功,进行提交
- 首先,在点击确定时,校验表单
<el-form ref="addForm" label-width="120px" :model="form" :rules="rules">
<el-button @click='handleSubmit' type="primary" size="small">确定</el-button>
// 提交表单
handleSubmit() {
this.$refs.addForm.validate(valid => {
if (valid) {
// 表示可以提交了
}
})
}
总结:需要手动验证表单,因为可能会直接点击提交按钮
validate方法是在哪里定义的?el-form组件中定义的
封装新增接口
- 首先, 封装新增部门的api模块
**src/api/departments.js**
export function reqAddDepartments(data) {
return request({
url: '/company/department',
method: 'post',
data
})
}
因为是添加子部门,所以我们需要将新增的部门pid设置成当前部门的id,新增的部门就成了自己的子部门
同样,在新增成功之后,调用告诉父组件,重新拉取数据
// 添加部门提交表单
handleSubmit () {
// 手动触发表单验证
this.$refs.addForm.validate(async valid => {
if (valid) {
// 表单验证通过
try {
await reqAddDepartments({
...this.form,
pid: this.deptId
})
// 关闭窗口,刷新列表
this.handleClose()
this.$emit('on-success')
} catch {
this.$message.error('添加部门失败')
}
}
})
},
- 父组件
<tree-tools :node-data="scope.data" @on-success="onSuccess" @add-dept="addDept" />
总结:
- 手动表单验证
- 添加部门需要指定父级部门pid
- 子组件向父组件传值
取消按钮操作
功能:1、清空表单;2、关闭弹窗
handleClose () {
// 控制关闭碳层
this.$refs.addForm.resetFields()
// this.$emit('hide-addbox')
this.$emit('update:showDialog', false)
}
<el-button @click='handleClose' size="small">取消</el-button>
总结:重置表单的简单用法,基于ElementUI的el-form组件的实例方法resetFields实现。
注意:ref可以直接操作组件实例对象
编辑部门
展示编辑部门弹窗
编辑部门功能实际上 可以和 新增窗体采用同一个组件,只不过我们需要将新增场景变成编辑场景
- 首先点击编辑部门时,子传父
**tree-tools.vue**
if (type === 'add') {
// 添加部门
this.$emit('show-addbox', {
...this.nodeData,
type
})
} else if (type === 'edit') {
// 编辑部门
this.$emit('edit-dept', this.nodeData.id)
}
- 父组件监听事件
<tree-tools :node-data="scope.data" @on-success="onSuccess" @add-dept="addDept" @edit-dept="editDept" />
editDept (id) {
// 点击编辑按钮,显示编辑弹窗
this.currentDeptId = id
this.isShowDeptEdit = true
},
总结:点击编辑按钮弹窗
- 子向父组件传值
- ElementUI弹窗组件基本使用
获取部门数据并回填表单
- 封装获取部门信息的模块
**src/api/departments.js**
// 获取部门详情数据
export function reqGetDepartDetail(id) {
return request({
url: `/company/department/${id}`
})
}
- 回调表单
// 获取负责人列表数据
async handleOpen () {
try {
// 获取负责人列表信息
const ret = await getEmployeeSimple()
this.plist = ret.data
// 获取整个表单的数据(部门的原始信息)
const info = await reqGetDepartDetail(this.deptId)
this.form = info.data
} catch {
this.$message.error('获取部门数据失败')
}
},
总结:调用接口;获取数据;填充表单
注意:编辑时,需要使用this.deptId (当前部门的id)
编辑提交
接下来,需要在点击确定时,同时支持
新增部门
和编辑部门
两个场景,可以根据form
是否有id进行区分
- 封装编辑部门接口
**src/api/departments.js**
// 编辑部门
export function reqUpdateDepartments(data) {
return request({
url: `/company/department/${data.id}`,
method: 'put',
data
})
}
- 点击确定时,进行场景区分
handleSubmit () {
// 手动触发表单验证
this.$refs.addForm.validate(async valid => {
if (valid) {
// 表单验证通过
try {
await reqUpdateDepartments(this.form)
// 关闭窗口,刷新列表
this.handleClose()
this.$emit('on-success')
} catch {
this.$message.error('编辑部门失败')
}
}
})
},
总结:调用接口提交表单,关闭弹窗,刷新列表
- 子向父组件传值
编辑表单验证
除此之外,我们发现原来的校验规则实际和编辑部门有些冲突,所以需要进一步处理
- 部门名称的重复验证(编辑的时候,需要验证同级部门名称是否重复;编辑的当前部门名称可以重复,但是当前部门名称不可以和同级部门名称重复)
- 部门编号的重复验证(编辑的时候,需要验证全局是否有编号重复,编辑的当前部门的编号可以重复,但是当前部门的编号不可以和全局其他部门的编号重复)
// 验证部门名称
// 自定义验证规则:验证部门名称
const validateName = (rule, value, callback) => {
// 同级部门的名称不可以重复
// 1、找到同级部门信息:根据父级部门的id
const depts = this.list.filter(item => {
// 查找当前部门的同级部门(this.deptInfo.pid表示当前编辑部门的父级部门的id)
return item.pid === this.deptInfo.pid
})
// 2、判断输入的部门名称和同级部门名称是否冲突
// 同级部门名称不可以重复,但是当前编辑的部门的名称可以重复
const flag = depts.some(item => {
return item.name === value && item.id !== this.deptInfo.id
})
// 判断存在性
if (flag) {
// 重复了
callback(new Error('同级部门名称不可以重复'))
} else {
// 不重复
callback()
}
}
// 验证部门编码
// 自定义验证规则:验证部门编码
const validateCode = (rule, value, callback) => {
// 部门编码全局不可以重复
const flag = this.list.some(item => {
// 把当前要编辑的部门编号排除掉
return item.code === value && item.code !== this.deptInfo.code
})
if (flag) {
// 重复了
callback(new Error('部门编码重复了'))
} else {
// 不重复
callback()
}
}
总结:
- 验证编辑的部门名称时,需要注意:部门名称可以不做修改,验证要保证通过
- 验证部门编码时要保证全局不重复,但是编辑的部门编码可以不修改,验证要保证通过
由于获取数据的延迟性,为了更好的体验,可以给页面增加一个Loading进度条,采用element的指令解决方案即可
- 定义loading变量
loading: false // 用来控制进度弹层的显示和隐藏
- 赋值变量给指令
<div v-loading="loading" class="departments-container">
- 获取方法前后设置变量
// 获取原始的组织架构列表
async loadDepartments () {
try {
this.loading = true
const ret = await reqGetDepartments()
this.company.name = ret.data.companyName
// 把列表数据转换为树形数据
this.list = JSON.parse(JSON.stringify(ret.data.depts))
this.departs = this.translateListToTreeData(ret.data.depts, '')
} catch {
this.$message.error('获取组织架构数据失败')
}
this.loading = false
}