5

使用Vue-TreeSelect组件实现公司-部门-人员级联下拉列表的处理

 3 years ago
source link: https://www.cnblogs.com/wuhuacong/p/14681187.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近在改造原有Bootstrap开发框架,增加一个Vue&Element前端的时候,发现需要处理一个级联更新的过程,就是选择公司,然后更新部门,选择部门,或者人员列表,选择作为主管的一个实现,不同于Bootstrap使用Select2的插件,这里前端是Vue&Element,那么我们可以选择下拉列表的方式展现,在Element中可以考虑使用Cascader 级联选择器,也可以考虑使用封装Tree 树形控件,或者使用第三方组件Vue-TreeSelect组件。本篇随笔介绍使用Vue-TreeSelect组件实现公司-部门-人员级联下拉列表的处理过程。

1、Vue-TreeSelect组件的使用

在我早期随笔《循序渐进VUE+Element 前端应用开发(8)--- 树列表组件的使用》中也大概介绍了一下Vue-TreeSelect组件,这里我们再来回顾一下它的用法。

GitHub地址:https://github.com/riophae/vue-treeselect

官网地址:https://vue-treeselect.js.org/

NPM安装:

npm install --save @riophae/vue-treeselect

界面代码如下所示。

<template>
  <div id="app">
    <treeselect v-model="value" :multiple="true" :options="options" />
  </div>
</template>

这里的value就是选中的集合,options则是树列表的节点数据,和Element中的Tree组件一样,options的格式也包含id, lable, children这几个属性。

如果常规的数据提供,我们只要准备这些数据格式给options即可。

如下面的数据格式。

      treedata: [// 初始化树列表
        { // 默认数据
          label: '一级 1',
          children: [{
            label: '二级 1-1'
          }]
        }
      ]

不过我们一般数据是动态从后端接口中提取的,不是静态的,所以需要使用相应的方法来获取,并设置。

如果是后端接口无法满足特定的属性名称,那么Vue-TreeSelect组件也提供了一个 normalizer 属性方法用来重定义节点属性名称

 类似下面的javascript代码

export default {
  data: () => ({
    value: null,
    options: [ {
      key: 'a',
      name: 'a',
      subOptions: [ {
        key: 'aa',
        name: 'aa',
      } ],
    } ],
    normalizer(node) {
      return {
        id: node.key,
        label: node.name,
        children: node.subOptions,
      }
    },
  }),
}

通过normalizer 属性方法可以把数据源的属性映射到树列表中去。有时候我们对于空列表,可能还需要判断为空,并移除这个属性,代码如下所示。

  normalizer (node) {
    if (node.children && !node.children.length) {
      delete node.children
    }
    return {
      id: node.key,
      label: node.name,
      children: node.children,
    }
  },

另外,有时候需要在列表值变化的时候,触发级联更新,那么就需要处理@input事件了。

  <treeselect
    :options="options"
    :value="value"
    :searchable="false"
    @input="updateValue"
    />

2、公司-部门-人员级联下拉列表的处理

综合上面的几个特点,我们公司-部门-人员级联下拉列表的处理就需要上面的知识点来处理。

 在上面的弹出对话框中,选择所属公司,默认部门,所属经理的操作,级联处理过程效果如下所示。

 界面代码如下所示

  <el-col :span="12">
    <el-form-item label="所属公司" prop="company_ID">
      <treeselect :options="myGroupCompany" v-model="addForm.company_ID" :searchable="false"
        :default-expand-level="Infinity" :open-on-click="true" :open-on-focus="true"
        @input="updateGroupCompany" placeholder="所属公司" />
    </el-form-item>
  </el-col>
  <el-col :span="12">
    <el-form-item label="默认部门" prop="dept_ID">
      <treeselect :options="myDeptTree" v-model="addForm.dept_ID" :searchable="false"
        :default-expand-level="Infinity" :open-on-click="true" :open-on-focus="true" @input="updateDeptUser"
        :normalizer="normalizer" placeholder="所属部门" />
    </el-form-item>
  </el-col>
  <el-col :span="12">
    <el-form-item label="所属经理" prop="pid">
      <treeselect :options="myDeptUser" v-model="addForm.pid" :searchable="false"
        :default-expand-level="Infinity" :open-on-click="true" :open-on-focus="true"
        :normalizer="normalizer" placeholder="所属经理" />
    </el-form-item>
  </el-col>

