# 组织架构模块
# 业务功能介绍

说明:组织架构模块主要管理公司所有部门信息,支持添加、删除、编辑操作
# 基本组件布局
目标:使用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>
总结:
- 栅格系统的基本使用
- 栅格中可以再次嵌套栅格
- 栅格组件支持相关的布局熟悉
# 树形组件用法
目标:熟悉树形组件的基本用法
- 树形组件关键属性
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---|---|---|---|---|
| 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 配置数据的属性名称
# 实现树形的静态组织架构
目标:基于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组件 -->
<el-tree
:data="departs"
:props="defaultProps"
:default-expand-all="true"
>
<!-- 用了一个行列布局 -->
<template #default="{ data }">
<el-row
type="flex"
justify="space-between"
align="middle"
style="height: 40px; width: 100%;"
>
<el-col :span="20">
<span>{{ data.name }}</span>
</el-col>
<el-col :span="4">
<el-row type="flex" justify="end">
<!-- 两个内容 -->
<el-col>{{ 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表示每一行数据的相关状态属性(比如是否展开等信息))
# 回顾
- 路由配置
- 静态路由和动态路由的概念
- 初始化业务模块
- 配置组织架构的路由
- 左侧菜单的基本架构
- 动态组件的用法;动态组件中插槽的用法
- v-bind的值如果是一个对象,那么对象的所有属性都会绑定到标签上
- 拆分路由模块
- 导入拆分的路由形成动态路由数组并且与静态路由进行合并
- 控制左侧菜单的点击高亮
- 组织架构
- 熟悉组织架构的基本业务
- 熟悉树形组件的基本使用
- data 提供数据
- props 用于配置数据的属性映射
- 基于静态数据定制树形组件的布局
- 作用域插槽:定制列表中的条目的布局
- 获取条目数据的方式
v-slot=‘scope’v-slot:default='scope'#default='scope'
- scope.data 条目的数据
- scope.node 条目的状态信息
# 拆分组织架构组件
目标: 将树形的操作内容单独抽提成组件
src/views/departments/components/tree-tools.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>编辑部分</el-dropdown-item>
<el-dropdown-item>删除部门</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-col>
</el-row>
</el-col>
</el-row>
</template>
<script>
export default {
props: {
// 定义传入的属性
nodeData: {
type: Object,
required: true
}
}
}
</script>
- 在组织架构中应用组件**
src/views/departments/index.vue**
<template>
<div class="departments-container">
<div class="app-container">
<el-card class="tree-card">
<!-- 用了一个行列布局 -->
<tree-tools :node-data="company" />
<!-- 放置一个el-tree组件 -->
<el-tree
:data="departs"
:props="defaultProps"
:default-expand-all="true"
>
<!-- 用了一个行列布局 -->
<template #default="{ data }">
<tree-tools :node-data="data" />
</template>
</el-tree>
</el-card>
</div>
</div>
</template>
- 上面代码中,company变量需要在data中定义
company: { name: '江苏传智播客教育科技股份有限公司', manager: '负责人' },
同时,由于在两个位置都使用了该组件,但是放置在最上层的组件是不需要显示 **
删除部门和编辑部门**的所以,增加一个新的属性 **
isRoot(是否根节点)**进行控制
export default {
props: {
// 节点的内容展示
nodeData: {
type: Object,
required: true
},
// 是否根节点
isRoot: {
type: Boolean,
default: false
}
}
}
<tree-tools :node-data="company" :is-root="true" />
- 组件中, 根据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="!isRoot">编辑部分</el-dropdown-item>
<el-dropdown-item v-if="!isRoot">删除部门</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
总结:
- 封装基本的树形条目的组件(抽取动态属性)
- 导入并使用组件,然后把数据注入进去
# 配置本地接口环境
安装MongoDB数据库(不要勾选 install mongoDB compass )
配置环境变量(sysdm.cpl)
启动MongoDB数据库(命令行输入 mongod;如果没有配置环境变量,需要进入数据库的安装位置运行命令 C:\Program Files\MongoDB\Server\4.2\bin)
下载后端接口代码(Word文档中有地址)
运行后端接口项目
- npm install
- npm start 初始化数据库(仅仅需要执行一次即可)
- npm run serve 启动项目
可以通过 http://localhost:3000/ 地址测试接口是否可以访问
修改项目中的代理的目标地址
proxy: {
// 所有的请求路径以api开始的地址都会被代理
// 发送的请求 http://localhost:9528/api/login
// 代理的目标 http://ihrm-java.itheima.net/api/login
'^/api': {
// 代理的目标地址
// target: 'http://ihrm-java.itheima.net'
target: 'http://localhost:3000'
}
}
- 重启前端项目
# 获取组织架构接口数据
**
目标**获取真实的组织架构数据,并将其转化成树形数据显示在页面上
- 封装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.company.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
},
- 调用转化方法,转化树形结构
methods: {
// 获取原始的组织架构列表
async loadDepartments () {
try {
const ret = await reqGetDepartments()
this.company.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
methods: {
handleAction (type) {
if (type === 'add') {
// 添加部门
} else if (type === 'edit') {
// 编辑部门
} else if (type === 'del') {
// 删除部门
this.$confirm('确认要删除部门吗?, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 确认删除
reqDelDepartments(this.nodeData.id)
// 通知父组件刷新列表
this.$emit('on-success')
}).catch((e) => {
if (e === 'cancel') {
// 点击了取消
this.$message({
type: 'info',
message: '已取消删除'
})
} else {
// 调用接口异常
this.$message({
type: 'error',
message: '删除部门失败'
})
}
})
}
}
}
这边也可以自己注册三个点击事件, 但是自己注册需要 @click.native = "delFn"
- 用删除接口,通知父组件更新数据
删除之前,提示用户是否删除,然后调用删除接口
但是怎么通知父组件进行更新呢? 子传父, 通知父组件
this.$emit('on-success')
- 父组件监听事件
src/views/department/index.vue
<tree-tools @on-success='onSuccess' :node-data="scope.data" />
总结
- 绑定点击事件(基于Element组件进行绑定;基于原生click事件绑定)
- 删除需要提示
- 删除成功后需要通知父组件刷新列表
# 新增部门-基本功能
# 新建组件-准备弹层
我们需要构建一个新增部门的窗体组件
src/views/department/components/add-dept.vue
- 准备弹窗组件
- 控制弹窗的显示和隐藏
- 弹窗的基本布局
- 基本布局
<template>
<!-- 放置弹层组件 -->
<el-dialog title="新增部门" :visible="showDialog">
我是添加的弹层
</el-dialog>
</template>
<script>
export default {
// 需要传入一个props变量来控制 显示或者隐藏
props: {
showDialog: {
type: Boolean,
required: true
}
}
}
</script>
- 在**
departments/index.vue** 中引入该组件
import AddDept from './components/add-dept'
import TreeTools from './components/tree-tools'
export default {
components: {
TreeTools,
AddDept
},
}
- 定义控制窗体显示的变量**
showDialog**
<add-dept :showDialog="showDialog"/>
data() {
return {
...
showDialog: true
}
},
总结:
- index.vue把状态位传递给add-dept.vue组件
- 点击添加部门按钮事件发生在tree-tools.vue组件中
# 点击 x 关闭弹层 (子传父)
控制添加部门组件的弹窗打开和关闭
add-dept.vue给 dialog 注册 close 事件
<el-dialog title="添加部门" :visible="showDialog" @close="handleClose">
add-dept.vue子传父, 传递 false
methods: {
handleClose() {
// 子传父
this.$emit('hide-addbox')
}
}
index.vue父组件中, 关闭弹层
<add-dept @hide-addbox="showDialog=false" :showDialog="showDialog"/>
<!-- 简化写法 -->
<add-dept :showDialog.sync='showDialog' />
this.$emit('update:showDialog', false)
总结:
- 监听el-dialog组件的close事件
- 事件函数中通知父组件关闭弹窗
# 准备弹层的结构内容
<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/tree-tools.vue
if (type === 'add') {
// 添加部门
this.$emit('show-addbox', this.nodeData)
}
- 父组件监听事件
<tree-tools @show-addbox="showAddbox" @on-success='onSuccess' :node-data="scope.data" />
- 方法中弹出层,记录在哪个节点下添加子部门
data () {
return {
currentDept: {}
}
}
// 处理添加操作, 显示弹层
showAddbox (data) {
// 控制弹窗的显示
this.showDialog = true
// 记录当前部门的信息
this.currentDept = data
},
总结
- 点击添加子部门时,需要记录当前部门的信息
- 这个部门信息需要传递给add-dept.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>
# 配置校验规则
- 定义校验规则
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.list = JSON.parse(JSON.stringify(ret.data.depts))
this.departs = this.translateListToTreeData(ret.data.depts, '')
} catch {
this.$message.error('获取组织架构数据失败')
}
}
// data中添加一行数据
// 原始的组织列表数据
+ list: [],
- 把原始部门列表数据传递给add-dept.vue组件
<add-dept :list="list" :node-data="currentDept" :showDialog.sync="showDialog" />
- 子组件接收原始列表数据
props: {
showDialog: {
type: Boolean,
required: true
},
// 当前部门信息
nodeData: {
type: Object,
required: true
},
// 原始部门列表数据
+ list: {
+ type: Array,
+ required: true
+ }
},
- 提供自定义校验函数
data () {
// 验证部门名称
const checkNameRepeat = (rule, value, callback) => {
// 查询当前部门的子部门
const children = this.list.filter(item => {
return item.pid === this.nodeData.id
})
// 判断children子部门列表中是否包含输入的部门名称value
const flag = children.some(item => {
return item.name === value
})
if (flag) {
// 部门名称重复了
callback(new Error('部门名称重复!'))
} else {
callback()
}
}
// 验证部门编码
const checkCodeRepeat = (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: checkNameRepeat, trigger: 'blur' }
],
code: [
{ required: true, message: '部门编码不能为空', trigger: ['blur', 'change'] },
{ min: 1, max: 50, message: '部门编码要求1-50个字符', trigger: ['blur', 'change'] },
{ validator: checkCodeRepeat, trigger: 'blur' }
],
}
}
}
总结:
- 表单的自定义验证规则
- 数组相关API的用法filter/some
- 父组件向子组件传递数据
# 处理首部内容的pid数据
这里添加根级别的部门, 需要我们手动处理下, id 需要指定为 '', 就表示是所有根级别的父节点
- 点击首部【添加子部门】实际上添加的是一级部门(一级部门的pid应该是'')
// 公司信息
company: {
name: '江苏传智播客教育科技股份有限公司',
manager: '负责人',
+ id: ''
},
- 添加弹层控制
<tree-tools @show-addbox="showAddbox" :node-data="company" :is-root="true" />
# 新增部门-部门负责人
目标:获取新增表单中的部门负责人下拉数据在上节的表单中,部门负责人是下拉数据,我们应该从**
员工接口**中获取该数据
- 首先,封装获取简单员工列表的模块
src/api/employees.js
import request from '@/utils/request'
// 获取员工的简单列表
export function getEmployeeSimple() {
return request({
url: '/sys/user/simple'
})
}
- created中获取员工列表
import { reqGetEmployeeSimple } from '@/api/employees'
// 负责人列表数据
elist: [],
// 触发接口调用
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>
总结:调接口;获取数据;填充列表
注意:调用接口的时机(打开弹窗时调用)
# 如何修改提交后的备注
- 执行命令git commit --amend
- 打开vim编辑器窗口之后,按一次字符a,进入编辑状态(左下角出现【插入】)
- 移动光标,修改黄色的备注信息(井号开始的内容是注释,不需要处理)
- 修改完成后按【Esc】按键,退出编辑模式
- 在英文输入模式下,按 【shift + :】组合键,左下角出现光标
- 输入wq 保存并退出
# 回顾
- 拆解树形组件(树的单个节点 tree-tools.vue)
- 配置本地后台接口环境
- 启动后端接口项目:在项目的根路径(people)执行 npm run serve
- 获取组件架构的接口数据
- 把列表数据转换为树形结构的数据(递归算法)
- 删除部门(原生事件绑定.native;Element组件的事件绑定)
- 添加部门
- 控制弹窗的显示
- 控制弹窗的隐藏
- 属性绑定和事件绑定的用法可以简化为sync修饰符
- 记录当前添加的是那个部门的子部门
- 添加部门的表单验证
- 基本的表单验证(基于ElementUI)
- 验证部门名称和部门编号(自定义表单验证规则;如何获取同级部门信息)
- 添加一级部门的弹窗控制
- 负责人的列表数据的动态渲染
# 新增部门-提交表单
# 校验通过调用新增接口
当点击新增页面的确定按钮时,我们需要完成对表单的整体校验,如果校验成功,进行提交
- 首先,在点击确定时,校验表单
<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,
// 父级部门的id
pid: this.nodeData.id
})
// 关闭弹窗、刷新列表
this.handleClose()
this.$emit('on-success')
} catch {
this.$message.error('添加部门失败')
}
}
})
},
- 父组件
<add-dept @on-success="loadDepartments" :list="list" :node-data="currentDept" :show-dialog.sync="showDialog" />
总结:
- 手动表单验证
- 添加部门需要制定父级部门
- 子组件向父组件传值
# 取消按钮操作
功能:1、清空表单;2、关闭弹窗
handleClose () {
// 控制关闭碳层
this.$refs.addForm.resetFields()
// this.$emit('hide-addbox')
this.$emit('update:showDialog', false)
}
<el-button @click='handleClose' size="small">取消</el-button>
总结:重置表单的简单用法
# 编辑部门
# 展示编辑部门弹窗
编辑部门功能实际上 可以和 新增窗体采用同一个组件,只不过我们需要将新增场景变成编辑场景
- 首先点击编辑部门时,子传父
tree-tools.vue
if (type === 'add') {
// 添加部门
this.$emit('show-addbox', {
...this.nodeData,
type
})
} else if (type === 'edit') {
// 编辑部门
this.$emit('show-addbox', {
...this.nodeData,
type
})
}
# 修改表单标题
- 通过计算属性动态处理表单的标题
computed: {
title () {
return this.nodeData.type === 'add'? '新增部门': '编辑部门'
}
},
- 标题进行动态绑定
<el-dialog :title="title"
总结:需要通过父组件传递的type区分编辑的添加的状态
# 获取部门数据并回填表单
- 封装获取部门信息的模块
src/api/departments.js
// 获取部门详情数据
export function reqGetDepartDetail(id) {
return request({
url: `/company/department/${id}`
})
}
- 回调表单
async handleOpen () {
// 弹窗打开时触发
try {
// 判断是否为编辑状态
if (this.nodeData.type === 'edit') {
// 调用接口获取部门详情数据
const ret = await reqGetDepartDetail(this.nodeData.id)
// 把获取到的部门详情数据填充到表单
this.form = ret.data
}
const ret = await getEmployeeSimple()
this.elist = ret.data
} catch {
this.$message.error('获取员工信息失败')
}
},
总结:调用接口;获取数据;填充表单
注意:编辑时,需要使用this.nodeData.id (当前部门的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) {
if (this.nodeData.type === 'add') {
// 添加部门
try {
// 表单验证通过
await reqAddDepartments({
...this.form,
// 父级部门的id
pid: this.nodeData.id
})
} catch {
this.$message.error('添加部门失败')
}
} else if (this.nodeData.type === 'edit') {
// 编辑部门
try {
await reqUpdateDepartments(this.form)
} catch {
this.$message.error('编辑部门失败')
}
}
// 关闭弹窗、刷新列表
this.handleClose()
this.$emit('on-success')
}
})
},
总结:如果组件进行重用,那么逻辑判断会变得复杂 如果表单非常复杂,不建议编辑和添加功能重用表单(重用后组件本身的逻辑会变复杂,后期维护就麻烦了):单一职责(一个组件就做一件事情)
# 编辑表单验证
除此之外,我们发现原来的校验规则实际和编辑部门有些冲突,所以需要进一步处理
- 部门名称的重复验证(编辑的时候,需要验证同级部门名称是否重复;编辑的当前部门名称可以重复,但是当前部门名称不可以和同级部门名称重复)
- 部门编号的重复验证(编辑的时候,需要验证全局是否有编号重复,编辑的当前部门的编号可以重复,但是当前部门的编号不可以和全局其他部门的编号重复)
// 验证部门名称
const checkNameRepeat = (rule, value, callback) => {
// 添加部门时,判断子部门名称是否重复
// 编辑部门时,判断兄弟部门是否重复
let children = []
if (this.nodeData.type === 'edit') {
// 编辑部门(过滤出来的列表包含自己)
children = this.list.filter(item => {
// 排除当前的部门
return item.pid === this.nodeData.pid && item.id !== this.nodeData.id
})
} else {
// 添加部门
children = this.list.filter(item => {
return item.pid === this.nodeData.id
})
}
// 判断children子部门列表中是否包含输入的部门名称value
const flag = children.some(item => {
return item.name === value
})
if (flag) {
callback(new Error('部门名称重复!'))
} else {
callback()
}
}
// 验证部门编码
const checkCodeRepeat = (rule, value, callback) => {
const flag = this.list.some(item => {
if (this.nodeData.type === 'add') {
return item.code === value
} else if (this.nodeData.type === 'edit') {
// 如果是编辑部门,需要排除当前编辑的部门编号
return item.code === value && item.id !== this.nodeData.id
}
})
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
}
总结:控制加载状态ElementUI提供了一个指令 v-loading