• 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一下就拿过来用,更多的应该是理解其中的原理,并且自己进行实践才能进步。

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

  • 相关阅读:
    20169221 2016-2017-2 《移动平台应用开发实践》第十一周学习总结
    20169221 2016-2017-2 《移动平台应用与开发》第十周实验总结
    20169201 2016-2017-2 《网络攻防实践》实验三 缓冲区溢出实验
    20169218 2016-2017-2 《网络攻防实践》第九周作业
    2016-2017 《移动平台开发》实验三 敏捷开发与XP实践
    20169221 2016-2017-2 实验一 网络攻防环境的搭建与测试
    实验二《Java面向对象》实验报告
    20169221 2016-2017-2 《移动平台开发》第七周学习总结
    20169221 2016-2017-2 《网络攻防》第七周学习总结
    20169221 2016-2017-2 《移动平台开发》第六周学习总结
  • 原文地址:https://www.cnblogs.com/webbest/p/7354195.html
Copyright © 2020-2023  润新知