如第一项公司列表,我们获取列表后设置options的对象即可。这里面需要定义几个变量 myGroupCompany、myDeptTree、myDeptUser的集合属性。

这里保留了normalizer 映射新属性的做法,不过由于属性名称默认和树控件的属性一致,也可以省略。

在其中更新处理,用到了 @input="updateGroupCompany" 、@input="updateDeptUser" 用于触发更新其他关联内容的事件。

另外一点,我们的新增或者编辑框中v-modal中关联的值,需要设置为null即可。

  addForm: {// 新建表单
    id: '',
    pid: null,
    dept_ID: null,
    company_ID: null,
    ................
  },

在显示弹出对话框,打开新增用户的时候,需要触发获取公司信息列表,如下所示。

    showAdd () {
      this.resetForm('addForm')
      this.initData() //打开新增窗体的时候,初始化公司列表
      this.isAdd = true
    },

而其中initData的函数操作如下所示。

    async initData () {
      var param = {}
      await ou.GetMyGroupCompany(param).then(data => {
        console.log(data.result)
        var newTreedata = getJsonTree(data.result, {
          id: 'id',
          pid: 'pid',
          children: 'children',
          label: 'name'
        });
        this.myGroupCompany = newTreedata
      })
    },

这里调用ou的api进行获取公司信息的操作

import request from '@/utils/request'

import BaseApi from '@/api/base-api'
// 业务类自定义接口实现, 通用的接口已经在BaseApi中定义
class Api extends BaseApi {
  // 获取集团公司列表。如果是超级管理员,返回集团+公司节点;如果是公司管理员,返回其公司节点
  GetMyGroupCompany(data) {
    return request({
      url: this.baseurl + 'GetMyGroupCompany',
      method: 'get',
      params: data
    })
  }    
  ..........
}

而公司信息触发部门更新,我们用如下函数来处理变化。

    async updateGroupCompany (value, instanceId) {
      // console.log(value + '~' + instanceId)

      this.addForm.dept_ID = null //置空控件内容
      if (!this.isEmpty(value)) {
        var param = { parentId: value }
        await user.GetDeptJsTreeJson(param).then(data => {
          this.myDeptTree = data.result
        })
      }
    },

由于User的API中 GetDeptJsTreeJson返回的是符合树控件节点属性名称的,因此可以直接赋值给vue-TreeSelect的opition值。

<treeselect :options="myDeptTree" v-model="addForm.dept_ID" :searchable="false"
   :default-expand-level="Infinity" :open-on-click="true" :open-on-focus="true" @input="updateDeptUser"
   :normalizer="normalizer" placeholder="所属部门" />

而部门选择后,则触发部门用户列表的更新,如下代码所示。

    async updateDeptUser (value, instanceId) {
      // console.log(value + '~' + instanceId)
      this.addForm.pid = null //置空控件内容
      if (!this.isEmpty(value)) {
        var param = { deptId: value }
        await user.GetUserDictJson(param).then(data => {
          this.myDeptUser = data.result
        })
      }
    },

同样,由于由于User的API中 GetUserDictJson 返回的是符合树控件节点属性名称的,因此可以直接赋值给vue-TreeSelect的opition值。

<treeselect :options="myDeptUser" v-model="addForm.pid" :searchable="false"
   :default-expand-level="Infinity" :open-on-click="true" :open-on-focus="true"
   :normalizer="normalizer" placeholder="所属经理" />

3、特殊处理的内容

 前面我们介绍了,如果获取内容和树控件的属性不一致,需要进行转义映射,如下代码所示。

      normalizer (node) {
        if (node.children && !node.children.length) {
          delete node.children
        }
        return {
          id: node.id,
          label: node.label,
          children: node.children,
        }
      },

并在界面代码上指定normalizer处理。

