HR-05-组织架构模块

377 阅读9分钟

组织架构模块

业务功能介绍

image.png

说明:组织架构模块主要管理公司所有部门信息,支持添加、删除、编辑操作

基本组件布局

**目标**:使用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)

树形组件用法

目标:熟悉树形组件的基本用法

tree组件

  • 树形组件关键属性 | 参数 | 说明 | 类型 | 可选值 | 默认值 | | --- | --- | --- | --- | --- | | 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 配置数据的属性名称

作用域插槽

目标:熟悉作用域插槽的基本用法

  • 插槽基本使用
  • 具名插槽
  • 作用域插槽

image.png

image.png

总结:作用域插槽用于实现:通过子组件传递给父组件插槽的数据定制子组件的模板

  1. 子组件把数据绑定到slot标签上即可
  2. 父组件通过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>

总结:拆分组件为了降低代码的冗余度,也方便维护

  1. 封装基本的树形条目的组件(抽取动态属性)
  2. 导入并使用组件,然后把数据注入进去

获取组织架构接口数据

**目标**获取真实的组织架构数据,并将其转化成树形数据显示在页面上

  • 封装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 (空则没有父级部门)

image.png

  • 封装工具函数 **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列表中的所有子部门

删除部门

**目标**实现操作功能的删除功能

  1. 封装接口删除部门
  2. 绑定点击按钮的事件
  • 首先,封装删除功能模块 **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" />

总结

  1. 绑定点击事件(基于Element组件进行绑定;基于原生click事件绑定的话,需要native修饰符)
  2. 删除需要提示
  3. 删除成功后需要通知父组件刷新列表:子向父传值

新增部门-基本功能

新建组件-准备弹层

我们需要构建一个新增部门的窗体组件 **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
  }
},

总结:

  1. index.vue把状态位传递给AddDept.vue组件
  2. 点击添加部门按钮事件发生在tree-tools.vue组件中,通知父组件打开弹窗
  3. 点击弹窗的关闭按钮,通知父组件关闭弹窗

点击 x 关闭弹层 (子传父)

控制添加部门组件的弹窗打开和关闭

  1. add-dept.vue给 dialog 注册 close 事件
<el-dialog title="添加部门" :visible="showDialog" @before-close="handleClose">
  1. AddDept.vue子传父, 传递 false
methods: {
  handleClose() {
    // 子传父
    this.$emit('on-close')
  }
}
  1. 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)

总结:

  1. 监听el-dialog组件的before-close事件
  2. 事件函数中通知父组件关闭弹窗
  3. 父子组件之间属性的双向绑定操作:修饰符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
},

总结

  1. 点击添加子部门时,需要记录当前部门的信息(id)
  2. 这个部门信息需要传递给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表单验证:

  1. 绑定el-form的model和rules属性
  2. 绑定el-form-item的prop属性
  3. 绑定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'] }
  ]
}

总结:

表单验证基本流程

  1. el-form绑定model/rules/ref
  2. el-form-item绑定prop
  3. 表单输入域双向数据绑定

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

**注意**:部门名称和部门编码的规则 有两条我们需要通过**自定义校验函数validator**来实现,

且判断部门名称和编码是否存在, 需要遍历, 推荐将来校验用 blur, 失去焦点校验一次即可

  • 部门名称(name):同级部门中禁止出现重复部门
  • 部门编码(code):部门编码在整个模块中都不允许重复
  1. 保存原始部门列表数据
// 获取原始的组织架构列表
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: [],
  1. 把原始部门列表数据传递给add-dept.vue组件
<add-dept :list="dlist" :dept-id="currentDeptId" :is-show-dept.sync="isShowDept" />
  1. 子组件接收原始列表数据
  props: {
    // 控制弹窗
    isShowDept: {
      type: Boolean,
      required: true
    },
    // 父级部门的id
    deptId: {
      type: String,
      required: true
    },
    // 所有部门的数据
    list: {
      type: Array,
      required: true
    }
  },
  1. 提供自定义校验函数
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' }
      ],
    }
  }
}

总结:

  1. 表单的自定义验证规则 validator
  2. 数组相关API的用法filter/some
  3. 父组件向子组件传递数据

处理首部内容的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" />

总结:

  1. 手动表单验证
  2. 添加部门需要指定父级部门pid
  3. 子组件向父组件传值

取消按钮操作

功能: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
},

总结:点击编辑按钮弹窗

  1. 子向父组件传值
  2. 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('编辑部门失败')
      }
    }
  })
},

总结:调用接口提交表单,关闭弹窗,刷新列表

  1. 子向父组件传值

编辑表单验证

除此之外,我们发现原来的校验规则实际和编辑部门有些冲突,所以需要进一步处理

  • 部门名称的重复验证(编辑的时候,需要验证同级部门名称是否重复;编辑的当前部门名称可以重复,但是当前部门名称不可以和同级部门名称重复)
  • 部门编号的重复验证(编辑的时候,需要验证全局是否有编号重复,编辑的当前部门的编号可以重复,但是当前部门的编号不可以和全局其他部门的编号重复)
// 验证部门名称
// 自定义验证规则:验证部门名称
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()
  }
}

总结:

  1. 验证编辑的部门名称时,需要注意:部门名称可以不做修改,验证要保证通过
  2. 验证部门编码时要保证全局不重复,但是编辑的部门编码可以不修改,验证要保证通过

由于获取数据的延迟性,为了更好的体验,可以给页面增加一个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
}