• Vue项目实战之人力资源平台系统(五)组织架构模块


    前言

    一、组织架构模块效果图

    在这里插入图片描述

    二、组织架构模块结构布局

    2.1 将树形结构单独抽离成组件

    在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 v-if="!isRoot">编辑部门</el-dropdown-item>
                  <el-dropdown-item v-if="!isRoot">删除部门</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属性 如果不传 就会报错
        }
            // 由于在两个位置都使用了该组件,但是放置在最上层的组件是不需要显示 删除部门和编辑部门的
        // 所以,增加一个新的属性 isRoot(是否根节点)进行控制
        isRoot: {
          type: Boolean,
          default: false
        }
      }
    }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    2.2 在组织架构模块中应用树形结构组件

    在src/views/departments/index.vue进行代码的简化:

        import TreeTools from './components/tree-tools'
    
        <div class="app-container">
          <!-- 组织架构内容- 头部 -->
          <el-card class="tree-card">
            <!-- 放置结构内容 -->
            <tree-tools :tree-node="company" :is-root="true" @addDepts="addDepts" />
            <!-- 放置一个el-tree -->
            <el-tree :data="departs" :props="defaultProps" :default-expand-all="true">
              <!-- 传入内容 插槽内容 会循环多次 有多少节点 就循环多少次 -->
              <!-- 作用域插槽 slot-scope="obj" 接收传递给插槽的数据   data 每个节点的数据对象-->
              <tree-tools slot-scope="{ data }" :tree-node="data" />
            </el-tree>
          </el-card>
        </div>
    
    export default {
      components: {
        TreeTools
      },
      data() {
        return {
          company: { }, // 就是头部的数据结构
          departs: [],
          defaultProps: {
            label: 'name' // 表示 从这个属性显示内容
          },
        }
      },
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    三、获取组织架构数据,并进行树形处理

    3.1 封装API接口,获取组织架构数据

    首先在src/api/departments.js文件中构建请求:

    /** *
    *
    * 获取组织架构数据
    * **/
    export function getDepartments() {
      return request({
        url: '/company/department'
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    然后在src/views/departments/index.vue中调用接口:

    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 // 需要将其转化成树形结构
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    3.2 将数组数据转化成树形结构

    需要将获取到的列表数据,转化成树形数据,这里需要用到递归算法:
    在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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    回到src/views/departments/index.vue中,导入方法:

    import { tranListToTreeData } from '@/utils'
    
    this.departs = transListToTreeData(result.depts, '')
    
    • 1
    • 2
    • 3

    四、删除部门功能实现

    4.1 封装删除接口

    在src/api/departments.js中添加如下代码:

    /** *
    *  根据id删除部门
    * **/
    export function delDepartments(id) {
      return request({
        url: `/company/department/${id}`,
        method: 'delete'
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4.2 监听下拉菜单的点击事件并调用删除接口

    在src/views/departments/components/tree-tools.vue中添加如下代码:

            <el-col>
              <!-- 放置下拉菜单 -->
              <el-dropdown @command="operateDepts">
                <!-- 内容 -->
                <span>操作
                  <i class="el-icon-arrow-down" />
                </span>
                <!-- 具名插槽 -->
                <el-dropdown-menu slot="dropdown">
                  <!-- 下拉选项 -->
                  <el-dropdown-item command="add" :disabled="!checkPermission('add-dept')">添加子部门</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>
            </el-col>
    
    import { delDepartments } from '@/api/departments'
    
      methods: {
        // 点击 编辑 删除 新增时触发
        operateDepts(type) {
          if (type === 'add') {
          //  添加子部门
    
          } else if (type === 'edit') {
            // 编辑部门
    
          } else {
            // 删除部门
            // 删除之前,提示用户是否删除,然后调用删除接口
            this.$confirm('您确定要删除该组织部门吗').then(() => {
              return delDepartments(this.treeNode.id)
            }).then(() => {
              this.$emit('delDepts') // 触发自定义事件
              this.$message.success('删除部门成功')
            })
          }
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    在src/views/department/index.vue父组件监听事件:

    <tree-tools slot-scope="obj" :tree-node="obj.data" @delDepts="getDepartments" />
    
    • 1

    五、新增部门功能实现

    5.1 新增部门弹窗效果图

    在这里插入图片描述

    5.2 封装新增接口

    在src/api/departments.js中添加如下代码:

    /**
    *  新增部门接口
    *
    * ****/
    export function addDepartments(data) {
      return request({
        url: '/company/department',
        method: 'post',
        data
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    5.3 构建一个新增部门的窗体组件

    在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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    然后用属性控制组件的显示或者隐藏:

     // 需要传入一个props变量来控制 显示或者隐藏
      props: {
        showDialog: {
          type: Boolean,
          default: false
        }
        // 当前操作的节点
        treeNode: {
          type: Object,
          default: null
        }
      }
    
      <add-dept :show-dialog="showDialog" :tree-node="node" />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5.4 在组织架构模块中引入该组件

    在src/departments/index.vue中添加如下代码:

    import AddDept from './components/add-dept' // 引入新增部门组件
    export default {
      components: { AddDept }
    }
    
    data() {
        return {
          showDialog: false // 显示窗体
        }
      },
    
    <!-- 放置新增弹层组件  -->
    <add-dept :show-dialog="showDialog" />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    5.5 当点击新增部门按钮时弹出组件

    在src/views/departments/tree-tools.vue中添加如下代码:

      if (type === 'add') {
            // 添加子部门的操作
            // 告诉父组件 显示弹层
            this.$emit('addDepts', this.treeNode) // 为何传出treeNode 因为是添加子部门 需要当前部门的数据
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在src/departments/index.vue父组件中监听事件:

    <tree-tools slot-scope="obj" :tree-node="obj.data" @delDepts="getDepartments" @addDepts="addDepts"  />
    
    addDepts(node) {
          this.showDialog = true // 显示弹层
          // 因为node是当前的点击的部门, 此时这个部门应该记录下来,
          this.node = node
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5.6 新增部门的规则校验

    5.6.1 校验条件概述

    部门名称(name):必填 1-50个字符 / 同级部门中禁止出现重复部门
    部门编码(code):必填 1-50个字符 / 部门编码在整个模块中都不允许重复
    部门负责人(manager):必填
    部门介绍 ( introduce):必填 1-300个字符
    
    • 1
    • 2
    • 3
    • 4

    5.6.2 配置新增表单的基本校验规则

      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个字符' }]
          }
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    5.6.3 部门名称和部门编码的自定义校验

    首先,在校验名称和编码时,要获取最新的组织架构
    部门名称不能和同级别的重复,这里注意,我们需要找到所有同级别的数据,进行校验,所以还需要另一个参数pid
    根据当前部门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()
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    检查部门编码的过程同理:

    // 检查编码重复
        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()
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在规则中定义:

     // 定义校验规则
      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个字符' }]
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    5.6.4 解决根节点新增部门时的BUG

    在最根级的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: '' }
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.7 获取部门负责人数据

    在之前展示的表单中,部门负责人是下拉数据,我们应该从员工接口中获取该数据
    首先,在src/api/employees.js中封装获取简单员工列表的模块:

    import request from '@/utils/request'
    
    
    /**
    *  获取员工的简单列表
    * **/
    export function getEmployeeSimple() {
      return request({
        url: '/sys/user/simple'
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    然后,在src/views/department/components/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>
    
    • 1
    • 2
    • 3
    • 4

    最后获取员工列表:

    import  { getEmployeeSimple }   from '@/api/employees'
      methods: {
        // 获取员工简单列表数据
        async  getEmployeeSimple() {
          this.peoples = await getEmployeeSimple()
        }
      }
      peoples: [] // 接收获取的员工简单列表的数据
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5.8 新增部门的提交,取消,关闭功能

    当点击新增页面的确定按钮时,我们需要完成对表单的整体校验,如果校验成功,进行提交:
    在src/views/department/components/add-dept.vue中添加如下代码:

    //给el-form定义一个ref属性    
    <el-form ref="deptForm" :model="formData" :rules="rules" label-width="120px">
    
        // 点击确定时触发
        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)
            }
          })
        }
    
    // 父组件src/views/departments/index.vue使用sync修饰符
    <add-dept ref="addDept" :show-dialog.sync="showDialog" :tree-node="node" @addDepts="getDepartments" />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    取消时重置数据和校验

    btnCancel() {
          this.$refs.deptForm.resetFields() // 重置校验字段
          this.$emit('update:showDialog', false) // 关闭
    }
    
    //需要在el-dialog中监听其close事件
    <el-dialog title="新增部门" :visible="showDialog" @close="btnCancel">
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    六、给数据获取添加加载进度条

    由于获取数据的延迟性,为了更好的体验,可以给页面增加一个Loading进度条,采用element的指令解决方案即可

    loading: false // 用来控制进度弹层的显示和隐藏
    
    • 1

    总结

    今天主要实现了组织架构页面的相关业务逻辑,主要知识点包括树形结构的渲染,添加部门的规则校验,添加部门,删除部门,编辑部门的逻辑编写,总体来说难度不大, 但是代码量较多,需要耐心学习。

  • 相关阅读:
    css知识学习系列(10)-每天10个知识点
    基于PSO优化VIC算法的WORM蠕虫检测matlab仿真
    劫持TLS绕过canary && 堆和栈的灵活转换
    Java装饰者模式详解:为对象动态添加功能
    小林coding图解计算机网络|基础篇01|TCP/IP网络模型有哪几层?
    ⟅UNIX网络编程⟆⦔select函数的定义及参数
    List 对象集合,如何优雅地返回给前端?
    5 Flink CDC同步
    【wordPress】WordPress删除index.php后缀【亲测有效】(手把手教学)
    数字图像处理(十六)非局部均值去噪
  • 原文地址:https://blog.csdn.net/qq_40652101/article/details/126836202