:normalizer="normalizer"

有时候,我们返回的对象集合可能是一个二维列表内容,它本身有id,pid来标识它的层次关系,那么如果我们转换为嵌套列表的话,就可以使用getJsonTree 方法进行转换。

具体操作可以参考:https://blog.csdn.net/unamattin/article/details/77152451 

使用的时候,导入这个类方法即可。

import { getJsonTree } from '@/utils/json-tree.js' // 转换二维表数据为树列表数据的辅助类

如果前面介绍的

    async initData () {
      var param = {}
      await ou.GetMyGroupCompany(param).then(data => {
        console.log(data.result)
        var newTreedata = getJsonTree(data.result, {
          id: 'id',
          pid: 'pid',
          children: 'children',
          label: 'name'
        });
        this.myGroupCompany = newTreedata
      })
    },

如果两个都是嵌套结构的树列表,但是属性名称不同,那么也可以通过map的操作方法,定义一个js函数进行转换即可,转换的代码如下所示。

    getTree () { // 树列表数据获取
      var param = {}
      user.GetMyDeptJsTreeJson(param).then(data => {
        // console.log(data)
        this.treedata = [];// 树列表清空
        var list = data.result
        if (list) {
          this.treedata = list
        }

        //修改另一个Treedata
        const ass = (data) => {
          let item = [];
          data.map((list, i) => {
            let newData = {};
            newData.id = list.id;
            newData.label = list.label;
            newData.children = list.children ? ass(list.children) : null;    //如果还有子集,就再次调用自己
            //如果列表为空,则移除children
            if (list.children && !list.children.length) {
              delete newData.children;
            }
            item.push(newData);
          });
          return item;
        }
        this.selectTreeData = ass(list)
      });
    },

以上就是数据层次结构相同,属性名称不同的时候,进行转换处理的另外一种方式。

当然,我们定义返回列表数据的时候,如果需要用来绑定在树列表中的,也可以在后端WebAPI进行定义好符合格式的数据,避免在前端额外的代码转换。

        /// <summary>
        /// 根据用户获取对应人员层次(给树控件显示的下拉列表)(值为ID)
        /// </summary>
        /// <param name="deptId">用户所在部门</param>
        /// <returns></returns>
        public List<TreeNodeItem> GetUserDictJson(int deptId)
        {
            var itemList = new List<TreeNodeItem>();
            itemList.Insert(0, new TreeNodeItem("-1", "无"));

            var list = BLLFactory<User>.Instance.FindByDept(deptId);
            foreach (var info in list)
            {
                itemList.Add(new TreeNodeItem(info.ID, info.FullName));
            }

            return itemList;
        }

其中 TreeNodeItem 类定义了Id, Label,Children的属性,这样前端就可以直接绑定使用了。

另外,在提一下,使用Vue-TreeSelect组件的时候,有时候需要封装它为自定义组件,那么normalizer也会作为prop属性作为配置的,这个时候,可以在自定义组件中定义好默认的normalizer。具体代码如下所示。

<template>
  <div>
    <div class="flex-container">
      <div class="flex-item">
        <treeselect ref="tree" v-model="svalue" :disabled="disabled" :options="options" :multiple="false" :flat="false"
          :default-expand-level="Infinity" :open-on-click="true" :open-on-focus="true" clearable :max-height="200"
          :placeholder="placeholder" :normalizer="normalizer" />
      </div>
      <div v-if="showcheck" class="flex-item">
        <el-checkbox v-model="isTop" :label="checkboxLable" border @change="checkChange" />
      </div>
    </div>
  </div>
</template>

那么prop中的normalizer的定义如下所示。

使用这个自定义组件的时候,可以指定它的normalizer。

<MyTreeselectTop v-model="editForm.pid" :options="selectTreeData" :normalizer="normalizer" />

以上就是前后端树列表的绑定处理,以及使用Vue-TreeSelect组件实现公司-部门-人员级联下拉列表的功能操作,希望大家不吝赐教。

把Bootstrap框架界面改造为Vue+Element前端界面后,

页面列表效果如下所示。

 编辑界面效果如下所示。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK