目标
:使用element-UI组件布局组织架构的基本布局
<el-card class="tree-card">
<!-- 用了一个行列布局 -->
<el-row type="flex" justify="space-between" align="middle" style="height: 40px">
<el-col>
<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 140px;
font-size:14px;
}
</style>
<!--放置一个属性 这里的props和我们之前学习的父传子 的props没关系-->
<el-tree :data="departs" :props="defaultProps" />
export default {
data() {
return {
defaultProps: {
label: 'name'
},
departs: [{
name: '总裁办', children: [{
name: '董事会' }] },
{
name: '行政部' }, {
name: '人事部' }]
}
}
}
<el-tree :data="departs" :props="defaultProps" :default-expand-all="true">
<!-- 传入内容 插槽内容 会循环多次 有多少节点 就循环多少次 -->
<!-- 作用域插槽 slot-scope="obj" 接收传递给插槽的数据 data 每个节点的数据对象-->
<el-row slot-scope="{ data }" type="flex" justify="space-between" align="middle" style="height: 40px; width: 100%">
<el-col>
<!-- 左侧内容 -->
<span>{
{
data.name }}</span>
</el-col>
<el-col :span="4">
<el-row type="flex" justify="end">
<el-col>{
{
data.manager }}</el-col>
<el-col>
<!-- 放置下拉菜单 -->
<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> </el-tree>
<script>
export default {
data() {
return {
departs: [{
name: '总裁办', manager: '曹操', children: [{
name: '董事会', manager: '曹丕' }] },
{
name: '行政部', manager: '刘备' },
{
name: '人事部', manager: '孙权' }],
defaultProps: {
label: 'name' // 表示 从这个属性显示内容
}
}
}
}
</script>
目标
: 将树形的操作内容单独抽提成组件
通过封装,代码看上去更加紧凑,简洁,这就是封装的魅力
本节任务
:将树形内容单独抽提组件
src/views/departments/components/tree-tools.vue
<template>
<el-row type="flex" justify="space-between" align="middle" style="height: 40px;width: 100%">
<el-col>
<!-- 名称应该变成 对应的节点中的name -->
<span>{
{
treeNode.name }}</span>
</el-col>
<el-col :span="4">
<el-row type="flex" justify="end">
<!-- 两个内容 -->
<el-col>{
{
treeNode.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>
// 该组件需要对外开放属性 外部需要提供一个对象 对象里需要有name manager
export default {
// props可以用数组来接收数据 也可以用对象来接收
// props: { props属性: { 配置选项 } }
props: {
// 定义一个props属性
treeNode: {
type: Object, // 对象类型
required: true // 要求对方使用您的组件的时候 必须传treeNode属性 如果不传 就会报错
}
}
}
</script>
src/views/departments/index.vue
**进行代码的简化<template>
<div class="dashboard-container">
<div class="app-container">
<!-- 实现页面的基本布局 -->
<el-card class="tree-card">
<!-- 用了一个行列布局 -->
<!-- 缺少treeNode -->
<tree-tools :tree-node="company" />
<!--放置一个属性 这里的props和我们之前学习的父传子 的props没关系-->
<el-tree :data="departs" :props="defaultProps" default-expand-all>
<!-- 说明el-tree里面的这个内容 就是插槽内容 => 填坑内容 => 有多少个节点循环多少次 -->
<!-- scope-scope 是 tree组件传给每个节点的插槽的内容的数据 -->
<!-- 顺序一定是 执行slot-scope的赋值 才去执行 props的传值 -->
<tree-tools slot-scope="{ data }" :tree-node="data" />
</el-tree>
</el-card>
</div>
</div>
</template>
company: {
name: '江苏传智播客教育科技股份有限公司', manager: '负责人' },
删除部门
和编辑部门
**的isRoot(是否根节点)
**进行控制 props: {
treeNode: {
required: true, // 设置当前数据为必填
type: Object // 类型是Object
},
isRoot: {
type: Boolean,
default: false
}
}
<tree-tools :tree-node="company" :is-root="true" />
<!-- 编辑部门和删除部门只会在子节点上显示 -->
<el-dropdown-item v-if="!isRoot">编辑部门</el-dropdown-item>
<el-dropdown-item v-if="!isRoot">删除部门</el-dropdown-item>
**目标
**获取真实的组织架构数据,并将其转化成树形数据显示在页面上
现在基本的静态结构已经形成,接下来需要获取真实的数据
src/api/departments.js
/** *
*
* 获取组织架构数据
* **/
export function getDepartments() {
return request({
url: '/company/department',
method: 'get' // 默认是 get 可以不写
})
}
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 = transListToTreeData(result.depts, '')
**目标
**实现操作功能的删除功能
src/api/departments.js
/** *
* 根据id根据部门 接口是根据restful的规则设计的 删除 delete 新增 post 修改put 获取 get
* **/
export function delDepartments(id) {
return request({
url: `/company/department/${
id}`,
method: 'delete'
})
}
src/views/departments/index.vue
<el-dropdown @command="operateDepts">
<span>
操作<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>
// 操作节点调用的方法
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
<tree-tools slot-scope="obj" :tree-node="obj.data" @delDepts="getDepartments" />
目标
:实现新增部门功能的组件建立
src/api/departments.js
/**
* 新增部门接口
*
* ****/
export function addDepartments(data) {
return request({
url: '/company/department',
method: 'post',
data
})
}
src/views/department/components/add-dept.vue
<template>
<!-- 新增部门的弹层 -->
<el-dialog title="新增部门">
<!-- 表单组件 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有专门放置底部操作栏的 插槽 具名插槽 -->
<el-row slot="footer" type="flex" justify="center">
<!-- 列被分为24 -->
<el-col :span="6">
<el-button type="primary" size="small">确定</el-button>
<el-button size="small">取消</el-button>
</el-col>
</el-row>
</el-dialog>
</template>
// 需要传入一个props变量来控制 显示或者隐藏
props: {
showDialog: {
type: Boolean,
default: false
}
}
<el-dialog title="新增部门" :visible="showDialog">
departments/index.vue
** 中引入该组件import AddDept from './components/add-dept' // 引入新增部门组件
export default {
components: {
AddDept }
}
showDialog
** data() {
return {
showDialog: false // 显示窗体
}
},
<!-- 放置新增弹层组件 -->
<add-dept :show-dialog="showDialog" />
src/views/departments/tree-tools.vue
if (type === 'add') {
// 添加子部门的操作
// 告诉父组件 显示弹层
this.$emit('addDepts', this.treeNode) // 为何传出treeNode 因为是添加子部门 需要当前部门的数据
}
<tree-tools slot-scope="obj" :tree-node="obj.data" @delDepts="getDepartments" @addDepts="addDepts" />
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
}
},
<add-dept :show-dialog="showDialog" :tree-node="node" />
// 现在定义一个函数 这个函数的目的是 去找 同级部门下 是否有重复的部门名称
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()
}
{
trigger: 'blur', validator: checkNameRepeat // 自定义函数的形式校验 }
{
trigger: 'blur', validator: checkCodeRepeat// 自定义函数的形式校验 }
需要注意
:在最根级的**tree-tools
**组件中,由于treenode属性中没有id,id便是undefined,但是通过undefined进行等值判断是寻找不到对应的根节点的, 所以在传值时,我们将id属性设置为 “”
src/views/departments/index.vue
async getDepartments() {
const result = await getDepartments()
this.departs = transListToTreeData(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
**中调用该接口,因为我们要获取实时的最新数据 <el-select v-model="formData.manager" style="width:80%" placeholder="请选择" @focus="getEmployeeSimple">
<!-- 需要循环生成选项 这里做一下简单的处理 显示的是用户名 存的也是用户名-->
<el-option v-for="item in peoples" :key="item.id" :label="item.username" :value="item.username" />
</el-select>
import {
getEmployeeSimple } from '@/api/employees'
methods: {
// 获取员工简单列表数据
async getEmployeeSimple() {
this.peoples = await getEmployeeSimple()
}
}
peoples: [] // 接收获取的员工简单列表的数据
目标
: 完成新增模块的提交-取消-关闭等功能
本节注意
:同学们可能会疑惑,我们**tree-tools.vue
** 和**add-dept.vue
**两个组件都触发了addDepts事件,不冲突吗? (类似 @click 的意思)
这里,我们触发的自定义事件都是组件自身的,他们之间没有任何关系,只是名字相同而已,大家不要混淆
当点击新增页面的确定按钮时,我们需要完成对表单的整体校验,如果校验成功,进行提交
首先,在点击确定时,校验表单
<el-form ref="deptForm" :model="formData" :rules="rules" label-width="120px">
// 点击确定时触发
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')
<add-dept :show-dialog="showDialog" :tree-node="node" @addDepts="getDepartments" />
这里我们学习一个新的技巧,
sync修饰符
showDialog
**的话,需要这样做// 子组件
this.$emit('changedialog', false) //触发事件
// 父组件
<child @changedialog="method" :showDialog="showDialog" />
method(value) {
this.showDialog = value
}
sync修饰符
**,它提供了一种简写模式 也就是// 子组件 update:固定写法 (update:props名称, 值)
this.$emit('update:showDialog', false) //触发事件
// 父组件 sync修饰符
<child :showDialog.sync="showDialog" />
// 点击确定时触发
btnOK() {
this.$refs.deptForm.validate(async isOK => {
if (isOK) {
// 表示可以提交了
await addDepartments({
...this.formData, pid: this.treeNode.id }) // 调用新增接口 添加父部门的id
this.$emit('addDepts') // 告诉父组件 新增数据成功 重新拉取数据
// update:props名称
this.$emit('update:showDialog', false)
}
})
}
btnCancel() {
this.$refs.deptForm.resetFields() // 重置校验字段
this.$emit('update:showDialog', false) // 关闭
}
<el-dialog title="新增部门" :visible="showDialog" @close="btnCancel">
目标
:实现编辑部门的功能
编辑部门功能实际上和新增窗体采用的是一个组件,只不过我们需要将新增场景变成编辑场景
tree-tools.vue
this.$emit('editDepts', this.treeNode)
<tree-tools slot-scope="obj" :tree-node="obj.data" @delDepts="getDepartments" @editDepts="editDepts" />
// 编辑部门节点
editDepts(node) {
// 首先打开弹层
this.showDialog = true
this.node = node // 赋值操作的节点
}
编辑时,我们需要获取点击部门的信息
src/api/departments.js
/** *
* 获取部门详情
* ***/
export function getDepartDetail(id) {
return request({
url: `/company/department/${
id}`
})
}
editDepts
中通过ref
调用add-dept.vue
**的实例方法 // 获取部门详情
async getDepartDetail(id) {
this.formData = await getDepartDetail(id)
}
//先设置 ref
<AddDept ref="addDept" :showDialog.sync="showDialog" :tree-node="node" @addDepts="getDepartments"></AddDept>
// 点击编辑触发的父组件的方法
editDepts(node) {
this.showDialog = true // 显示新增组件弹层
this.node = node // 存储传递过来的node数据
// 我们需要在这个位置 调用子组件的方法
// 父组件 调用子组件的方法
this.$refs.addDept.getDepartDetail(node.id) // 直接调用子组件中的方法 传入一个id
}
需要根据当前的场景区分显示的标题
如何判断新增还是编辑
computed: {
showTitle() {
return this.formData.id ? '编辑部门' : '新增子部门'
}
},
btnCancel() {
// 重置数据 因为resetFields 只能重置 表单上的数据 非表单上的 比如 编辑中id 不能重置
this.formData = {
name: '',
code: '',
manager: '',
introduce: ''
}
// 关闭弹层
this.$emit('update:showDialog', false)
// 清除之前的校验 可以重置数据 只能重置 定义在data中的数据
this.$refs.deptForm.resetFields()
}
接下来,需要在点击确定时,同时支持新增部门和编辑部门两个场景,我们可以根据formData是否有id进行区分
src/api/departments.js
/**
* 编辑部门
*
* ***/
export function updateDepartments(data) {
return request({
url: `/company/department/${
data.id}`,
method: 'put',
data
})
}
// 点击确定时触发
btnOK() {
this.$refs.deptForm.validate(async isOK => {
if (isOK) {
// 要分清楚现在是编辑还是新增
if (this.formData.id) {
// 编辑模式 调用编辑接口
await updateDepartments(this.formData)
} else {
// 新增模式
await addDepartments({
...this.formData, pid: this.treeNode.id }) // 调用新增接口 添加父部门的id
}
// 表示可以提交了
this.$emit('addDepts') // 告诉父组件 新增数据成功 重新拉取数据
// update:props名称
this.$emit('update:showDialog', false)
}
})
},
// 现在定义一个函数 这个函数的目的是 去找 同级部门下 是否有重复的部门名称
const checkNameRepeat = async(rule, value, callback) => {
// 先要获取最新的组织架构数据
const {
depts } = await getDepartments()
// 检查重复规则 需要支持两种 新增模式 / 编辑模式
// depts是所有的部门数据
// 如何去找技术部所有的子节点
let isRepeat = false
if (this.formData.id) {
// 有id就是编辑模式
// 编辑 张三 => 校验规则 除了我之外 同级部门下 不能有叫张三的
isRepeat = depts.filter(item => item.id !== this.formData.id && item.pid === this.treeNode.pid).some(item => item.name === value)
} else {
// 没id就是新增模式
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()
let isRepeat = false
if (this.formData.id) {
// 编辑模式 因为编辑模式下 不能算自己
isRepeat = depts.some(item => item.id !== this.formData.id && item.code === value && value)
} else {
// 新增模式
isRepeat = depts.some(item => item.code === value && value) // 这里加一个 value不为空 因为我们的部门有可能没有code
}
isRepeat ? callback(new Error(`组织架构中已经有部门使用${
value}编码`)) : callback()
}
目标
给当前组织架构添加加载进度条
由于获取数据的延迟性,为了更好的体验,可以给页面增加一个Loading进度条,采用element的指令解决方案即可
loading: false // 用来控制进度弹层的显示和隐藏
<div v-loading="loading" class="dashboard-container">
async getDepartments() {
this.loading = true
const result = await getDepartments()
this.departs = transListToTreeData(result.depts, '')
this.company = {
name: result.companyName, manager: '负责人', id: '' }
this.loading = false
}