• ElementUI制作树形表组件


     

     

    提要

    最近项目中需要用到树形表格来描述部门、区域之间的父子展开关系。但是已经在项目中使用的Vue的成熟组件ElementUI以及iViewUI组件都没有提供相应的树形表格组件,无奈找了其他替代方案也都被pass掉了,只能从改造现有组件放面着手。

    在网上也找到了一些实践案例:http://blog.csdn.net/s8460049/article/details/61414751

    第一种方案

    第一种方案就是原作者介绍的,即将具有层级关系的数据进行提前处理。比如: 数据结构为:

    [
        {
            id: 1,
            parentId: 0,
            name: '测试1',
            age: 18,
            sex: '男',
            children: [
                {
                    id: 2,
                    parentId: 1,
                    name: '测试2',
                    age: 22,
                    sex: '男'
                }
            ]
        },
        {
            id: 3,
            parentId: 0,
            name: '测试3',
            age: 23,
            sex: '女',
            children: [
                {
                    id: 4,
                    parentId: 3,
                    name: '测试4',
                    age: 22,
                    sex: '男'
                },
                {
                    id: 5,
                    parentId: 3,
                    name: '测试5',
                    age: 25,
                    sex: '男'
                },
                {
                    id: 6,
                    parentId: 3,
                    name: '测试6',
                    age: 26,
                    sex: '女',
                    children: [
                        {
                            id: 7,
                            parentId: 6,
                            name: '测试7',
                            age: 27,
                            sex: '男'
                        }
                    ]
                }
            ]
        },
        {
            id: 18,
            parentId: 0,
            name: '测试8',
            age: 18,
            sex: '男'
        }
    ]

    这样可以通过数据转换方法,把每一条数据从它的父级中取出来,把树形结构数据转换成数组数据。

    dataTranslate.js内容:

    import Vue from 'vue'  
    function DataTransfer (data) {  
      if (!(this instanceof DataTransfer)) {  
        return new DataTransfer(data, null, null)  
      }  
    }  
      
      
    DataTransfer.treeToArray = function (data, parent, level, expandedAll) {  
      let tmp = []  
      Array.from(data).forEach(function (record) {  
        if (record._expanded === undefined) {  
          Vue.set(record, '_expanded', expandedAll)  
        }  
        if (parent) {  
          Vue.set(record, '_parent', parent)  
        }  
        let _level = 0  
        if (level !== undefined && level !== null) {  
          _level = level + 1  
        }  
        Vue.set(record, '_level', _level)  
        tmp.push(record)  
        if (record.children && record.children.length > 0) {  
          let children = DataTransfer.treeToArray(record.children, record, _level, expandedAll)  
          tmp = tmp.concat(children)  
        }  
      })  
      return tmp  
    }  
      
      
    export default DataTransfer

    有了进行数据转换的方法之后,开始正式些数据TreeGrid.vue组件:

    <template>  
      <el-table  
        :data="data"  
        border  
        style=" 100%"  
        :row-style="showTr">  
        <el-table-column v-for="(column, index) in columns" :key="column.dataIndex"  
          :label="column.text">  
          <template scope="scope">  
            <span v-if="spaceIconShow(index)" v-for="(space, levelIndex) in scope.row._level" class="ms-tree-space"></span>  
            <button class="button is-outlined is-primary is-small" v-if="toggleIconShow(index,scope.row)" @click="toggle(scope.$index)">  
              <i v-if="!scope.row._expanded" class="el-icon-caret-right" aria-hidden="true"></i>  
              <i v-if="scope.row._expanded" class="el-icon-caret-bottom" aria-hidden="true"></i>  
            </button>  
            <span v-else-if="index===0" class="ms-tree-space"></span>  
            {{scope.row[column.dataIndex]}}  
          </template>  
        </el-table-column>  
        <el-table-column label="操作" v-if="treeType === 'normal'" width="260">  
          <template scope="scope">  
            <button type="button" class="el-button el-button--default el-button--small">  
              <router-link  
                :to="{ path: requestUrl + 'edit', query: {id: scope.row.Oid} }"  
                tag="span">  
                编辑  
              </router-link>  
            </button>  
            <el-button  
              size="small"  
              type="danger"  
              @click="handleDelete()">  
              删除  
            </el-button>  
            <button type="button" class="el-button el-button--success el-button--small">  
              <router-link :to="{ path: requestUrl, query: {parentId: scope.row.parentOId} }"  
                           tag="span">  
                添加下级树结构  
              </router-link>  
            </button>  
          </template>  
        </el-table-column>  
      </el-table>  
    </template>  
    <script>  
      import DataTransfer from '../utils/dataTranslate.js'  
      import Vue from 'vue'  
      export default {  
        name: 'tree-grid',  
        props: {  
    // 该属性是确认父组件传过来的数据是否已经是树形结构了,如果是,则不需要进行树形格式化  
          treeStructure: {  
            type: Boolean,  
            default: function () {  
              return false  
            }  
          },  
    // 这是相应的字段展示  
          columns: {  
            type: Array,  
            default: function () {  
              return []  
            }  
          },  
    // 这是数据源  
          dataSource: {  
            type: Array,  
            default: function () {  
              return []  
            }  
          },  
    // 这个作用是根据自己需求来的,比如在操作中涉及相关按钮编辑,删除等,需要向服务端发送请求,则可以把url传过来  
          requestUrl: {  
            type: String,  
            default: function () {  
              return ''  
            }  
          },  
    // 这个是是否展示操作列  
          treeType: {  
            type: String,  
            default: function () {  
              return 'normal'  
            }  
          },  
    // 是否默认展开所有树  
          defaultExpandAll: {  
            type: Boolean,  
            default: function () {  
              return false  
            }  
          }  
        },  
        data () {  
          return {}  
        },  
        computed: {  
        // 格式化数据源  
          data: function () {  
            let me = this  
            if (me.treeStructure) {  
              let data = DataTransfer.treeToArray(me.dataSource, null, null, me.defaultExpandAll)  
              console.log(data)  
              return data  
            }  
            return me.dataSource  
          }  
        },  
        methods: {  
        // 显示行  
          showTr: function (row, index) {  
            let show = (row._parent ? (row._parent._expanded && row._parent._show) : true)  
            row._show = show  
            return show ? '' : 'display:none;'  
          },  
        // 展开下级  
          toggle: function (trIndex) {  
            let me = this  
            let record = me.data[trIndex]  
            record._expanded = !record._expanded  
          },  
        // 显示层级关系的空格和图标  
          spaceIconShow (index) {  
            let me = this  
            if (me.treeStructure && index === 0) {  
              return true  
            }  
            return false  
          },  
        // 点击展开和关闭的时候,图标的切换  
          toggleIconShow (index, record) {  
            let me = this  
            if (me.treeStructure && index === 0 && record.children && record.children.length > 0) {  
              return true  
            }  
            return false  
          },  
          handleDelete () {  
            this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {  
              confirmButtonText: '确定',  
              cancelButtonText: '取消',  
              type: 'error'  
            }).then(() => {  
              this.$message({  
                type: 'success',  
                message: '删除成功!'  
              })  
            }).catch(() => {  
              this.$message({  
                type: 'info',  
                message: '已取消删除'  
              })  
            })  
          }  
        }  
      }  
    </script>  
    <style scoped>  
      .ms-tree-space{position: relative;  
        top: 1px;  
        display: inline-block;  
        font-family: 'Glyphicons Halflings';  
        font-style: normal;  
        font-weight: 400;  
        line-height: 1;  
         18px;  
        height: 14px;}  
      .ms-tree-space::before{content: ""}  
      table td{  
        line-height: 26px;  
      }  
    </style>

    写好了树形表格组件,使用方式和普通的Vue组件使用方法相同:

    <template>  
      <div class="hello">  
        <tree-grid :columns="columns" :tree-structure="true" :data-source="dataSource"></tree-grid>  
      </div>  
    </template>  
      
    <script>  
    import {TreeGrid} from './TreeGrid'  
    export default {  
      name: 'hello',  
      data () {  
        return {  
          columns: [  
              {  
                text: '姓名',  
                dataIndex: 'name'  
              },  
              {  
                text: '年龄',  
                dataIndex: 'age'  
              },  
              {  
                text: '性别',  
                dataIndex: 'sex'  
              }  
            ],  
          dataSource: [  
            {  
              id: 1,  
              parentId: 0,  
              name: '测试1',  
              age: 18,  
              sex: '男',  
              children: [  
                {  
                  id: 2,  
                  parentId: 1,  
                  name: '测试2',  
                  age: 22,  
                  sex: '男'  
                }  
              ]  
            },  
            {  
              id: 3,  
              parentId: 0,  
              name: '测试3',  
              age: 23,  
              sex: '女',  
              children: [  
                {  
                  id: 4,  
                  parentId: 3,  
                  name: '测试4',  
                  age: 22,  
                  sex: '男'  
                },  
                {  
                  id: 5,  
                  parentId: 3,  
                  name: '测试5',  
                  age: 25,  
                  sex: '男'  
                },  
                {  
                  id: 6,  
                  parentId: 3,  
                  name: '测试6',  
                  age: 26,  
                  sex: '女',  
                  children: [  
                    {  
                      id: 7,  
                      parentId: 6,  
                      name: '测试7',  
                      age: 27,  
                      sex: '男'  
                    }  
                  ]  
                }  
              ]  
            },  
            {  
              id: 18,  
              parentId: 0,  
              name: '测试8',  
              age: 18,  
              sex: '男'  
            }  
          ]  
        }  
      },  
      components: {  
        TreeGrid  
      }  
    }  
    </script>

    以上就是实现树形表格的方法,提前把树形表格数据处理成数组数据,然后针对父级和子集分别增加不同的表示,以实现不同的表格首行显示效果。

    但是,该组件将说有数据都加在到Table中,然后采用操作css样式"display:none"的方式进行隐藏和显示。数量比较时候倒是可以完全满足使用,但是如果数据量超过100条或者更多就会出现页面卡顿的现象。

    第二种方式

    第二种方式在原方法的基础上进行操作,原理就是基于MVVM框架vue的数据驱动原理。采用操作数据的方式执行子级数据的显示隐藏。

    根据ElementUI 的 Table组件会根据数据变化进行加载的原理,通过只传入需要展示的数据的方式,来提高浏览器渲染的速度。

    直接上代码TreeGrid.vue:

    <template>
        <div class="table-content">
            <el-table
                :data="TableDate"
                border
                style=" 18.82rem">
                <el-table-column
                  label="部门名称"
                  min-width="400">
                  <template scope="scope">
                      <span v-for="(space, levelIndex) in scope.row._level" :key="levelIndex" class="ms-tree-space"></span>
                      <span class="button is-outlined is-primary is-small" v-if="toggleIconShow(scope.row)" @click="toggle(scope.row)">
                          <i v-if="!scope.row._expanded" class="el-icon-arrow-right" aria-hidden="true"></i>
                          <i v-if="scope.row._expanded" class="el-icon-arrow-down" aria-hidden="true"></i>
                      </span>
                      <span v-else class="ms-tree-space"></span>
                      <span :title="scope.row.dpmName">
                          {{ scope.row.dpmName }}
                      </span>
                  </template>
                </el-table-column>
                <el-table-column
                  label="组织机构代码"
                  min-width="300">
                  <template scope="scope">
                      <span :title="scope.row.dpmAdc">
                          {{ scope.row.dpmAdc }}
                      </span>
                  </template>
                </el-table-column>
                <el-table-column
                  label="所属地区"
                  min-width="300">
                  <template scope="scope">
                      <span :title="scope.row.areaName">
                          {{ scope.row.areaName }}
                      </span>
                  </template>
                </el-table-column>
                <el-table-column
                  label="上级部门"
                  min-width="315">
                  <template scope="scope">
                      <span :title="scope.row.parentName">
                          {{ scope.row.parentName }}
                      </span>
                  </template>
                </el-table-column>
            </el-table>
        </div>
    </template>
    
    <script>
        import {deepCopy} from "../utils/util.js"
        Array.prototype.removeByValue = function(val) {
            //对数组原型添加删除指定项的方法
            for(var i=0; i<this.length; i++) {
                if(this[i] == val) {
                    this.splice(i, 1);
                    break;
                }
            }
        };
        export default {
            name: 'TreeGrid',
            components: {
                
            },
            data(){
                return {
                    TableDate:[]
                }
            },
            computed:{
                allData(){
                    let me = this;
                    let newData = deepCopy(me.$store.getters.Data);
                    return newData;
                }
            },
            watch: {
                allData(val){
                    this.TableDate = deepCopy(val);
                }
            },
            methods: {
                toggleIconShow (record) {
                    /**
                     * 点击展开和关闭的时候,图标的切换
                     */
                    let me = this;
                    if (record.children && record.children.length > 0) {
                        return true
                    }
                    return false
                },
                toggle(rowData) {
                    let me = this;
                    /**
                     * 展开下级
                     */
                    let childLen = rowData.children.length;
                    if(rowData._expanded){
                        let dataArr=[];
                        dataArr.push(rowData);
                        let arr = me.getChildFlowId(dataArr,[]);
                        for(let i=0; i < childLen; i++){
                            me.TableDate.map((value)=>{
                                if(arr.indexOf(value.parentId) > -1){
                                    me.TableDate.removeByValue(value);
                                }
                            });
                        }
                    } else {
                        rowData.children = me.setSpaceIcon(rowData.children,rowData._level);
                        let index = me.TableDate.indexOf(rowData);
                        let pre = me.TableDate.slice(0,index+1);
                        let last = me.TableDate.slice(index+1);
                        let concatChildren = pre.concat(rowData.children);
                        me.TableDate = concatChildren.concat(last);
                    }
                    rowData._expanded = !rowData._expanded;
                },
                getChildFlowId(data,emptyArr){
                    // 获取子级的flowId
                    let me = this;
                    Array.from(data).forEach((record)=>{
                        emptyArr.push(record.flowId);
                        if(record.children&&record.children.length > 0){
                            let childFlowIdArr = me.getChildFlowId(record.children,emptyArr);
                            emptyArr.concat(childFlowIdArr);
                        }
                    });
                    return emptyArr;
                },
                setSpaceIcon(data,level){
                    // 设置第一列的空格和方向按钮
                    let me = this;
                    let _level = 0;
                    data.forEach((value)=>{
                        value._expanded = false;
                        if(level !== undefined && level !== null){
                            _level = level + 1;
                        } else {
                            _level = 1;
                        }
                        value._level = _level;
                        if(value.children&&value.children.length > 0){
                            me.setSpaceIcon(value.children, _level);
                        }
                    });
                    return data;
                }
            }
        }
    </script>

    虽然上了大段的代码,不过也有很多不细致的地方。重点还是理解实现的方式,尽量减少需要写个方法或者组件的时候,在网上Google一下就拿过来用,更多的应该是理解其中的原理,并且自己进行实践才能进步。

    不明白的地方,欢迎提问交流!

  • 相关阅读:
    【java】对象赋值给另一个对象
    spring boot系列(五)spring boot 配置spring data jpa (查询方法)
    Spring Data JPA 查询
    Spring Data JPA 介绍
    OpenID简介
    OAUTH协议介绍
    URL encoding(URL编码)
    RESTful 介绍
    spring boot系列(四)spring boot 配置spring data jpa (保存修改删除方法)
    spring boot 启动报 java.lang.NoClassDefFoundError: ch/qos/logback/core/spi/LifeCycle 错误
  • 原文地址:https://www.cnblogs.com/webbest/p/7354195.html
Copyright © 2020-2023  润新知