Vue project actual human resources platform system (5) organizational structure module

foreword

1. Rendering diagram of organizational structure module

insert image description here

2. Organizational structure module structure layout

2.1 Separate the tree structure into components

Create a new tree-tools.vue component in the src/views/departments/components/ directory:

<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>

2.2 Apply tree structure components in the organizational structure module

Simplify the code in 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' // 表示 从这个属性显示内容
      },
    }
  },
}

3. Obtain organizational structure data and perform tree processing

3.1 Encapsulate the API interface to obtain organizational structure data

First build the request in the src/api/departments.js file:

/** *
*
* 获取组织架构数据
* **/
export function getDepartments() {
    
    
  return request({
    
    
    url: '/company/department'
  })
}

Then call the interface in 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 // 需要将其转化成树形结构
    }
  }
}

3.2 Convert array data into a tree structure

It is necessary to convert the obtained list data into tree data, and a recursive algorithm is needed here:
encapsulate a method in 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
}

Back in src/views/departments/index.vue, import method:

import {
    
     tranListToTreeData } from '@/utils'

this.departs = transListToTreeData(result.depts, '')

4. Realization of delete department function

4.1 Encapsulation delete interface

Add the following code in src/api/departments.js:

/** *
*  根据id删除部门
* **/
export function delDepartments(id) {
    
    
  return request({
    
    
    url: `/company/department/${
      
      id}`,
    method: 'delete'
  })
}

4.2 Listen to the click event of the drop-down menu and call the delete interface

Add the following code in 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('删除部门成功')
        })
      }
    }
  }

Listen to events in the src/views/department/index.vue parent component:

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

5. Realization of new department functions

5.1 Effect picture of new department pop-up window

insert image description here

5.2 Package new interface

Add the following code in src/api/departments.js:

/**
*  新增部门接口
*
* ****/
export function addDepartments(data) {
    
    
  return request({
    
    
    url: '/company/department',
    method: 'post',
    data
  })
}

5.3 Build a form component for a new department

Create a new add-dept.vue component in the src/views/department/components/ directory:

<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>

Then use attributes to control the display or hiding of components:

 // 需要传入一个props变量来控制 显示或者隐藏
  props: {
    
    
    showDialog: {
    
    
      type: Boolean,
      default: false
    }
    // 当前操作的节点
    treeNode: {
    
    
      type: Object,
      default: null
    }
  }

  <add-dept :show-dialog="showDialog" :tree-node="node" />

5.4 Introduce this component in the organizational structure module

Add the following code in src/departments/index.vue:

import AddDept from './components/add-dept' // 引入新增部门组件
export default {
    
    
  components: {
    
     AddDept }
}

data() {
    
    
    return {
    
    
      showDialog: false // 显示窗体
    }
  },

<!-- 放置新增弹层组件  -->
<add-dept :show-dialog="showDialog" />

5.5 Pop-up component when clicking the Add Department button

Add the following code in src/views/departments/tree-tools.vue:

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

Listen to events in the src/departments/index.vue parent component:

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

addDepts(node) {
    
    
      this.showDialog = true // 显示弹层
      // 因为node是当前的点击的部门, 此时这个部门应该记录下来,
      this.node = node
}

5.6 Check the rules of the new department

5.6.1 Overview of calibration conditions

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

5.6.2 Configure the basic validation rules for the newly added form

  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个字符' }]
      }
    }
  }

5.6.3 Custom verification of department name and department code

First of all, when verifying the name and code, it is necessary to obtain the latest organizational structure.
The department name cannot be repeated at the same level. Note here that we need to find all data at the same level for verification, so another parameter pid is needed
according to the current Department id, find data related to all sub-departments, and determine whether it is repeated:

// 现在定义一个函数 这个函数的目的是 去找 同级部门下 是否有重复的部门名称
    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()
    }

The process of checking the department code is the same:

// 检查编码重复
    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()
    }

Defined in the rule:

 // 定义校验规则
  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个字符' }]
  }

5.6.4 Solve the bug when adding a department to the root node

In the root-level tree-tools component, since there is no id in the treenode attribute, the id is undefined,
but the corresponding root node cannot be found through the equivalent judgment of undefined, so when passing the value, we set the id attribute
Modify the code in the src/views/departments/index.vue file for "" :

async getDepartments() {
    
    
      const result = await getDepartments()
      this.departs = transListToTreeData(result.depts, '')
      this.company = {
    
     name: result.companyName, manager: '负责人', id: '' }
},

5.7 Obtain data of department heads

In the form shown before, the department head is the drop-down data, we should get the data from the employee interface
First, encapsulate the module to get the simple employee list in src/api/employees.js:

import request from '@/utils/request'


/**
*  获取员工的简单列表
* **/
export function getEmployeeSimple() {
    
    
  return request({
    
    
    url: '/sys/user/simple'
  })
}

Then, call this interface in the select focus event focus in src/views/department/components/add-dept.vue:

   <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>

Finally get the list of employees:

import  {
    
     getEmployeeSimple }   from '@/api/employees'
  methods: {
    
    
    // 获取员工简单列表数据
    async  getEmployeeSimple() {
    
    
      this.peoples = await getEmployeeSimple()
    }
  }
  peoples: [] // 接收获取的员工简单列表的数据

5.8 Submit, cancel and close functions of new departments

When clicking the OK button on the new page, we need to complete the overall verification of the form. If the verification is successful, submit it: add the
following code in 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" />

Reset data and checksums on cancel

btnCancel() {
    
    
      this.$refs.deptForm.resetFields() // 重置校验字段
      this.$emit('update:showDialog', false) // 关闭
}

//需要在el-dialog中监听其close事件
<el-dialog title="新增部门" :visible="showDialog" @close="btnCancel">

6. Add a loading progress bar to data acquisition

Due to the delay in obtaining data, for a better experience, you can add a Loading progress bar to the page, and use the element command solution

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

Summarize

Today, the relevant business logic of the organizational structure page is mainly realized. The main knowledge points include the rendering of the tree structure, the rule verification of adding departments, adding departments, deleting departments, and editing the logic of departments. Generally speaking, it is not difficult, but the code The amount is large, and it takes patience to learn.

Guess you like

Origin blog.csdn.net/qq_40652101/article/details/126836202